@liiift-studio/sanity-font-manager 2.3.19 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +437 -437
- package/dist/UploadModal-6LIX7XOK.js +6 -0
- package/dist/UploadModal-NME2W53V.mjs +6 -0
- package/dist/chunk-646WCBRR.mjs +7276 -0
- package/dist/chunk-FH4QKHOH.js +7276 -0
- package/dist/index.js +747 -1675
- package/dist/index.mjs +400 -1237
- package/package.json +85 -85
- package/src/components/BatchUploadFonts.jsx +653 -639
- package/src/components/BulkActions.jsx +99 -0
- package/src/components/ExistingDocumentResolver.jsx +152 -0
- package/src/components/FontReviewCard.jsx +415 -0
- package/src/components/FontScriptUploaderComponent.jsx +463 -463
- package/src/components/GenerateCollectionsPairsComponent.jsx +259 -259
- package/src/components/KeyValueInput.jsx +95 -95
- package/src/components/KeyValueReferenceInput.jsx +254 -254
- package/src/components/NestedObjectArraySelector.jsx +146 -146
- package/src/components/PriceInput.jsx +26 -26
- package/src/components/PrimaryCollectionGeneratorTypeface.jsx +116 -116
- package/src/components/RegenerateSubfamiliesComponent.jsx +185 -185
- package/src/components/SetOTF.jsx +87 -87
- package/src/components/SingleUploaderTool.jsx +672 -673
- package/src/components/StatusDisplay.jsx +26 -26
- package/src/components/StyleCountInput.jsx +16 -16
- package/src/components/UpdateScriptsComponent.jsx +76 -76
- package/src/components/UploadButton.jsx +43 -43
- package/src/components/UploadModal.jsx +268 -0
- package/src/components/UploadScriptsComponent.jsx +539 -537
- package/src/components/UploadStep1Settings.jsx +272 -0
- package/src/components/UploadStep2Review.jsx +472 -0
- package/src/components/UploadStep3Execute.jsx +234 -0
- package/src/components/UploadSummary.jsx +196 -0
- package/src/components/VariableInstanceReferencesInput.jsx +190 -190
- package/src/hooks/useNestedObjects.js +92 -92
- package/src/hooks/useSanityClient.js +9 -9
- package/src/index.js +115 -70
- package/src/schema/openTypeField.js +1945 -1945
- package/src/schema/styleCountField.js +12 -12
- package/src/schema/stylesField.js +268 -268
- package/src/schema/stylisticSetField.js +301 -301
- package/src/utils/buildUploadPlan.js +325 -0
- package/src/utils/executeUploadPlan.js +437 -0
- package/src/utils/executionReducer.js +56 -0
- package/src/utils/fontHelpers.js +267 -0
- package/src/utils/generateCssFile.js +207 -205
- package/src/utils/generateFontData.js +98 -145
- package/src/utils/generateFontFile.js +38 -38
- package/src/utils/generateKeywords.js +185 -185
- package/src/utils/generateSubset.js +45 -45
- package/src/utils/getEmptyFontKit.js +101 -99
- package/src/utils/parseFont.js +55 -0
- package/src/utils/parseVariableFontInstances.js +211 -211
- package/src/utils/planReducer.js +517 -0
- package/src/utils/planTypes.js +183 -0
- package/src/utils/processFontFiles.js +529 -477
- package/src/utils/regenerateFontData.js +146 -146
- package/src/utils/resolveExistingFont.js +87 -0
- package/src/utils/sanitizeForSanityId.js +65 -65
- package/src/utils/updateFontPrices.js +94 -94
- package/src/utils/updateTypefaceDocument.js +149 -160
- package/src/utils/uploadFontFiles.js +405 -316
- package/src/utils/utils.js +24 -24
|
@@ -1,673 +1,672 @@
|
|
|
1
|
-
// Per-font file manager — TTF/OTF/WOFF/WOFF2/CSS rows always visible; EOT/SVG/WEB/SUBSET/DATA behind an advanced toggle
|
|
2
|
-
|
|
3
|
-
import React, { useState, useEffect, useCallback, useMemo } from 'react';
|
|
4
|
-
import { Button, Grid, Stack, Flex, Box, Text, Card } from '@sanity/ui';
|
|
5
|
-
import { TrashIcon, ControlsIcon } from '@sanity/icons';
|
|
6
|
-
import { useFormValue, set, unset } from 'sanity';
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
} from '../utils/
|
|
16
|
-
import
|
|
17
|
-
import
|
|
18
|
-
import
|
|
19
|
-
import
|
|
20
|
-
import
|
|
21
|
-
import
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* @param {Object} props
|
|
28
|
-
* @param {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const [
|
|
37
|
-
const [
|
|
38
|
-
const [
|
|
39
|
-
const [
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const
|
|
43
|
-
const
|
|
44
|
-
const
|
|
45
|
-
const
|
|
46
|
-
const
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
fileInput?.
|
|
63
|
-
fileInput?.
|
|
64
|
-
fileInput?.
|
|
65
|
-
fileInput?.
|
|
66
|
-
fileInput?.
|
|
67
|
-
fileInput?.
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if (cur.originalFilename.endsWith('.
|
|
82
|
-
else if (cur.originalFilename.endsWith('.
|
|
83
|
-
else if (cur.originalFilename.endsWith('.woff2') && cur._id ===
|
|
84
|
-
else if (cur.originalFilename.endsWith('.woff2')
|
|
85
|
-
else if (cur.originalFilename.endsWith('.
|
|
86
|
-
else if (cur.originalFilename.endsWith('.
|
|
87
|
-
else if (cur.originalFilename.endsWith('.
|
|
88
|
-
else if (cur.originalFilename.endsWith('.
|
|
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
|
-
setStatus('
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
setError(
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
setStatus('
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
setError(
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
setError(
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
const
|
|
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
|
-
setError(
|
|
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
|
-
setStatus('
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
setError(
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
const
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
setError(
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
const
|
|
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
|
-
const
|
|
352
|
-
const
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
);
|
|
356
|
-
const
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
setError(
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
setError(
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
setError(
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
.
|
|
448
|
-
.
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
setError(
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
const
|
|
470
|
-
const
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
<
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
const
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
<
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
const
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
!fileInput?.
|
|
644
|
-
!fileInput?.
|
|
645
|
-
!fileInput?.
|
|
646
|
-
!fileInput?.
|
|
647
|
-
!
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
{renderFontSection('
|
|
659
|
-
{renderFontSection('
|
|
660
|
-
{
|
|
661
|
-
{showAdvanced && renderTopLevelAssetSection('
|
|
662
|
-
{showAdvanced &&
|
|
663
|
-
{showAdvanced && renderFontSection('
|
|
664
|
-
{
|
|
665
|
-
{
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
};
|
|
1
|
+
// Per-font file manager — TTF/OTF/WOFF/WOFF2/CSS rows always visible; EOT/SVG/WEB/SUBSET/DATA behind an advanced toggle
|
|
2
|
+
|
|
3
|
+
import React, { useState, useEffect, useCallback, useMemo } from 'react';
|
|
4
|
+
import { Button, Grid, Stack, Flex, Box, Text, Card } from '@sanity/ui';
|
|
5
|
+
import { TrashIcon, ControlsIcon } from '@sanity/icons';
|
|
6
|
+
import { useFormValue, set, unset } from 'sanity';
|
|
7
|
+
import { parseFont } from '../utils/parseFont';
|
|
8
|
+
|
|
9
|
+
import { useSanityClient } from '../hooks/useSanityClient';
|
|
10
|
+
import {
|
|
11
|
+
readFontFile,
|
|
12
|
+
extractFontMetadata,
|
|
13
|
+
determineWeight,
|
|
14
|
+
} from '../utils/processFontFiles';
|
|
15
|
+
import { generateStyleKeywords } from '../utils/generateKeywords';
|
|
16
|
+
import generateCssFile from '../utils/generateCssFile';
|
|
17
|
+
import generateFontData from '../utils/generateFontData';
|
|
18
|
+
import generateFontFile from '../utils/generateFontFile';
|
|
19
|
+
import generateSubset from '../utils/generateSubset';
|
|
20
|
+
import { parseVariableFontInstances } from '../utils/parseVariableFontInstances';
|
|
21
|
+
import StatusDisplay from './StatusDisplay';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Font file manager rendered inside a font document.
|
|
25
|
+
* Shows TTF/OTF/WOFF/WOFF2/WEB/SUBSET/EOT/SVG/CSS/DATA rows with Upload/Build/Delete controls.
|
|
26
|
+
* @param {Object} props
|
|
27
|
+
* @param {Object} props.elementProps
|
|
28
|
+
* @param {Function} props.onChange
|
|
29
|
+
*/
|
|
30
|
+
export const SingleUploaderTool = (props) => {
|
|
31
|
+
const client = useSanityClient();
|
|
32
|
+
|
|
33
|
+
const { elementProps: { ref }, onChange } = props;
|
|
34
|
+
|
|
35
|
+
const [message, setMessage] = useState('');
|
|
36
|
+
const [status, setStatus] = useState('ready');
|
|
37
|
+
const [error, setError] = useState(false);
|
|
38
|
+
const [filenames, setFilenames] = useState({});
|
|
39
|
+
const [showAdvanced, setShowAdvanced] = useState(false);
|
|
40
|
+
|
|
41
|
+
const fileInput = useFormValue(['fileInput']);
|
|
42
|
+
const doc_id = useFormValue(['_id']);
|
|
43
|
+
const doc_title = useFormValue(['title']);
|
|
44
|
+
const doc_typefaceName = useFormValue(['typefaceName']);
|
|
45
|
+
const doc_variableFont = useFormValue(['variableFont']);
|
|
46
|
+
const doc_weight = useFormValue(['weight']);
|
|
47
|
+
const doc_style = useFormValue(['style']);
|
|
48
|
+
const doc_slug = useFormValue(['slug']);
|
|
49
|
+
const doc_metaData = useFormValue(['metaData']);
|
|
50
|
+
|
|
51
|
+
const { weightKeywordList, italicKeywordList } = useMemo(() => generateStyleKeywords(), []);
|
|
52
|
+
|
|
53
|
+
useEffect(() => { handleSetFilenames(); }, [fileInput]);
|
|
54
|
+
|
|
55
|
+
/** Fetches originalFilename for each asset ref in fileInput (including woff2_web/woff2_subset). */
|
|
56
|
+
const handleSetFilenames = useCallback(async () => {
|
|
57
|
+
const woff2WebRef = fileInput?.woff2_web?.asset?._ref ?? null;
|
|
58
|
+
const woff2SubsetRef = fileInput?.woff2_subset?.asset?._ref ?? null;
|
|
59
|
+
|
|
60
|
+
const assetIds = [
|
|
61
|
+
fileInput?.ttf?.asset?._ref,
|
|
62
|
+
fileInput?.otf?.asset?._ref,
|
|
63
|
+
fileInput?.woff?.asset?._ref,
|
|
64
|
+
fileInput?.woff2?.asset?._ref,
|
|
65
|
+
fileInput?.eot?.asset?._ref,
|
|
66
|
+
fileInput?.svg?.asset?._ref,
|
|
67
|
+
fileInput?.css?.asset?._ref,
|
|
68
|
+
woff2WebRef,
|
|
69
|
+
woff2SubsetRef,
|
|
70
|
+
].filter(Boolean);
|
|
71
|
+
|
|
72
|
+
if (assetIds.length === 0) { setFilenames({}); return; }
|
|
73
|
+
|
|
74
|
+
const assetData = await client.fetch(
|
|
75
|
+
`*[_id in $assetIds]{ _id, originalFilename }`,
|
|
76
|
+
{ assetIds }
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const fontNames = assetData.reduce((acc, cur) => {
|
|
80
|
+
if (cur.originalFilename.endsWith('.ttf')) acc.ttf = cur.originalFilename;
|
|
81
|
+
else if (cur.originalFilename.endsWith('.otf')) acc.otf = cur.originalFilename;
|
|
82
|
+
else if (cur.originalFilename.endsWith('.woff2') && cur._id === woff2WebRef) acc.woff2_web = cur.originalFilename;
|
|
83
|
+
else if (cur.originalFilename.endsWith('.woff2') && cur._id === woff2SubsetRef) acc.woff2_subset = cur.originalFilename;
|
|
84
|
+
else if (cur.originalFilename.endsWith('.woff2')) acc.woff2 = cur.originalFilename;
|
|
85
|
+
else if (cur.originalFilename.endsWith('.woff')) acc.woff = cur.originalFilename;
|
|
86
|
+
else if (cur.originalFilename.endsWith('.eot')) acc.eot = cur.originalFilename;
|
|
87
|
+
else if (cur.originalFilename.endsWith('.svg')) acc.svg = cur.originalFilename;
|
|
88
|
+
else if (cur.originalFilename.endsWith('.css')) acc.css = cur.originalFilename;
|
|
89
|
+
return acc;
|
|
90
|
+
}, {});
|
|
91
|
+
|
|
92
|
+
setFilenames(fontNames);
|
|
93
|
+
}, [fileInput, client]);
|
|
94
|
+
|
|
95
|
+
/** Regenerates the @font-face CSS file from the stored woff2 asset. */
|
|
96
|
+
const handleGenerateCssFile = useCallback(async () => {
|
|
97
|
+
setMessage('Building CSS: ' + doc_title + '.css');
|
|
98
|
+
setStatus('Building CSS file');
|
|
99
|
+
setError(false);
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
const woff2AssetRef = fileInput?.woff2?.asset?._ref;
|
|
103
|
+
if (!woff2AssetRef) throw new Error('No woff2 file available');
|
|
104
|
+
|
|
105
|
+
const [woff2Asset] = await client.fetch(
|
|
106
|
+
`*[_id == $id]{ originalFilename, url }`,
|
|
107
|
+
{ id: woff2AssetRef }
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
const blob = await (await fetch(woff2Asset.url)).blob();
|
|
111
|
+
|
|
112
|
+
const newFileInput = await generateCssFile({
|
|
113
|
+
woff2File: blob,
|
|
114
|
+
fileInput: fileInput,
|
|
115
|
+
fontName: doc_title,
|
|
116
|
+
fileName: woff2Asset.originalFilename.replace('.woff2', ''),
|
|
117
|
+
variableFont: doc_variableFont,
|
|
118
|
+
weight: doc_weight,
|
|
119
|
+
client: client,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
setMessage('CSS built');
|
|
123
|
+
setStatus('CSS built successfully');
|
|
124
|
+
setTimeout(() => { setMessage(''); setStatus('ready'); }, 2000);
|
|
125
|
+
onChange(set(newFileInput));
|
|
126
|
+
} catch (err) {
|
|
127
|
+
console.error('Error building CSS file:', err);
|
|
128
|
+
setMessage('Error building CSS file: ' + err.message);
|
|
129
|
+
setStatus('Error building CSS file');
|
|
130
|
+
setError(true);
|
|
131
|
+
setTimeout(() => { setMessage(''); setStatus('ready'); setError(false); }, 3000);
|
|
132
|
+
}
|
|
133
|
+
}, [fileInput, onChange, doc_title, doc_variableFont, doc_weight, client]);
|
|
134
|
+
|
|
135
|
+
/** Converts and uploads the source font file to one or more target formats. */
|
|
136
|
+
const handleGenerateFontFile = useCallback(async (code, sourceFile) => {
|
|
137
|
+
const isMissing = Array.isArray(code);
|
|
138
|
+
const label = code === 'all' ? 'all font files' : isMissing ? 'missing files' : code + ' file';
|
|
139
|
+
setMessage(`Building ${label}...`);
|
|
140
|
+
setStatus(`Building ${label}`);
|
|
141
|
+
setError(false);
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
const url = `https://cdn.sanity.io/files/${process.env.SANITY_STUDIO_PROJECT_ID}/${process.env.SANITY_STUDIO_DATASET}/${sourceFile?.asset?._ref.replace('file-', '').replace('-', '.')}`;
|
|
145
|
+
const codes = code === 'all' ? ['otf', 'woff', 'woff2', 'eot', 'svg', 'data'] : isMissing ? code : [code];
|
|
146
|
+
|
|
147
|
+
await generateFontFile({
|
|
148
|
+
codes,
|
|
149
|
+
srcUrl: url,
|
|
150
|
+
filename: doc_slug.current,
|
|
151
|
+
documentId: doc_id,
|
|
152
|
+
documentTitle: doc_title,
|
|
153
|
+
documentVariableFont: doc_variableFont,
|
|
154
|
+
documentStyle: doc_style,
|
|
155
|
+
documentWeight: doc_weight,
|
|
156
|
+
fileInput: fileInput,
|
|
157
|
+
client: client,
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
setMessage('Files built');
|
|
161
|
+
setStatus('Files built successfully');
|
|
162
|
+
setTimeout(() => { setMessage(''); setStatus('ready'); }, 2000);
|
|
163
|
+
} catch (err) {
|
|
164
|
+
console.error('Error building font files:', err);
|
|
165
|
+
setMessage('Error building font files: ' + err.message);
|
|
166
|
+
setStatus('Error building font files');
|
|
167
|
+
setError(true);
|
|
168
|
+
setTimeout(() => { setMessage(''); setStatus('ready'); setError(false); }, 3000);
|
|
169
|
+
}
|
|
170
|
+
}, [doc_id, doc_title, doc_variableFont, doc_style, doc_weight, doc_slug, fileInput, client]);
|
|
171
|
+
|
|
172
|
+
/** Re-extracts metadata from the stored TTF and regenerates font data fields. */
|
|
173
|
+
const handleGenerateFontData = useCallback(async () => {
|
|
174
|
+
setMessage('Building font data...');
|
|
175
|
+
setStatus('Building font data');
|
|
176
|
+
setError(false);
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
if (!fileInput?.ttf?.asset?._ref) {
|
|
180
|
+
setMessage('Error: TTF file is required for font data generation');
|
|
181
|
+
setStatus('Error: TTF file is required');
|
|
182
|
+
setError(true);
|
|
183
|
+
setTimeout(() => { setMessage(''); setStatus('ready'); setError(false); }, 2000);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const [ttfAsset] = await client.fetch(
|
|
188
|
+
`*[_id == $id]{ url }`,
|
|
189
|
+
{ id: fileInput.ttf.asset._ref }
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
if (!ttfAsset?.url) throw new Error('Could not fetch TTF file URL');
|
|
193
|
+
|
|
194
|
+
const arrayBuffer = await (await fetch(ttfAsset.url)).arrayBuffer();
|
|
195
|
+
const font = await parseFont(arrayBuffer, `${doc_id}.ttf`);
|
|
196
|
+
|
|
197
|
+
const { weightName, subfamilyName, style, variableFont } = extractFontMetadata(
|
|
198
|
+
font,
|
|
199
|
+
doc_typefaceName,
|
|
200
|
+
weightKeywordList,
|
|
201
|
+
italicKeywordList,
|
|
202
|
+
);
|
|
203
|
+
const weight = determineWeight(font, weightName);
|
|
204
|
+
|
|
205
|
+
await client.patch(doc_id).set({ weightName, subfamily: subfamilyName, style, variableFont, weight }).commit();
|
|
206
|
+
|
|
207
|
+
const fontData = await generateFontData({
|
|
208
|
+
url: ttfAsset.url,
|
|
209
|
+
fontKit: font,
|
|
210
|
+
fontId: doc_id,
|
|
211
|
+
client: client,
|
|
212
|
+
commit: true,
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
if (variableFont && fontData.variableInstances) {
|
|
216
|
+
const fontObj = {
|
|
217
|
+
_id: doc_id,
|
|
218
|
+
typefaceName: doc_typefaceName,
|
|
219
|
+
variableFont,
|
|
220
|
+
variableInstances: fontData.variableInstances,
|
|
221
|
+
};
|
|
222
|
+
const instanceMappings = await parseVariableFontInstances(fontObj, client);
|
|
223
|
+
if (instanceMappings.length > 0) {
|
|
224
|
+
await client.patch(doc_id).set({ variableInstanceReferences: instanceMappings }).commit();
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
setMessage('Font data built successfully');
|
|
229
|
+
setStatus('Font data built successfully');
|
|
230
|
+
setTimeout(() => { setMessage(''); setStatus('ready'); }, 2000);
|
|
231
|
+
} catch (err) {
|
|
232
|
+
console.error('Error building font data:', err);
|
|
233
|
+
setMessage('Error building font data: ' + err.message);
|
|
234
|
+
setStatus('Error building font data');
|
|
235
|
+
setError(true);
|
|
236
|
+
setTimeout(() => { setMessage(''); setStatus('ready'); setError(false); }, 3000);
|
|
237
|
+
}
|
|
238
|
+
}, [fileInput, doc_id, doc_typefaceName, client, weightKeywordList, italicKeywordList]);
|
|
239
|
+
|
|
240
|
+
/** Builds woff2_web (DS-WEB fingerprinted) and woff2_subset from the existing woff2 via fontWorker. */
|
|
241
|
+
const handleGenerateSubsetAndWeb = useCallback(async () => {
|
|
242
|
+
try {
|
|
243
|
+
const woff2AssetRef = fileInput?.woff2?.asset?._ref;
|
|
244
|
+
if (!woff2AssetRef) throw new Error('No woff2 file available');
|
|
245
|
+
|
|
246
|
+
setMessage('Building WEB + SUBSET files...');
|
|
247
|
+
setStatus('Building WEB + SUBSET');
|
|
248
|
+
setError(false);
|
|
249
|
+
|
|
250
|
+
const [woff2Asset] = await client.fetch(
|
|
251
|
+
`*[_id == $id]{ originalFilename, url }`,
|
|
252
|
+
{ id: woff2AssetRef }
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
await generateSubset({
|
|
256
|
+
woff2Url: woff2Asset.url,
|
|
257
|
+
filename: doc_slug.current,
|
|
258
|
+
documentId: doc_id,
|
|
259
|
+
documentTitle: doc_title,
|
|
260
|
+
documentVariableFont: doc_variableFont,
|
|
261
|
+
documentStyle: doc_style,
|
|
262
|
+
documentWeight: doc_weight,
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
setMessage('WEB + SUBSET building in background');
|
|
266
|
+
setStatus('Building in background');
|
|
267
|
+
setTimeout(() => { setMessage(''); setStatus('ready'); }, 4000);
|
|
268
|
+
} catch (err) {
|
|
269
|
+
console.error('Error building WEB + SUBSET:', err);
|
|
270
|
+
setMessage('Error: ' + err.message);
|
|
271
|
+
setStatus('Error building WEB + SUBSET');
|
|
272
|
+
setError(true);
|
|
273
|
+
setTimeout(() => { setMessage(''); setStatus('ready'); setError(false); }, 3000);
|
|
274
|
+
}
|
|
275
|
+
}, [fileInput, doc_id, doc_title, doc_variableFont, doc_style, doc_weight, doc_slug, client]);
|
|
276
|
+
|
|
277
|
+
/** Uploads a file into fileInput.[fieldName] (woff2_web, woff2_subset). */
|
|
278
|
+
const handleUploadTopLevelFile = useCallback(async (event, fieldName) => {
|
|
279
|
+
try {
|
|
280
|
+
const file = event.target.files[0];
|
|
281
|
+
if (!file) return;
|
|
282
|
+
|
|
283
|
+
const ext = file.name.split('.').pop();
|
|
284
|
+
const filename = `${doc_slug.current}-${fieldName}.${ext}`;
|
|
285
|
+
|
|
286
|
+
setMessage(`Uploading ${fieldName}...`);
|
|
287
|
+
setStatus(`Uploading ${fieldName}`);
|
|
288
|
+
setError(false);
|
|
289
|
+
|
|
290
|
+
const asset = await client.assets.upload('file', file, { filename });
|
|
291
|
+
const newFileInput = {
|
|
292
|
+
...fileInput,
|
|
293
|
+
[fieldName]: { _type: 'file', asset: { _ref: asset._id, _type: 'reference' } },
|
|
294
|
+
};
|
|
295
|
+
onChange(set(newFileInput));
|
|
296
|
+
|
|
297
|
+
setMessage(`${fieldName} uploaded`);
|
|
298
|
+
setStatus(`${fieldName} uploaded successfully`);
|
|
299
|
+
setTimeout(() => { setMessage(''); setStatus('ready'); }, 2000);
|
|
300
|
+
} catch (err) {
|
|
301
|
+
console.error(`Error uploading ${fieldName}:`, err);
|
|
302
|
+
setMessage('Error: ' + err.message);
|
|
303
|
+
setStatus(`Error uploading ${fieldName}`);
|
|
304
|
+
setError(true);
|
|
305
|
+
setTimeout(() => { setMessage(''); setStatus('ready'); setError(false); }, 3000);
|
|
306
|
+
}
|
|
307
|
+
}, [fileInput, onChange, doc_slug, client]);
|
|
308
|
+
|
|
309
|
+
/** Uploads a single font file and triggers CSS/metadata generation as appropriate. */
|
|
310
|
+
const handleUpload = useCallback(async (event, code) => {
|
|
311
|
+
try {
|
|
312
|
+
const file = event.target.files[0];
|
|
313
|
+
if (!file) { setMessage('No file selected'); setStatus('No file selected'); setError(true); return; }
|
|
314
|
+
|
|
315
|
+
const ext = file.name.split('.').pop();
|
|
316
|
+
const filename = doc_slug.current + '.' + ext;
|
|
317
|
+
|
|
318
|
+
setMessage('Uploading: ' + filename);
|
|
319
|
+
setStatus('Uploading: ' + filename);
|
|
320
|
+
setError(false);
|
|
321
|
+
|
|
322
|
+
const asset = await client.assets.upload('file', file, { filename });
|
|
323
|
+
|
|
324
|
+
let newFileInput = {
|
|
325
|
+
...fileInput,
|
|
326
|
+
[code]: { _type: 'file', asset: { _ref: asset._id, _type: 'reference' } },
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
setMessage(filename + ' uploaded');
|
|
330
|
+
setStatus(filename + ' uploaded successfully');
|
|
331
|
+
|
|
332
|
+
if (code === 'woff2') {
|
|
333
|
+
setMessage('Building CSS: ' + doc_title + '.css');
|
|
334
|
+
setStatus('Building CSS file');
|
|
335
|
+
newFileInput = await generateCssFile({
|
|
336
|
+
woff2File: file,
|
|
337
|
+
fileInput: newFileInput,
|
|
338
|
+
fontName: doc_title,
|
|
339
|
+
fileName: filename.replace('.woff2', ''),
|
|
340
|
+
variableFont: doc_variableFont,
|
|
341
|
+
weight: doc_weight,
|
|
342
|
+
style: doc_style || 'Normal',
|
|
343
|
+
client: client,
|
|
344
|
+
});
|
|
345
|
+
setMessage(doc_title + '.css built');
|
|
346
|
+
setStatus('CSS file built successfully');
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (code === 'ttf') {
|
|
350
|
+
const fontBuffer = await readFontFile(file);
|
|
351
|
+
const font = await parseFont(fontBuffer, file.name);
|
|
352
|
+
const { weightName, subfamilyName, style, variableFont } = extractFontMetadata(
|
|
353
|
+
font, doc_typefaceName, weightKeywordList, italicKeywordList
|
|
354
|
+
);
|
|
355
|
+
const weight = determineWeight(font, weightName);
|
|
356
|
+
const normalizedId = doc_id.startsWith('drafts.') ? doc_id.replace('drafts.', '') : doc_id;
|
|
357
|
+
await client.patch(normalizedId).set({ weightName, subfamily: subfamilyName, style, variableFont, weight }).commit();
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
onChange(set(newFileInput));
|
|
361
|
+
setTimeout(() => { setMessage(''); setStatus('ready'); }, 2000);
|
|
362
|
+
} catch (err) {
|
|
363
|
+
console.error('Error uploading file:', err);
|
|
364
|
+
setMessage('Error uploading file: ' + err.message);
|
|
365
|
+
setStatus('Error uploading file');
|
|
366
|
+
setError(true);
|
|
367
|
+
setTimeout(() => { setMessage(''); setStatus('ready'); setError(false); }, 3000);
|
|
368
|
+
}
|
|
369
|
+
}, [fileInput, onChange, doc_title, doc_typefaceName, doc_variableFont, doc_weight, doc_slug, doc_id, client, weightKeywordList, italicKeywordList]);
|
|
370
|
+
|
|
371
|
+
/** Deletes a single fileInput font file asset. */
|
|
372
|
+
const handleDelete = useCallback(async (code) => {
|
|
373
|
+
try {
|
|
374
|
+
setMessage(`Deleting ${code} file...`);
|
|
375
|
+
setStatus(`Deleting ${code} file`);
|
|
376
|
+
setError(false);
|
|
377
|
+
|
|
378
|
+
const asset = fileInput[code]?.asset?._ref;
|
|
379
|
+
if (!asset) { setMessage(`No ${code} file to delete`); setStatus(`No ${code} file to delete`); setError(true); return; }
|
|
380
|
+
|
|
381
|
+
onChange(unset([code]));
|
|
382
|
+
await client.delete(asset);
|
|
383
|
+
|
|
384
|
+
setMessage(`${code} file deleted`);
|
|
385
|
+
setStatus(`${code} file deleted successfully`);
|
|
386
|
+
setTimeout(() => { setMessage(''); setStatus('ready'); }, 2000);
|
|
387
|
+
} catch (err) {
|
|
388
|
+
console.error('Error deleting asset:', err);
|
|
389
|
+
setMessage('WARNING: ' + err.message);
|
|
390
|
+
setStatus('Error deleting asset');
|
|
391
|
+
setError(true);
|
|
392
|
+
setTimeout(() => { setMessage(''); setStatus('ready'); setError(false); }, 3000);
|
|
393
|
+
}
|
|
394
|
+
}, [fileInput, onChange, client]);
|
|
395
|
+
|
|
396
|
+
/** Deletes a fileInput sub-field asset (woff2_web, woff2_subset). */
|
|
397
|
+
const handleDeleteTopLevel = useCallback(async (fieldName) => {
|
|
398
|
+
try {
|
|
399
|
+
setMessage(`Deleting ${fieldName}...`);
|
|
400
|
+
setStatus(`Deleting ${fieldName}`);
|
|
401
|
+
setError(false);
|
|
402
|
+
|
|
403
|
+
const asset = fileInput?.[fieldName]?.asset?._ref;
|
|
404
|
+
if (!asset) { setMessage(`No ${fieldName} file to delete`); setStatus(`No ${fieldName} file to delete`); setError(true); return; }
|
|
405
|
+
|
|
406
|
+
onChange(unset([fieldName]));
|
|
407
|
+
await client.delete(asset);
|
|
408
|
+
|
|
409
|
+
setMessage(`${fieldName} deleted`);
|
|
410
|
+
setStatus(`${fieldName} deleted successfully`);
|
|
411
|
+
setTimeout(() => { setMessage(''); setStatus('ready'); }, 2000);
|
|
412
|
+
} catch (err) {
|
|
413
|
+
console.error(`Error deleting ${fieldName}:`, err);
|
|
414
|
+
setMessage('Error: ' + err.message);
|
|
415
|
+
setStatus(`Error deleting ${fieldName}`);
|
|
416
|
+
setError(true);
|
|
417
|
+
setTimeout(() => { setMessage(''); setStatus('ready'); setError(false); }, 3000);
|
|
418
|
+
}
|
|
419
|
+
}, [fileInput, onChange, client]);
|
|
420
|
+
|
|
421
|
+
/** Deletes all font file assets and resets all metadata fields. */
|
|
422
|
+
const handleDeleteAll = useCallback(async () => {
|
|
423
|
+
try {
|
|
424
|
+
setMessage('Deleting all files and metadata...');
|
|
425
|
+
setStatus('Deleting all files and metadata');
|
|
426
|
+
setError(false);
|
|
427
|
+
|
|
428
|
+
onChange(unset([]));
|
|
429
|
+
|
|
430
|
+
await client.patch(doc_id).set({
|
|
431
|
+
characterSet: { chars: [] },
|
|
432
|
+
glyphCount: 0,
|
|
433
|
+
metaData: undefined,
|
|
434
|
+
metrics: undefined,
|
|
435
|
+
normalWeight: undefined,
|
|
436
|
+
price: 0,
|
|
437
|
+
sell: false,
|
|
438
|
+
style: 'Normal',
|
|
439
|
+
variableAxes: undefined,
|
|
440
|
+
variableFont: false,
|
|
441
|
+
weight: 400,
|
|
442
|
+
variableInstances: undefined,
|
|
443
|
+
}).commit();
|
|
444
|
+
|
|
445
|
+
const allAssets = Object.keys(fileInput)
|
|
446
|
+
.filter(k => k !== 'documentInfo')
|
|
447
|
+
.map(k => fileInput[k]?.asset?._ref)
|
|
448
|
+
.filter(Boolean);
|
|
449
|
+
|
|
450
|
+
for (const assetRef of allAssets) {
|
|
451
|
+
try { await client.delete(assetRef); } catch (e) { console.error('Error deleting asset:', e.message); }
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
setMessage('All files and metadata deleted');
|
|
455
|
+
setStatus('All files and metadata deleted successfully');
|
|
456
|
+
setTimeout(() => { setMessage(''); setStatus('ready'); }, 2000);
|
|
457
|
+
} catch (err) {
|
|
458
|
+
console.error('Error deleting all files:', err);
|
|
459
|
+
setMessage('Delete error: ' + err.message);
|
|
460
|
+
setStatus('Error deleting all files');
|
|
461
|
+
setError(true);
|
|
462
|
+
setTimeout(() => { setMessage(''); setStatus('ready'); setError(false); }, 3000);
|
|
463
|
+
}
|
|
464
|
+
}, [fileInput, doc_id, onChange, client]);
|
|
465
|
+
|
|
466
|
+
/** Renders a bordered upload/build/delete row for a fileInput format. */
|
|
467
|
+
const renderFontSection = (format, buildSource = null) => {
|
|
468
|
+
const formatUpper = format.toUpperCase();
|
|
469
|
+
const hasFile = !!fileInput?.[format]?.asset?._ref;
|
|
470
|
+
const fileUrl = hasFile
|
|
471
|
+
? `https://cdn.sanity.io/files/${process.env.SANITY_STUDIO_PROJECT_ID}/${process.env.SANITY_STUDIO_DATASET}/${fileInput[format].asset._ref.replace('file-', '').replace('-', '.')}`
|
|
472
|
+
: null;
|
|
473
|
+
|
|
474
|
+
return (
|
|
475
|
+
<Card border radius={1} paddingX={2} paddingY={3}>
|
|
476
|
+
<Flex justify="space-between" align="center" gap={2}>
|
|
477
|
+
<Flex gap={3} align="center" style={{ flex: 1, minWidth: 0 }}>
|
|
478
|
+
<Text size={0} style={{ fontFamily: 'monospace', minWidth: '2.5rem', flexShrink: 0, opacity: hasFile ? 1 : 0.5 }}>
|
|
479
|
+
{formatUpper}
|
|
480
|
+
</Text>
|
|
481
|
+
{hasFile ? (
|
|
482
|
+
<Box style={{ flex: 1, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
|
483
|
+
<a href={fileUrl} target="_blank" rel="noreferrer">{filenames?.[format] || 'File'}</a>
|
|
484
|
+
</Box>
|
|
485
|
+
) : (
|
|
486
|
+
<Text size={1} muted>—</Text>
|
|
487
|
+
)}
|
|
488
|
+
</Flex>
|
|
489
|
+
{status === 'ready' && (
|
|
490
|
+
<Flex gap={1} align="center" style={{ flexShrink: 0 }}>
|
|
491
|
+
{buildSource && fileInput?.[buildSource] && (
|
|
492
|
+
<Button mode="ghost" tone="primary" fontSize={1} padding={2} onClick={() => handleGenerateFontFile(format, fileInput[buildSource])} text="Build" />
|
|
493
|
+
)}
|
|
494
|
+
<Button as="label" mode="ghost" tone="primary" fontSize={1} padding={2} style={{ cursor: 'pointer' }}>
|
|
495
|
+
<Text size={1}>Upload</Text>
|
|
496
|
+
<input ref={ref} type="file" hidden onChange={(e) => handleUpload(e, format)} />
|
|
497
|
+
</Button>
|
|
498
|
+
{hasFile && (
|
|
499
|
+
<Button mode="bleed" tone="critical" icon={TrashIcon} padding={2} onClick={() => handleDelete(format)} />
|
|
500
|
+
)}
|
|
501
|
+
</Flex>
|
|
502
|
+
)}
|
|
503
|
+
</Flex>
|
|
504
|
+
</Card>
|
|
505
|
+
);
|
|
506
|
+
};
|
|
507
|
+
|
|
508
|
+
/** Renders an upload/build/delete row for a top-level document asset field (woff2_web, woff2_subset). */
|
|
509
|
+
const renderTopLevelAssetSection = (label, fieldName, assetRef, filename, onBuild) => {
|
|
510
|
+
const hasFile = !!assetRef;
|
|
511
|
+
const fileUrl = hasFile
|
|
512
|
+
? `https://cdn.sanity.io/files/${process.env.SANITY_STUDIO_PROJECT_ID}/${process.env.SANITY_STUDIO_DATASET}/${assetRef.replace('file-', '').replace('-', '.')}`
|
|
513
|
+
: null;
|
|
514
|
+
|
|
515
|
+
return (
|
|
516
|
+
<Card border radius={1} paddingX={2} paddingY={3}>
|
|
517
|
+
<Flex justify="space-between" align="center" gap={2}>
|
|
518
|
+
<Flex gap={3} align="center" style={{ flex: 1, minWidth: 0 }}>
|
|
519
|
+
<Text size={0} style={{ fontFamily: 'monospace', minWidth: '2.5rem', flexShrink: 0, opacity: hasFile ? 1 : 0.5 }}>
|
|
520
|
+
{label}
|
|
521
|
+
</Text>
|
|
522
|
+
{hasFile ? (
|
|
523
|
+
<Box style={{ flex: 1, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
|
524
|
+
<a href={fileUrl} target="_blank" rel="noreferrer">{filename || 'File'}</a>
|
|
525
|
+
</Box>
|
|
526
|
+
) : (
|
|
527
|
+
<Text size={1} muted>—</Text>
|
|
528
|
+
)}
|
|
529
|
+
</Flex>
|
|
530
|
+
{status === 'ready' && (
|
|
531
|
+
<Flex gap={1} align="center" style={{ flexShrink: 0 }}>
|
|
532
|
+
{onBuild && fileInput?.woff2 && (
|
|
533
|
+
<Button mode="ghost" tone="primary" fontSize={1} padding={2} onClick={onBuild} text="Build" />
|
|
534
|
+
)}
|
|
535
|
+
<Button as="label" mode="ghost" tone="primary" fontSize={1} padding={2} style={{ cursor: 'pointer' }}>
|
|
536
|
+
<Text size={1}>Upload</Text>
|
|
537
|
+
<input type="file" hidden onChange={(e) => handleUploadTopLevelFile(e, fieldName)} />
|
|
538
|
+
</Button>
|
|
539
|
+
{hasFile && (
|
|
540
|
+
<Button mode="bleed" tone="critical" icon={TrashIcon} padding={2} onClick={() => handleDeleteTopLevel(fieldName)} />
|
|
541
|
+
)}
|
|
542
|
+
</Flex>
|
|
543
|
+
)}
|
|
544
|
+
</Flex>
|
|
545
|
+
</Card>
|
|
546
|
+
);
|
|
547
|
+
};
|
|
548
|
+
|
|
549
|
+
/** Renders the CSS row — build-only, no direct upload. */
|
|
550
|
+
const renderCssSection = () => {
|
|
551
|
+
const hasFile = !!fileInput?.css?.asset?._ref;
|
|
552
|
+
const fileUrl = hasFile
|
|
553
|
+
? `https://cdn.sanity.io/files/${process.env.SANITY_STUDIO_PROJECT_ID}/${process.env.SANITY_STUDIO_DATASET}/${fileInput.css.asset._ref.replace('file-', '').replace('-', '.')}`
|
|
554
|
+
: null;
|
|
555
|
+
|
|
556
|
+
return (
|
|
557
|
+
<Card border radius={1} paddingX={2} paddingY={3}>
|
|
558
|
+
<Flex justify="space-between" align="center" gap={2}>
|
|
559
|
+
<Flex gap={3} align="center" style={{ flex: 1, minWidth: 0 }}>
|
|
560
|
+
<Text size={0} style={{ fontFamily: 'monospace', minWidth: '2.5rem', flexShrink: 0, opacity: hasFile ? 1 : 0.5 }}>
|
|
561
|
+
CSS
|
|
562
|
+
</Text>
|
|
563
|
+
{hasFile ? (
|
|
564
|
+
<Box style={{ flex: 1, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
|
565
|
+
<a href={fileUrl} target="_blank" rel="noreferrer">{filenames?.css || 'File'}</a>
|
|
566
|
+
</Box>
|
|
567
|
+
) : (
|
|
568
|
+
<Text size={1} muted>—</Text>
|
|
569
|
+
)}
|
|
570
|
+
</Flex>
|
|
571
|
+
{status === 'ready' && (
|
|
572
|
+
<Flex gap={1} align="center" style={{ flexShrink: 0 }}>
|
|
573
|
+
{fileInput?.woff2 && (
|
|
574
|
+
<Button mode="ghost" tone="primary" fontSize={1} padding={2} onClick={() => handleGenerateCssFile()} text="Build" />
|
|
575
|
+
)}
|
|
576
|
+
{hasFile && (
|
|
577
|
+
<Button mode="bleed" tone="critical" icon={TrashIcon} padding={2} onClick={() => handleDelete('css')} />
|
|
578
|
+
)}
|
|
579
|
+
</Flex>
|
|
580
|
+
)}
|
|
581
|
+
</Flex>
|
|
582
|
+
</Card>
|
|
583
|
+
);
|
|
584
|
+
};
|
|
585
|
+
|
|
586
|
+
/** Renders the Data row — shows metadata version and build button. */
|
|
587
|
+
const renderDataSection = () => (
|
|
588
|
+
<Card border radius={1} paddingX={2} paddingY={3}>
|
|
589
|
+
<Flex justify="space-between" align="center" gap={2}>
|
|
590
|
+
<Flex gap={3} align="center" style={{ flex: 1, minWidth: 0 }}>
|
|
591
|
+
<Text size={0} style={{ fontFamily: 'monospace', minWidth: '2.5rem', flexShrink: 0, opacity: doc_metaData?.version ? 1 : 0.5 }}>
|
|
592
|
+
DATA
|
|
593
|
+
</Text>
|
|
594
|
+
{doc_metaData?.version ? (
|
|
595
|
+
<Text size={1}>v{doc_metaData.version} <Text as="span" size={1} muted>({doc_metaData.genDate})</Text></Text>
|
|
596
|
+
) : (
|
|
597
|
+
<Text size={1} muted>—</Text>
|
|
598
|
+
)}
|
|
599
|
+
</Flex>
|
|
600
|
+
{status === 'ready' && fileInput?.ttf && (
|
|
601
|
+
<Flex gap={1} align="center" style={{ flexShrink: 0 }}>
|
|
602
|
+
<Button mode="ghost" tone="primary" fontSize={1} padding={2} onClick={() => handleGenerateFontData()} text="Build" />
|
|
603
|
+
</Flex>
|
|
604
|
+
)}
|
|
605
|
+
</Flex>
|
|
606
|
+
</Card>
|
|
607
|
+
);
|
|
608
|
+
|
|
609
|
+
return (
|
|
610
|
+
<Stack space={2}>
|
|
611
|
+
<StatusDisplay
|
|
612
|
+
status={status}
|
|
613
|
+
error={error}
|
|
614
|
+
action={
|
|
615
|
+
<Button
|
|
616
|
+
mode="bleed"
|
|
617
|
+
icon={ControlsIcon}
|
|
618
|
+
padding={2}
|
|
619
|
+
tone={showAdvanced ? 'primary' : 'default'}
|
|
620
|
+
title="Show advanced file formats"
|
|
621
|
+
onClick={() => setShowAdvanced(v => !v)}
|
|
622
|
+
/>
|
|
623
|
+
}
|
|
624
|
+
/>
|
|
625
|
+
|
|
626
|
+
{renderFontSection('ttf')}
|
|
627
|
+
|
|
628
|
+
{status === 'ready' && fileInput?.ttf && (
|
|
629
|
+
<Grid columns={[1, 2]} gap={2}>
|
|
630
|
+
<Button
|
|
631
|
+
mode="ghost"
|
|
632
|
+
tone="primary"
|
|
633
|
+
onClick={() => handleGenerateFontFile('all', fileInput.ttf)}
|
|
634
|
+
text="Rebuild All from TTF"
|
|
635
|
+
style={{ width: '100%' }}
|
|
636
|
+
/>
|
|
637
|
+
<Button
|
|
638
|
+
mode="ghost"
|
|
639
|
+
tone="primary"
|
|
640
|
+
onClick={() => {
|
|
641
|
+
const missing = [
|
|
642
|
+
!fileInput?.otf?.asset?._ref && 'otf',
|
|
643
|
+
!fileInput?.woff?.asset?._ref && 'woff',
|
|
644
|
+
!fileInput?.woff2?.asset?._ref && 'woff2',
|
|
645
|
+
!fileInput?.eot?.asset?._ref && 'eot',
|
|
646
|
+
!fileInput?.svg?.asset?._ref && 'svg',
|
|
647
|
+
!doc_metaData?.version && 'data',
|
|
648
|
+
].filter(Boolean);
|
|
649
|
+
handleGenerateFontFile(missing, fileInput.ttf);
|
|
650
|
+
}}
|
|
651
|
+
text="Build Missing"
|
|
652
|
+
style={{ width: '100%' }}
|
|
653
|
+
/>
|
|
654
|
+
</Grid>
|
|
655
|
+
)}
|
|
656
|
+
|
|
657
|
+
{renderFontSection('otf', 'woff')}
|
|
658
|
+
{renderFontSection('woff', 'ttf')}
|
|
659
|
+
{renderFontSection('woff2', 'ttf')}
|
|
660
|
+
{showAdvanced && renderTopLevelAssetSection('WEB', 'woff2_web', fileInput?.woff2_web?.asset?._ref, filenames?.woff2_web, handleGenerateSubsetAndWeb)}
|
|
661
|
+
{showAdvanced && renderTopLevelAssetSection('SUBSET', 'woff2_subset', fileInput?.woff2_subset?.asset?._ref, filenames?.woff2_subset, handleGenerateSubsetAndWeb)}
|
|
662
|
+
{showAdvanced && renderFontSection('eot', 'ttf')}
|
|
663
|
+
{showAdvanced && renderFontSection('svg', 'ttf')}
|
|
664
|
+
{renderCssSection()}
|
|
665
|
+
{showAdvanced && renderDataSection()}
|
|
666
|
+
|
|
667
|
+
{status === 'ready' && (fileInput?.ttf || fileInput?.otf || fileInput?.woff || fileInput?.woff2) && (
|
|
668
|
+
<Button mode="ghost" tone="critical" onClick={() => handleDeleteAll()} text="Delete All" style={{ width: '100%' }} />
|
|
669
|
+
)}
|
|
670
|
+
</Stack>
|
|
671
|
+
);
|
|
672
|
+
};
|