@liiift-studio/sanity-font-manager 2.5.3 → 2.5.4

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.
@@ -1,4250 +0,0 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }// src/components/UploadModal.jsx
2
- var _react = require('react'); var _react2 = _interopRequireDefault(_react);
3
- var _ui = require('@sanity/ui');
4
-
5
- // src/utils/planTypes.js
6
- var FONT_STATUS = {
7
- PENDING: "pending",
8
- PROCESSING: "processing",
9
- PROCESSED: "processed",
10
- ERROR: "error"
11
- };
12
- var PLAN_PHASE = {
13
- IDLE: "idle",
14
- PROCESSING: "processing",
15
- REVIEWING: "reviewing",
16
- READY: "ready",
17
- EXECUTING: "executing",
18
- COMPLETE: "complete",
19
- ERROR: "error"
20
- };
21
- var RECOMMENDATION = {
22
- USE_EXACT: "use-exact",
23
- USE_CANDIDATE: "use-candidate",
24
- AMBIGUOUS: "ambiguous",
25
- CREATE: "create"
26
- };
27
- var EXECUTION_STATUS = {
28
- QUEUED: "queued",
29
- UPLOADING_ASSETS: "uploading-assets",
30
- GENERATING_CSS: "generating-css",
31
- GENERATING_METADATA: "generating-metadata",
32
- CREATING_DOCUMENT: "creating-document",
33
- PATCHING_TYPEFACE: "patching-typeface",
34
- COMPLETE: "complete",
35
- ERROR: "error",
36
- SKIPPED: "skipped"
37
- };
38
- var PROCESSING_OWNED_FIELDS = ["parsedMetadata", "glyphCount", "opentypeFeatures", "variationAxes", "status"];
39
- var PLAN_VERSION = 1;
40
- var CONCURRENCY_LIMIT = 3;
41
- var MAX_RETRIES = 3;
42
- var BASE_BACKOFF_MS = 1e3;
43
- var JITTER_FACTOR = 0.25;
44
- function backoffWithJitter(attempt) {
45
- const base = BASE_BACKOFF_MS * Math.pow(2, attempt);
46
- const jitter = base * JITTER_FACTOR * (Math.random() * 2 - 1);
47
- return Math.round(base + jitter);
48
- }
49
- function createFontDecisions({
50
- titleSource = "fontkit-fullName",
51
- title = "",
52
- titleOriginal = "",
53
- documentId = "",
54
- weight = 400,
55
- weightSource = "default-400",
56
- matchedKeyword = null,
57
- weightName = "",
58
- weightNameSource = "nameId17-preferredSubfamily",
59
- style = "Regular",
60
- styleSource = "default-regular",
61
- styleReason = "",
62
- subfamily = "",
63
- subfamilySource = "default-empty",
64
- titleAlternatives = []
65
- }) {
66
- return {
67
- title: {
68
- source: titleSource,
69
- original: titleOriginal,
70
- processed: title,
71
- alternatives: titleAlternatives,
72
- userOverride: null
73
- },
74
- documentId: {
75
- source: "derived-from-title",
76
- generated: documentId,
77
- userOverride: null
78
- },
79
- weight: {
80
- source: weightSource,
81
- detected: weight,
82
- matchedKeyword,
83
- rawWeightName: weightName,
84
- userOverride: null
85
- },
86
- weightName: {
87
- source: weightNameSource,
88
- detected: weightName,
89
- userOverride: null
90
- },
91
- style: {
92
- source: styleSource,
93
- detected: style,
94
- reason: styleReason,
95
- userOverride: null
96
- },
97
- subfamily: {
98
- source: subfamilySource,
99
- detected: subfamily,
100
- userOverride: null
101
- },
102
- existingDocument: {
103
- recommendation: RECOMMENDATION.CREATE,
104
- exact: null,
105
- candidates: [],
106
- userChoice: null,
107
- selectedCandidate: null,
108
- lookupFailed: false
109
- }
110
- };
111
- }
112
- function createEmptyPlan(settings = {}) {
113
- return {
114
- version: PLAN_VERSION,
115
- settings: {
116
- price: 0,
117
- preserveShortenedNames: false,
118
- preserveFileNames: false,
119
- ...settings
120
- },
121
- fonts: {},
122
- subfamilyGroups: {},
123
- phase: PLAN_PHASE.IDLE,
124
- processingProgress: {
125
- total: 0,
126
- completed: 0,
127
- failed: 0,
128
- currentFile: null
129
- }
130
- };
131
- }
132
-
133
- // src/utils/sanitizeForSanityId.js
134
- var _slugify = require('slugify'); var _slugify2 = _interopRequireDefault(_slugify);
135
- function sanitizeForSanityId(str) {
136
- if (!str || typeof str !== "string") {
137
- return "font-" + Date.now();
138
- }
139
- let sanitized = str.toLowerCase().trim();
140
- sanitized = sanitized.replace(/\+/g, "plus");
141
- sanitized = sanitized.replace(/&/g, "and");
142
- sanitized = sanitized.replace(/@/g, "at");
143
- sanitized = _slugify2.default.call(void 0, sanitized, {
144
- replacement: "-",
145
- remove: /[^\w\s-]/g,
146
- lower: true,
147
- strict: true,
148
- locale: "en",
149
- trim: true
150
- });
151
- sanitized = sanitized.replace(/[^a-z0-9\-_]/g, "-");
152
- sanitized = sanitized.replace(/-+/g, "-");
153
- sanitized = sanitized.replace(/^[-_]+|[-_]+$/g, "");
154
- if (sanitized && !/^[a-z_]/.test(sanitized)) {
155
- sanitized = "font_" + sanitized;
156
- }
157
- if (!sanitized) {
158
- sanitized = "font_" + Date.now();
159
- }
160
- if (sanitized.length > 128) {
161
- const hash = Math.random().toString(36).substring(2, 8);
162
- sanitized = sanitized.substring(0, 120) + "_" + hash;
163
- }
164
- if (!/^[a-z_][a-z0-9\-_]*$/.test(sanitized)) {
165
- console.warn(`ID sanitization produced invalid result: "${sanitized}", using fallback`);
166
- sanitized = "font_" + Date.now();
167
- }
168
- return sanitized;
169
- }
170
-
171
- // src/utils/planReducer.js
172
- var VALID_TRANSITIONS = {
173
- [PLAN_PHASE.IDLE]: [PLAN_PHASE.PROCESSING],
174
- [PLAN_PHASE.PROCESSING]: [PLAN_PHASE.REVIEWING],
175
- [PLAN_PHASE.REVIEWING]: [PLAN_PHASE.READY, PLAN_PHASE.EXECUTING],
176
- [PLAN_PHASE.READY]: [PLAN_PHASE.EXECUTING],
177
- [PLAN_PHASE.EXECUTING]: [PLAN_PHASE.COMPLETE, PLAN_PHASE.ERROR],
178
- [PLAN_PHASE.COMPLETE]: [PLAN_PHASE.EXECUTING],
179
- [PLAN_PHASE.ERROR]: [PLAN_PHASE.EXECUTING]
180
- };
181
- function planReducer(state, action) {
182
- switch (action.type) {
183
- // ---------------------------------------------------------------
184
- // Phase / Settings
185
- // ---------------------------------------------------------------
186
- case "SET_PHASE": {
187
- if (action.phase === PLAN_PHASE.IDLE) {
188
- return { ...state, phase: PLAN_PHASE.IDLE };
189
- }
190
- const validNext = VALID_TRANSITIONS[state.phase] || [];
191
- if (!validNext.includes(action.phase)) {
192
- console.warn(`Invalid phase transition: ${state.phase} \u2192 ${action.phase}`);
193
- return state;
194
- }
195
- const nextState = { ...state, phase: action.phase };
196
- if (typeof action.totalFiles === "number") {
197
- nextState.processingProgress = {
198
- ...state.processingProgress,
199
- total: action.totalFiles,
200
- completed: 0,
201
- failed: 0,
202
- currentFile: null
203
- };
204
- }
205
- return nextState;
206
- }
207
- case "SET_SETTINGS": {
208
- if (state.phase !== PLAN_PHASE.IDLE && state.phase !== PLAN_PHASE.REVIEWING && state.phase !== PLAN_PHASE.READY) {
209
- console.warn("SET_SETTINGS blocked \u2014 settings locked during processing/execution");
210
- return state;
211
- }
212
- return { ...state, settings: { ...state.settings, ...action.settings } };
213
- }
214
- // ---------------------------------------------------------------
215
- // Processing (Phase 1) — dispatched by buildUploadPlan callbacks
216
- // ---------------------------------------------------------------
217
- case "UPDATE_PROCESSING_PROGRESS": {
218
- return {
219
- ...state,
220
- processingProgress: { ...state.processingProgress, ...action.progress }
221
- };
222
- }
223
- case "ADD_PROCESSED_FONT": {
224
- const { tempId, fontEntry } = action;
225
- const fonts = { ...state.fonts };
226
- if (fonts[tempId]) {
227
- const existing = fonts[tempId];
228
- const merged = { ...existing };
229
- for (const field of PROCESSING_OWNED_FIELDS) {
230
- merged[field] = fontEntry[field];
231
- }
232
- fonts[tempId] = merged;
233
- } else {
234
- fonts[tempId] = fontEntry;
235
- }
236
- const subfamilyGroups = { ...state.subfamilyGroups };
237
- const sfName = fontEntry.subfamily || "default";
238
- if (!fontEntry.variableFont || fontEntry.subfamily) {
239
- if (!subfamilyGroups[sfName]) {
240
- subfamilyGroups[sfName] = { title: sfName, fontIds: [] };
241
- }
242
- if (!subfamilyGroups[sfName].fontIds.includes(tempId)) {
243
- subfamilyGroups[sfName] = {
244
- ...subfamilyGroups[sfName],
245
- fontIds: [...subfamilyGroups[sfName].fontIds, tempId]
246
- };
247
- }
248
- }
249
- return {
250
- ...state,
251
- fonts,
252
- subfamilyGroups,
253
- processingProgress: {
254
- ...state.processingProgress,
255
- completed: state.processingProgress.completed + 1
256
- }
257
- };
258
- }
259
- case "SET_PROCESSING_ERROR": {
260
- const { tempId, error } = action;
261
- if (!state.fonts[tempId]) return state;
262
- return {
263
- ...state,
264
- fonts: {
265
- ...state.fonts,
266
- [tempId]: { ...state.fonts[tempId], status: FONT_STATUS.ERROR, error }
267
- },
268
- processingProgress: {
269
- ...state.processingProgress,
270
- failed: state.processingProgress.failed + 1
271
- }
272
- };
273
- }
274
- // ---------------------------------------------------------------
275
- // User Edits (Review Step)
276
- // ---------------------------------------------------------------
277
- case "SET_FONT_TITLE": {
278
- const { tempId, title, source: titleSource } = action;
279
- const font = state.fonts[tempId];
280
- if (!font) return state;
281
- const newSource = titleSource || "user-override";
282
- const updated = {
283
- ...font,
284
- title,
285
- decisions: {
286
- ...font.decisions,
287
- title: { ...font.decisions.title, userOverride: title, source: newSource }
288
- }
289
- };
290
- if (!font.decisions.documentId.userOverride) {
291
- const newDocId = sanitizeForSanityId(title);
292
- updated.documentId = newDocId;
293
- updated.decisions = {
294
- ...updated.decisions,
295
- documentId: { ...updated.decisions.documentId, generated: newDocId }
296
- };
297
- }
298
- return updateFontAndCheckConflicts(state, tempId, updated);
299
- }
300
- case "SET_FONT_DOCUMENT_ID": {
301
- const { tempId, documentId } = action;
302
- const font = state.fonts[tempId];
303
- if (!font) return state;
304
- const sanitized = sanitizeForSanityId(documentId);
305
- const updated = {
306
- ...font,
307
- documentId: sanitized,
308
- decisions: {
309
- ...font.decisions,
310
- documentId: { ...font.decisions.documentId, userOverride: documentId }
311
- }
312
- };
313
- return updateFontAndCheckConflicts(state, tempId, updated);
314
- }
315
- case "SET_FONT_WEIGHT": {
316
- const { tempId, weight } = action;
317
- const font = state.fonts[tempId];
318
- if (!font) return state;
319
- const clamped = Math.max(1, Math.min(1e3, weight));
320
- return {
321
- ...state,
322
- fonts: {
323
- ...state.fonts,
324
- [tempId]: {
325
- ...font,
326
- weight: clamped,
327
- decisions: {
328
- ...font.decisions,
329
- weight: { ...font.decisions.weight, userOverride: clamped }
330
- }
331
- }
332
- }
333
- };
334
- }
335
- case "SET_FONT_WEIGHT_NAME": {
336
- const { tempId, weightName } = action;
337
- const font = state.fonts[tempId];
338
- if (!font) return state;
339
- return {
340
- ...state,
341
- fonts: {
342
- ...state.fonts,
343
- [tempId]: {
344
- ...font,
345
- weightName,
346
- decisions: {
347
- ...font.decisions,
348
- weightName: { ...font.decisions.weightName, userOverride: weightName }
349
- }
350
- }
351
- }
352
- };
353
- }
354
- case "SET_FONT_STYLE": {
355
- const { tempId, style } = action;
356
- const font = state.fonts[tempId];
357
- if (!font) return state;
358
- return {
359
- ...state,
360
- fonts: {
361
- ...state.fonts,
362
- [tempId]: {
363
- ...font,
364
- style,
365
- decisions: {
366
- ...font.decisions,
367
- style: { ...font.decisions.style, userOverride: style }
368
- }
369
- }
370
- }
371
- };
372
- }
373
- case "SET_FONT_SUBFAMILY": {
374
- const { tempId, subfamily } = action;
375
- const font = state.fonts[tempId];
376
- if (!font) return state;
377
- const oldSubfamily = font.subfamily || "default";
378
- const newSubfamily = subfamily || "default";
379
- const updated = {
380
- ...font,
381
- subfamily,
382
- decisions: {
383
- ...font.decisions,
384
- subfamily: { ...font.decisions.subfamily, userOverride: subfamily }
385
- }
386
- };
387
- let newState = { ...state, fonts: { ...state.fonts, [tempId]: updated } };
388
- if (oldSubfamily !== newSubfamily) {
389
- newState = moveFontBetweenGroups(newState, tempId, oldSubfamily, newSubfamily);
390
- }
391
- return newState;
392
- }
393
- case "SET_FONT_ACTION": {
394
- const { tempId, decision } = action;
395
- const font = state.fonts[tempId];
396
- if (!font) return state;
397
- const existingDoc = { ...font.decisions.existingDocument, userChoice: decision };
398
- if (decision === "create") {
399
- existingDoc.selectedCandidate = null;
400
- }
401
- return {
402
- ...state,
403
- fonts: {
404
- ...state.fonts,
405
- [tempId]: {
406
- ...font,
407
- decisions: { ...font.decisions, existingDocument: existingDoc }
408
- }
409
- }
410
- };
411
- }
412
- case "SET_FONT_CANDIDATE": {
413
- const { tempId, candidate } = action;
414
- const font = state.fonts[tempId];
415
- if (!font) return state;
416
- return {
417
- ...state,
418
- fonts: {
419
- ...state.fonts,
420
- [tempId]: {
421
- ...font,
422
- documentId: candidate._id,
423
- decisions: {
424
- ...font.decisions,
425
- existingDocument: {
426
- ...font.decisions.existingDocument,
427
- selectedCandidate: candidate,
428
- userChoice: "update"
429
- }
430
- }
431
- }
432
- }
433
- };
434
- }
435
- // ---------------------------------------------------------------
436
- // Subfamily Organization
437
- // ---------------------------------------------------------------
438
- case "MOVE_FONT_TO_SUBFAMILY": {
439
- const { tempId, fromSubfamily, toSubfamily } = action;
440
- const font = state.fonts[tempId];
441
- if (!font) return state;
442
- let newState = {
443
- ...state,
444
- fonts: {
445
- ...state.fonts,
446
- [tempId]: { ...font, subfamily: toSubfamily }
447
- }
448
- };
449
- return moveFontBetweenGroups(newState, tempId, fromSubfamily, toSubfamily);
450
- }
451
- case "CREATE_SUBFAMILY_GROUP": {
452
- const { title } = action;
453
- if (state.subfamilyGroups[title]) return state;
454
- return {
455
- ...state,
456
- subfamilyGroups: {
457
- ...state.subfamilyGroups,
458
- [title]: { title, fontIds: [] }
459
- }
460
- };
461
- }
462
- case "REMOVE_SUBFAMILY_GROUP": {
463
- const { title } = action;
464
- const group = state.subfamilyGroups[title];
465
- if (!group) return state;
466
- if (group.fontIds.length > 0) {
467
- console.warn("Cannot remove subfamily group with fonts \u2014 reassign fonts first");
468
- return state;
469
- }
470
- const { [title]: _, ...remaining } = state.subfamilyGroups;
471
- return { ...state, subfamilyGroups: remaining };
472
- }
473
- // ---------------------------------------------------------------
474
- // Bulk Actions
475
- // ---------------------------------------------------------------
476
- case "ACCEPT_ALL_SUGGESTIONS": {
477
- const scope = action.scope || Object.keys(state.fonts);
478
- const fonts = { ...state.fonts };
479
- for (const tempId of scope) {
480
- if (!fonts[tempId]) continue;
481
- fonts[tempId] = resetFontToSuggestions(fonts[tempId]);
482
- }
483
- const subfamilyGroups = rebuildSubfamilyGroups(fonts);
484
- return { ...state, fonts, subfamilyGroups };
485
- }
486
- case "RESET_FONT_TO_SUGGESTIONS": {
487
- const { tempId } = action;
488
- const font = state.fonts[tempId];
489
- if (!font) return state;
490
- const reset = resetFontToSuggestions(font);
491
- const fonts = { ...state.fonts, [tempId]: reset };
492
- const subfamilyGroups = rebuildSubfamilyGroups(fonts);
493
- return { ...state, fonts, subfamilyGroups };
494
- }
495
- case "REMOVE_FONT": {
496
- const { tempId } = action;
497
- if (!state.fonts[tempId]) return state;
498
- const { [tempId]: removed, ...remainingFonts } = state.fonts;
499
- const subfamilyGroups = {};
500
- for (const [key, group] of Object.entries(state.subfamilyGroups)) {
501
- const filtered = group.fontIds.filter((id) => id !== tempId);
502
- if (filtered.length > 0) {
503
- subfamilyGroups[key] = { ...group, fontIds: filtered };
504
- }
505
- }
506
- return { ...state, fonts: remainingFonts, subfamilyGroups };
507
- }
508
- default:
509
- return state;
510
- }
511
- }
512
- function resetFontToSuggestions(font) {
513
- const d = font.decisions;
514
- return {
515
- ...font,
516
- title: d.title.processed,
517
- documentId: d.documentId.generated,
518
- weight: d.weight.detected,
519
- weightName: d.weightName.detected,
520
- style: d.style.detected,
521
- subfamily: d.subfamily.detected,
522
- _idConflict: false,
523
- decisions: {
524
- ...d,
525
- title: { ...d.title, userOverride: null, source: d.title.original ? d.title.source : d.title.source },
526
- documentId: { ...d.documentId, userOverride: null },
527
- weight: { ...d.weight, userOverride: null },
528
- weightName: { ...d.weightName, userOverride: null },
529
- style: { ...d.style, userOverride: null },
530
- subfamily: { ...d.subfamily, userOverride: null },
531
- existingDocument: { ...d.existingDocument, userChoice: null, selectedCandidate: null }
532
- }
533
- };
534
- }
535
- function moveFontBetweenGroups(state, tempId, fromKey, toKey) {
536
- const groups = { ...state.subfamilyGroups };
537
- if (groups[fromKey]) {
538
- const filtered = groups[fromKey].fontIds.filter((id) => id !== tempId);
539
- if (filtered.length === 0) {
540
- delete groups[fromKey];
541
- } else {
542
- groups[fromKey] = { ...groups[fromKey], fontIds: filtered };
543
- }
544
- }
545
- if (!groups[toKey]) {
546
- groups[toKey] = { title: toKey, fontIds: [] };
547
- }
548
- if (!groups[toKey].fontIds.includes(tempId)) {
549
- groups[toKey] = { ...groups[toKey], fontIds: [...groups[toKey].fontIds, tempId] };
550
- }
551
- return { ...state, subfamilyGroups: groups };
552
- }
553
- function updateFontAndCheckConflicts(state, tempId, updatedFont) {
554
- const fonts = { ...state.fonts, [tempId]: updatedFont };
555
- const idMap = {};
556
- for (const [id, font] of Object.entries(fonts)) {
557
- fonts[id] = { ...font, _idConflict: false };
558
- const docId = font.documentId;
559
- if (!idMap[docId]) {
560
- idMap[docId] = [id];
561
- } else {
562
- idMap[docId].push(id);
563
- }
564
- }
565
- for (const ids of Object.values(idMap)) {
566
- if (ids.length > 1) {
567
- for (const id of ids) {
568
- fonts[id] = { ...fonts[id], _idConflict: true };
569
- }
570
- }
571
- }
572
- return { ...state, fonts };
573
- }
574
- function rebuildSubfamilyGroups(fonts) {
575
- const groups = {};
576
- for (const [tempId, font] of Object.entries(fonts)) {
577
- if (font.status === FONT_STATUS.ERROR) continue;
578
- const sfName = font.subfamily || "default";
579
- if (!font.variableFont || font.subfamily) {
580
- if (!groups[sfName]) {
581
- groups[sfName] = { title: sfName, fontIds: [] };
582
- }
583
- if (!groups[sfName].fontIds.includes(tempId)) {
584
- groups[sfName].fontIds.push(tempId);
585
- }
586
- }
587
- }
588
- return groups;
589
- }
590
-
591
- // src/utils/buildUploadPlan.js
592
- var _nanoid = require('nanoid');
593
-
594
- // src/utils/parseFont.js
595
- var _Font = null;
596
- async function getFont() {
597
- if (!_Font) {
598
- const pako = await Promise.resolve().then(() => _interopRequireWildcard(require("pako")));
599
- globalThis.pako = pako.default || pako;
600
- await Promise.resolve().then(() => _interopRequireWildcard(require("./unbrotli-NSXTNE2T.js")));
601
- const mod = await Promise.resolve().then(() => _interopRequireWildcard(require("lib-font")));
602
- _Font = mod.Font;
603
- }
604
- return _Font;
605
- }
606
- var MAX_FONT_FILE_SIZE = 50 * 1024 * 1024;
607
- async function parseFont(buffer, filename) {
608
- if (buffer.byteLength > MAX_FONT_FILE_SIZE) {
609
- throw new Error(`Font file exceeds ${MAX_FONT_FILE_SIZE / 1024 / 1024}MB limit: ${filename} (${(buffer.byteLength / 1024 / 1024).toFixed(1)}MB)`);
610
- }
611
- const Font = await getFont();
612
- return new Promise((resolve, reject) => {
613
- const font = new Font("font", { skipStyleSheet: true });
614
- font.onload = (evt) => resolve(evt.detail.font);
615
- font.onerror = (evt) => {
616
- var _a;
617
- return reject(new Error(((_a = evt.detail) == null ? void 0 : _a.message) || `Failed to parse ${filename}`));
618
- };
619
- font.fromDataBuffer(buffer, filename);
620
- });
621
- }
622
-
623
- // src/utils/fontHelpers.js
624
- var nameCache = /* @__PURE__ */ new WeakMap();
625
- function getNameString(font, nameID) {
626
- var _a, _b, _c;
627
- if (!nameCache.has(font)) nameCache.set(font, {});
628
- const cache = nameCache.get(font);
629
- if (nameID in cache) return cache[nameID];
630
- const records = ((_c = (_b = (_a = font.opentype) == null ? void 0 : _a.tables) == null ? void 0 : _b.name) == null ? void 0 : _c.nameRecords) || [];
631
- const win = records.find((r) => r.nameID === nameID && r.platformID === 3 && r.languageID === 1033);
632
- if (win == null ? void 0 : win.string) {
633
- cache[nameID] = win.string;
634
- return win.string;
635
- }
636
- const mac = records.find((r) => r.nameID === nameID && r.platformID === 1 && r.languageID === 0);
637
- if (mac == null ? void 0 : mac.string) {
638
- cache[nameID] = mac.string;
639
- return mac.string;
640
- }
641
- const any = records.find((r) => r.nameID === nameID);
642
- const result = (any == null ? void 0 : any.string) || "";
643
- cache[nameID] = result;
644
- return result;
645
- }
646
- function getAllFeatureTags(font) {
647
- var _a;
648
- const tags = /* @__PURE__ */ new Set();
649
- const tables = (_a = font.opentype) == null ? void 0 : _a.tables;
650
- for (const layoutTable of [tables == null ? void 0 : tables.GSUB, tables == null ? void 0 : tables.GPOS]) {
651
- if (!layoutTable) continue;
652
- try {
653
- for (const scriptTag of layoutTable.getSupportedScripts()) {
654
- const script = layoutTable.getScriptTable(scriptTag);
655
- for (const langTag of layoutTable.getSupportedLangSys(script)) {
656
- const langsys = layoutTable.getLangSysTable(script, langTag);
657
- for (const feature of layoutTable.getFeatures(langsys)) {
658
- tags.add(feature.featureTag.trim());
659
- }
660
- }
661
- }
662
- } catch (err) {
663
- console.warn(`Error reading ${layoutTable === tables.GSUB ? "GSUB" : "GPOS"} features:`, err.message);
664
- }
665
- }
666
- return [...tags];
667
- }
668
- function getCharacterSet(font) {
669
- var _a, _b;
670
- const cmap = (_b = (_a = font.opentype) == null ? void 0 : _a.tables) == null ? void 0 : _b.cmap;
671
- if (!cmap) return [];
672
- try {
673
- return cmap.getSupportedCharCodes(3, 1);
674
- } catch (e2) {
675
- return [];
676
- }
677
- }
678
- function getVariationAxes(font) {
679
- var _a, _b;
680
- const fvar = (_b = (_a = font.opentype) == null ? void 0 : _a.tables) == null ? void 0 : _b.fvar;
681
- if (!(fvar == null ? void 0 : fvar.axes)) return null;
682
- const axes = {};
683
- for (const axis of fvar.axes) {
684
- if (axis.minValue === axis.maxValue) continue;
685
- axes[axis.tag] = {
686
- min: axis.minValue,
687
- max: axis.maxValue,
688
- default: axis.defaultValue,
689
- name: getNameString(font, axis.axisNameID) || axis.tag
690
- };
691
- }
692
- return Object.keys(axes).length > 0 ? axes : null;
693
- }
694
- function getNamedInstances(font) {
695
- var _a, _b;
696
- const fvar = (_b = (_a = font.opentype) == null ? void 0 : _a.tables) == null ? void 0 : _b.fvar;
697
- if (!(fvar == null ? void 0 : fvar.instances)) return [];
698
- return fvar.instances.map((inst) => ({
699
- name: getNameString(font, inst.subfamilyNameID),
700
- coordinates: inst.coordinates,
701
- postScriptName: getNameString(font, inst.postScriptNameID || 0)
702
- }));
703
- }
704
- function getFontMetrics(font) {
705
- var _a;
706
- const tables = (_a = font.opentype) == null ? void 0 : _a.tables;
707
- const os2 = tables == null ? void 0 : tables["OS/2"];
708
- const head = tables == null ? void 0 : tables.head;
709
- const post = tables == null ? void 0 : tables.post;
710
- const hhea = tables == null ? void 0 : tables.hhea;
711
- const useTypo = os2 ? (os2.fsSelection & 128) !== 0 : false;
712
- return {
713
- unitsPerEm: (head == null ? void 0 : head.unitsPerEm) || 1e3,
714
- ascender: useTypo ? (os2 == null ? void 0 : os2.sTypoAscender) || 0 : _nullishCoalesce(_nullishCoalesce((hhea == null ? void 0 : hhea.ascender), () => ( (os2 == null ? void 0 : os2.sTypoAscender))), () => ( 0)),
715
- descender: useTypo ? (os2 == null ? void 0 : os2.sTypoDescender) || 0 : _nullishCoalesce(_nullishCoalesce((hhea == null ? void 0 : hhea.descender), () => ( (os2 == null ? void 0 : os2.sTypoDescender))), () => ( 0)),
716
- lineGap: useTypo ? (os2 == null ? void 0 : os2.sTypoLineGap) || 0 : _nullishCoalesce(_nullishCoalesce((hhea == null ? void 0 : hhea.lineGap), () => ( (os2 == null ? void 0 : os2.sTypoLineGap))), () => ( 0)),
717
- underlinePosition: (post == null ? void 0 : post.underlinePosition) || 0,
718
- underlineThickness: (post == null ? void 0 : post.underlineThickness) || 0,
719
- italicAngle: (post == null ? void 0 : post.italicAngle) || 0,
720
- capHeight: (os2 == null ? void 0 : os2.version) >= 2 ? (os2 == null ? void 0 : os2.sCapHeight) || 0 : 0,
721
- xHeight: (os2 == null ? void 0 : os2.version) >= 2 ? (os2 == null ? void 0 : os2.sxHeight) || 0 : 0,
722
- boundingBox: {
723
- xMin: (head == null ? void 0 : head.xMin) || 0,
724
- yMin: (head == null ? void 0 : head.yMin) || 0,
725
- xMax: (head == null ? void 0 : head.xMax) || 0,
726
- yMax: (head == null ? void 0 : head.yMax) || 0
727
- }
728
- };
729
- }
730
- function getFontMetadata(font) {
731
- return {
732
- postscriptName: getNameString(font, 6),
733
- fullName: getNameString(font, 4),
734
- familyName: getNameString(font, 1),
735
- subfamilyName: getNameString(font, 2),
736
- copyright: getNameString(font, 0),
737
- version: getNameString(font, 5),
738
- genDate: (/* @__PURE__ */ new Date()).toISOString()
739
- };
740
- }
741
- function getWeightClass(font) {
742
- var _a, _b, _c;
743
- return ((_c = (_b = (_a = font.opentype) == null ? void 0 : _a.tables) == null ? void 0 : _b["OS/2"]) == null ? void 0 : _c.usWeightClass) || null;
744
- }
745
- function getFsSelection(font) {
746
- var _a, _b, _c;
747
- return ((_c = (_b = (_a = font.opentype) == null ? void 0 : _a.tables) == null ? void 0 : _b["OS/2"]) == null ? void 0 : _c.fsSelection) || 0;
748
- }
749
- function getMacStyle(font) {
750
- var _a, _b, _c;
751
- const macStyle = (_c = (_b = (_a = font.opentype) == null ? void 0 : _a.tables) == null ? void 0 : _b.head) == null ? void 0 : _c.macStyle;
752
- if (!macStyle) return 0;
753
- if (typeof macStyle === "number") return macStyle;
754
- if (typeof macStyle === "object") {
755
- let value = 0;
756
- for (let i = 0; i < 16; i++) {
757
- if (macStyle[i]) value |= 1 << 15 - i;
758
- }
759
- return value;
760
- }
761
- return 0;
762
- }
763
- function getItalicAngle(font) {
764
- var _a, _b, _c;
765
- return ((_c = (_b = (_a = font.opentype) == null ? void 0 : _a.tables) == null ? void 0 : _b.post) == null ? void 0 : _c.italicAngle) || 0;
766
- }
767
- function getGlyphCount(font) {
768
- var _a, _b, _c;
769
- return ((_c = (_b = (_a = font.opentype) == null ? void 0 : _a.tables) == null ? void 0 : _b.maxp) == null ? void 0 : _c.numGlyphs) || 0;
770
- }
771
- function getFamilyClass(font) {
772
- var _a, _b, _c;
773
- return ((_c = (_b = (_a = font.opentype) == null ? void 0 : _a.tables) == null ? void 0 : _b["OS/2"]) == null ? void 0 : _c.sFamilyClass) || 0;
774
- }
775
- function escapeCssFontName(name) {
776
- return name.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/"/g, '\\"').replace(/;/g, "");
777
- }
778
-
779
- // src/utils/processFontFiles.js
780
-
781
-
782
- // src/utils/generateKeywords.js
783
- var coreWeights = ["Hairline", "ExtraThin", "Thin", "Mager", "Maigre", "ExtraLight", "Light", "Chiaro", "Lite", "Leicht", "Demi", "Book", "Buch", "Regular", "Normal", "Medium", "Stark", "Thick", "Kr\xE4ftig", "Viertelfett", "Halbfett", "Dreiviertelfett", "Dark", "Bold", "Neretto", "Gras", "Fett", "Extrafett", "Black", "Nero", "Heavy", "Nerissimo", "Ultra", "Fat", "Poster"];
784
- var modifiers = ["Demi", "Semi", "Extra", "Ultra", "Super", "Plus"];
785
- var coreItalics = ["Italic", "Slant", "Oblique", "Cursive", "Rotalic", "Reverse", "Crab Claw", "Crabclaw", "South Paw", "Southpaw", "Backwards", "Backslant", "Backslanted", "Back Slant"];
786
- var alternativeSpelling = {
787
- Backslant: ["Bsl"],
788
- Backwards: ["Bck"],
789
- Black: ["Blak", "Blk"],
790
- Bold: ["Bd", "Bld"],
791
- // B omitted — too ambiguous
792
- Condensed: ["Cond", "Cnd"],
793
- Crabclaw: ["Crab", "Claw"],
794
- Cursive: ["Cur"],
795
- Dark: ["Drk"],
796
- Expanded: ["Exp"],
797
- Extra: ["Xt", "Xtra", "Xtr", "X"],
798
- // X omitted as standalone — too ambiguous
799
- ExtraBlack: ["Xblk", "XBlk", "Xblck", "XBlck"],
800
- ExtraBold: ["Xbd", "XBd", "Xbld", "XBld", "Xbold", "XBold", "ExBold", "Exbold", "Exbd", "ExBd", "Exbld", "ExBld"],
801
- ExtraCondensed: ["XCond", "Xcnd"],
802
- ExtraExpanded: ["XExp"],
803
- ExtraLight: ["Xlight", "XLight", "Xlt", "XLt", "Xlgt", "XLgt", "Xl", "XL", "Xlght", "XLght"],
804
- ExtraThin: ["Xthin", "Xthn", "Xth", "XThin", "XThn", "XTh", "XT"],
805
- Extended: ["Ext"],
806
- Hairline: ["Hl", "Hln", "Hlnn", "Hlnne", "Hlnnne"],
807
- Italic: ["Ital", "It"],
808
- Light: ["Lt", "Lght"],
809
- Medium: ["Med", "Md", "md", "med"],
810
- Oblique: ["Obl"],
811
- Plus: ["Pls"],
812
- Regular: ["Reg", "Rg"],
813
- Reverse: ["Rev"],
814
- Rotalic: ["Rot"],
815
- SemiBold: ["SmBd", "Sb", "Sbd", "Sbld", "Sbold", "Semibd", "SemiBd", "Semibld", "SemiBld", "semiBd", "semiBld"],
816
- Slant: ["Sl"],
817
- Southpaw: ["South", "Paw"],
818
- Super: ["Supr"],
819
- Thin: ["Thn"],
820
- Ultra: ["Ult", "Ultre", "Ul", "Ulta"],
821
- XX: ["XXt", "XXtra", "XXtr", "XX"],
822
- XXBlack: ["XXblk", "XXBlk", "XXblck", "XXBlck"],
823
- XXLight: ["XXlight", "XXLight", "XXlt", "XXLt", "XXlgt", "XXLgt", "XXl", "XXL", "XXlght", "XXLght"],
824
- XXX: ["XXXt", "XXXtra", "XXXtr", "XXX"],
825
- XXXLight: ["XXXlight", "XXXLight", "XXXlt", "XXXLt", "XXXlgt", "XXXLgt", "XXXl", "XXXL", "XXXlght", "XXXLght"]
826
- };
827
- function reverseSpellingLookup(str) {
828
- let exactMatch = "";
829
- Object.keys(alternativeSpelling).forEach(function(key) {
830
- alternativeSpelling[key].forEach(function(alternative) {
831
- if (str === alternative) {
832
- exactMatch = key;
833
- }
834
- });
835
- });
836
- if (exactMatch) return exactMatch;
837
- let result = "";
838
- let longestMatch = 0;
839
- Object.keys(alternativeSpelling).forEach(function(key) {
840
- alternativeSpelling[key].forEach(function(alternative) {
841
- const regex = new RegExp(`\\b${alternative}\\b`);
842
- if (regex.test(str) && alternative.length > longestMatch) {
843
- result = key;
844
- longestMatch = alternative.length;
845
- }
846
- });
847
- });
848
- return result;
849
- }
850
- function expandAbbreviations(str) {
851
- if (!str) return str;
852
- return str.split(" ").map((word) => {
853
- const expanded = reverseSpellingLookup(word);
854
- return expanded || word;
855
- }).join(" ");
856
- }
857
- function removeWeightNames(str) {
858
- if (!str) return str;
859
- return str.split(" ").map((word) => {
860
- coreWeights.forEach((weight) => {
861
- if (word === weight) word = "";
862
- modifiers.forEach((modifier) => {
863
- if (word === modifier || modifier + weight === word) word = "";
864
- });
865
- });
866
- const expanded = reverseSpellingLookup(word);
867
- if (expanded) return "";
868
- return word;
869
- }).join(" ").trim();
870
- }
871
- function generateStyleKeywords() {
872
- let weightKeywordList = [];
873
- let italicKeywordList = [];
874
- weightKeywordList = [...coreWeights];
875
- modifiers.forEach((modifier) => {
876
- coreWeights.forEach((weight) => {
877
- weightKeywordList.push(modifier + weight);
878
- });
879
- });
880
- weightKeywordList = [...weightKeywordList, ...modifiers];
881
- italicKeywordList = [...coreItalics];
882
- weightKeywordList = weightKeywordList.map(function(el) {
883
- var newEls = [];
884
- Object.keys(alternativeSpelling).forEach(function(key) {
885
- if (el.indexOf(key) !== -1) {
886
- alternativeSpelling[key].forEach(function(alternative) {
887
- let newSpelling = el.replace(key, alternative);
888
- newEls.push(newSpelling);
889
- Object.keys(alternativeSpelling).forEach(function(key2) {
890
- if (newSpelling.indexOf(key2) !== -1) {
891
- alternativeSpelling[key2].forEach(function(alternative2) {
892
- let newSpelling2 = newSpelling.replace(key2, alternative2);
893
- newEls.push(newSpelling2);
894
- Object.keys(alternativeSpelling).forEach(function(key3) {
895
- if (newSpelling2.indexOf(key3) !== -1) {
896
- alternativeSpelling[key3].forEach(function(alternative3) {
897
- newEls.push(newSpelling2.replace(key3, alternative3));
898
- });
899
- }
900
- });
901
- });
902
- }
903
- });
904
- });
905
- }
906
- });
907
- newEls.push(el);
908
- return newEls;
909
- }).reduce(function(a, b) {
910
- return a.concat(b);
911
- });
912
- italicKeywordList = italicKeywordList.map(function(el) {
913
- var newEls = [];
914
- Object.keys(alternativeSpelling).forEach(function(key) {
915
- if (el.indexOf(key) !== -1) {
916
- alternativeSpelling[key].forEach(function(alternative) {
917
- newEls.push(el.replace(key, alternative));
918
- });
919
- }
920
- });
921
- newEls.push(el);
922
- return newEls;
923
- }).reduce(function(a, b) {
924
- return a.concat(b);
925
- });
926
- weightKeywordList = weightKeywordList.sort((a, b) => b.length - a.length);
927
- italicKeywordList = italicKeywordList.sort((a, b) => b.length - a.length);
928
- weightKeywordList = weightKeywordList.filter((item, pos) => weightKeywordList.indexOf(item) === pos);
929
- italicKeywordList = italicKeywordList.filter((item, pos) => italicKeywordList.indexOf(item) === pos);
930
- return { weightKeywordList, italicKeywordList };
931
- }
932
-
933
- // src/utils/processFontFiles.js
934
- var readFontFile = (file) => {
935
- return new Promise((resolve, reject) => {
936
- const reader = new FileReader();
937
- reader.onload = (event) => {
938
- resolve(event.target.result);
939
- };
940
- reader.onerror = (error) => {
941
- reject(error);
942
- };
943
- reader.readAsArrayBuffer(file);
944
- });
945
- };
946
- var processFontFiles = async (files, title, weightKeywordList, italicKeywordList, setStatus, preserveShortenedNames = false, preserveFileNames = false) => {
947
- let failedFiles = [];
948
- let subfamilies = {};
949
- let fontsObjects = {};
950
- let newPreferredStyle = { weight: -100, style: "Italic", _ref: "" };
951
- for (let i = 0; i < files.length; i++) {
952
- const file = files[i];
953
- const fontBuffer = await readFontFile(file);
954
- const font = await parseFont(fontBuffer, file.name);
955
- console.log("File name:", file.name);
956
- const ttfFallbackMeta = await getWebfontFallbackMetadata(file, font, files);
957
- let { weightName, subfamilyName, fontTitle, style, italicKW, variableFont } = extractFontMetadata(
958
- font,
959
- title,
960
- weightKeywordList,
961
- italicKeywordList,
962
- preserveShortenedNames,
963
- ttfFallbackMeta
964
- );
965
- let id;
966
- let originalFilename = null;
967
- if (preserveFileNames) {
968
- originalFilename = file.name.replace(/\.(ttf|otf|woff2?|eot|svg)$/i, "");
969
- const normalizedName = originalFilename.replace(/-/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2").replace(/\s+/g, " ").trim();
970
- fontTitle = normalizedName;
971
- id = sanitizeForSanityId(normalizedName);
972
- } else {
973
- id = sanitizeForSanityId(fontTitle);
974
- }
975
- logFontInfo(id, fontTitle, font, file.name, subfamilyName, style, weightName, variableFont, italicKW);
976
- subfamilies[id] = subfamilyName;
977
- if (fontsObjects[id]) {
978
- fontsObjects[id].files = [...fontsObjects[id].files, file];
979
- if (preserveFileNames && originalFilename) {
980
- fontsObjects[id].originalFilename = originalFilename;
981
- }
982
- } else {
983
- fontsObjects[id] = createFontObject(
984
- id,
985
- fontTitle,
986
- title,
987
- font,
988
- variableFont,
989
- weightName,
990
- subfamilyName,
991
- file,
992
- preserveFileNames ? originalFilename : null
993
- );
994
- }
995
- }
996
- fontsObjects = sortFontObjects(fontsObjects);
997
- const uniqueSubfamilies = [...new Set(Object.values(subfamilies))];
998
- console.log("Subfamilies:", subfamilies);
999
- console.log("Unique subfamilies:", uniqueSubfamilies, uniqueSubfamilies.length);
1000
- console.log("Font objects:", Object.keys(fontsObjects));
1001
- return { fontsObjects, subfamilies, uniqueSubfamilies, newPreferredStyle, failedFiles };
1002
- };
1003
- var getWebfontFallbackMetadata = async (file, font, files) => {
1004
- if (!file.name.endsWith(".woff2") && !file.name.endsWith(".woff")) return null;
1005
- const fullName = getNameString(font, 4);
1006
- if (fullName && fullName !== "" && !/^[A-Z0-9]+$/.test(fullName)) return null;
1007
- const ttfFile = files.find((f) => f.name === file.name.replace(".woff2", ".ttf").replace(".woff", ".ttf"));
1008
- if (!ttfFile) return null;
1009
- try {
1010
- const ttfBuffer = await readFontFile(ttfFile);
1011
- const ttfFont = await parseFont(ttfBuffer, ttfFile.name);
1012
- return {
1013
- fullName: getNameString(ttfFont, 4),
1014
- familyName: getNameString(ttfFont, 1),
1015
- subfamilyName: getNameString(ttfFont, 2),
1016
- preferredSubfamily: getNameString(ttfFont, 17),
1017
- preferredFamily: getNameString(ttfFont, 16)
1018
- };
1019
- } catch (err) {
1020
- console.warn("Could not parse TTF companion for webfont fallback:", err.message);
1021
- return null;
1022
- }
1023
- };
1024
- var extractFontMetadata = (font, title, weightKeywordList, italicKeywordList, preserveShortenedNames = false, ttfFallbackMeta = null) => {
1025
- let weightName = extractWeightName(font, italicKeywordList, ttfFallbackMeta);
1026
- if (!preserveShortenedNames) {
1027
- weightName = expandAbbreviations(weightName);
1028
- }
1029
- const fullName = getNameString(font, 4) || (ttfFallbackMeta == null ? void 0 : ttfFallbackMeta.fullName) || "";
1030
- const axes = getVariationAxes(font);
1031
- const variableFont = axes !== null;
1032
- if (!variableFont && (weightName === "" || weightName.toLowerCase() === "roman") && fullName) {
1033
- weightName = extractWeightFromFullName(font, title, ttfFallbackMeta);
1034
- if (!preserveShortenedNames) {
1035
- weightName = expandAbbreviations(weightName);
1036
- }
1037
- }
1038
- const trimmedTitle = title.trim();
1039
- const nameId4Remainder = fullName ? fullName.replace(trimmedTitle, "").trim() : "";
1040
- const nameId1 = getNameString(font, 1) || (ttfFallbackMeta == null ? void 0 : ttfFallbackMeta.familyName) || "";
1041
- const nameId1Remainder = nameId1 ? nameId1.replace(trimmedTitle, "").trim() : "";
1042
- let subfamilyName = nameId4Remainder || nameId1Remainder;
1043
- if (!preserveShortenedNames) {
1044
- subfamilyName = expandAbbreviations(subfamilyName);
1045
- }
1046
- let fontTitle = fullName.trim() || "";
1047
- const italicAngle = getItalicAngle(font);
1048
- let style = italicAngle !== 0 || fullName.toLowerCase().includes("italic") ? "Italic" : "Regular";
1049
- const italicKW = processItalicKeywords(font, fontTitle, italicKeywordList);
1050
- subfamilyName = processSubfamilyName(subfamilyName, weightKeywordList, italicKW, preserveShortenedNames);
1051
- fontTitle = formatFontTitle(fontTitle, preserveShortenedNames);
1052
- subfamilyName = subfamilyName.replace(/\b(Italic|Slant|Slanted|Oblique|Backslant|Roman|Upright)\b/gi, "").replace(/\s+/g, " ").trim();
1053
- if (subfamilyName !== "") {
1054
- weightName = weightName.replace(`${subfamilyName} `, "").replace(` ${subfamilyName}`, "").trim();
1055
- }
1056
- if (variableFont) {
1057
- if (!fontTitle.toLowerCase().includes("vf")) {
1058
- fontTitle = fontTitle + " VF";
1059
- }
1060
- subfamilyName = "";
1061
- }
1062
- if (!(variableFont && fontTitle.toLowerCase().includes("italic"))) {
1063
- fontTitle = addItalicToFontTitle(font, fontTitle, italicKW, style, preserveShortenedNames);
1064
- }
1065
- return { weightName, subfamilyName, fontTitle, style, italicKW, variableFont };
1066
- };
1067
- var extractWeightName = (font, italicKW, ttfFallbackMeta = null) => {
1068
- let weightName = getNameString(font, 17) || getNameString(font, 2) || (ttfFallbackMeta == null ? void 0 : ttfFallbackMeta.preferredSubfamily) || (ttfFallbackMeta == null ? void 0 : ttfFallbackMeta.subfamilyName) || "";
1069
- const axes = getVariationAxes(font);
1070
- if (axes !== null) {
1071
- return "";
1072
- }
1073
- if (italicKW) {
1074
- italicKW.forEach((keyword) => {
1075
- const kwRegex = new RegExp(`\\b${keyword.trim()}\\b`, "i");
1076
- if (kwRegex.test(weightName)) {
1077
- weightName = weightName.replace(kwRegex, "").trim();
1078
- }
1079
- });
1080
- }
1081
- return weightName == null ? void 0 : weightName.toString().replace("Italic", "").replace("It", "").replace("Slanted", "").replace("Slant", "").replace("Backslant", "").trim();
1082
- };
1083
- var extractWeightFromFullName = (font, title, ttfFallbackMeta = null) => {
1084
- let weightName = getNameString(font, 4) || (ttfFallbackMeta == null ? void 0 : ttfFallbackMeta.fullName) || "";
1085
- weightName = weightName.replace(title + " ", "").replace(title, "").trim();
1086
- weightName = weightName.replace("Italic", "").replace("It", "").replace("Slanted", "").replace("Slant", "").trim();
1087
- return weightName;
1088
- };
1089
- var processSubfamilyName = (subfamilyName, weightKeywordList, italicKeywordList, preserveShortenedNames = false) => {
1090
- weightKeywordList.forEach((keyword) => {
1091
- const kwRegex = new RegExp(`\\b${keyword.trim()}\\b`, "i");
1092
- if (kwRegex.test(subfamilyName)) {
1093
- subfamilyName = subfamilyName.replace(kwRegex, "").trim();
1094
- }
1095
- subfamilyName = removeWeightNames(subfamilyName) || subfamilyName;
1096
- if (!preserveShortenedNames) {
1097
- subfamilyName = expandAbbreviations(subfamilyName);
1098
- }
1099
- });
1100
- italicKeywordList.forEach((keyword) => {
1101
- const kwRegex = new RegExp(`\\b${keyword.trim()}\\b`, "i");
1102
- if (kwRegex.test(subfamilyName)) {
1103
- subfamilyName = subfamilyName.replace(kwRegex, "").trim();
1104
- }
1105
- });
1106
- return subfamilyName;
1107
- };
1108
- var processItalicKeywords = (font, fontTitle, italicKeywordList) => {
1109
- let italicKW = [];
1110
- const fullName = getNameString(font, 4);
1111
- italicKeywordList.forEach((keyword) => {
1112
- const kw = keyword.trim();
1113
- const kwRegex = new RegExp(`\\b${kw}\\b`, "i");
1114
- if (kwRegex.test(fontTitle)) {
1115
- fontTitle = fontTitle.replace(kwRegex, "").trim();
1116
- italicKW.push(kw);
1117
- }
1118
- if (fullName && fullName.toLowerCase().includes(kw.toLowerCase())) {
1119
- if (!italicKW.includes(kw)) italicKW.push(kw);
1120
- }
1121
- });
1122
- return italicKW;
1123
- };
1124
- var formatFontTitle = (fontTitle, preserveShortenedNames = false) => {
1125
- const hasItalic = fontTitle.toLowerCase().includes("italic");
1126
- fontTitle = fontTitle.replace(/-/g, " ");
1127
- return fontTitle.replace(/\s+/g, " ").trim().split(" ").map((word) => {
1128
- if (hasItalic && word.toLowerCase() === "italic") return "Italic";
1129
- let fullWord = word;
1130
- if (!preserveShortenedNames) {
1131
- fullWord = reverseSpellingLookup(word) || word;
1132
- }
1133
- return fullWord[0].toUpperCase() + fullWord.slice(1);
1134
- }).join(" ");
1135
- };
1136
- var addItalicToFontTitle = (font, fontTitle, italicKW, style, preserveShortenedNames = false) => {
1137
- const hasItalicAngle = getItalicAngle(font) !== 0;
1138
- const fullName = getNameString(font, 4);
1139
- const hasItalicInName = fullName.toLowerCase().includes("italic");
1140
- if (italicKW.length > 0 || hasItalicAngle || hasItalicInName) {
1141
- italicKW = [...new Set(italicKW)];
1142
- if (italicKW.length === 0 && (hasItalicAngle || hasItalicInName)) {
1143
- italicKW = ["Italic"];
1144
- }
1145
- if (!preserveShortenedNames) {
1146
- italicKW = italicKW.map((item) => reverseSpellingLookup(item) || item);
1147
- }
1148
- italicKW = [...new Set(italicKW)];
1149
- if (italicKW.length > 1 && italicKW.includes("Italic")) {
1150
- italicKW = ["Italic"];
1151
- }
1152
- const fontTitleLower = fontTitle.toLowerCase();
1153
- italicKW = italicKW.filter((keyword) => {
1154
- const keywordLower = keyword.toLowerCase();
1155
- const kwRegex = new RegExp(`\\b${keywordLower}\\b`);
1156
- const isSubstring = fontTitleLower.split(" ").some(
1157
- (word) => word.includes(keywordLower) || keywordLower.includes(word)
1158
- );
1159
- return !kwRegex.test(fontTitleLower) && !isSubstring;
1160
- });
1161
- if (italicKW.length > 0) {
1162
- fontTitle = fontTitle.trim() + " " + italicKW.join(" ");
1163
- }
1164
- }
1165
- return fontTitle;
1166
- };
1167
- var createFontObject = (id, fontTitle, title, font, variableFont, weightName, subfamilyName, file, originalFilename = null) => {
1168
- const italicAngle = getItalicAngle(font);
1169
- const fullName = getNameString(font, 4);
1170
- const fontObject = {
1171
- _key: _nanoid.nanoid.call(void 0, ),
1172
- _id: id,
1173
- title: fontTitle,
1174
- slug: { _type: "slug", current: id },
1175
- typefaceName: title,
1176
- style: italicAngle !== 0 || fullName.toLowerCase().includes("italic") ? "Italic" : "Regular",
1177
- variableFont,
1178
- weightName,
1179
- subfamily: subfamilyName,
1180
- normalWeight: true,
1181
- weight: Number(determineWeight(font, weightName)),
1182
- fileInput: {},
1183
- files: [file],
1184
- fontKit: font
1185
- };
1186
- if (originalFilename) {
1187
- fontObject.originalFilename = originalFilename;
1188
- }
1189
- return fontObject;
1190
- };
1191
- var determineWeight = (font, weightName) => {
1192
- const usWeightClass = getWeightClass(font);
1193
- if (usWeightClass) {
1194
- return Number(usWeightClass);
1195
- }
1196
- const wn = (weightName == null ? void 0 : weightName.toLowerCase()) || "";
1197
- if (/hairline|extra thin|extrathin/.test(wn)) return 100;
1198
- if (/thin|extra light|extralight/.test(wn)) return 200;
1199
- if (/light|book/.test(wn)) return 300;
1200
- if (/regular|normal/.test(wn)) return 400;
1201
- if (/medium/.test(wn)) return 500;
1202
- if (/semi bold|semibold/.test(wn)) return 600;
1203
- if (/extra bold|extrabold/.test(wn)) return 800;
1204
- if (/bold/.test(wn)) return 700;
1205
- if (/black|ultra/.test(wn)) return 900;
1206
- return 400;
1207
- };
1208
- var sortFontObjects = (fontsObjects) => {
1209
- return Object.fromEntries(
1210
- Object.entries(fontsObjects).sort((a, b) => {
1211
- const weightA = Number(a[1].weight);
1212
- const weightB = Number(b[1].weight);
1213
- if (weightA === weightB) {
1214
- if (a[1].style === "Regular" && b[1].style === "Italic") return -1;
1215
- if (a[1].style === "Italic" && b[1].style === "Regular") return 1;
1216
- return 0;
1217
- }
1218
- return weightA - weightB;
1219
- })
1220
- );
1221
- };
1222
- var logFontInfo = (id, fontTitle, font, fileName, subfamilyName, style, weightName, variableFont, italicKW) => {
1223
- const fullName = getNameString(font, 4);
1224
- const familyName = getNameString(font, 1);
1225
- const italicAngle = getItalicAngle(font);
1226
- console.log("=== Font Info ====");
1227
- console.log("Font id:", id);
1228
- console.log("Font title:", fontTitle);
1229
- console.log("Full name:", fullName);
1230
- console.log("Family name:", familyName);
1231
- console.log("File name:", fileName);
1232
- console.log("Subfamily:", subfamilyName);
1233
- console.log("Style:", style);
1234
- console.log("Weight:", weightName);
1235
- console.log("Variable:", variableFont);
1236
- console.log("ItalicKW:", italicKW);
1237
- console.log("Italic detection:", italicAngle !== 0 || fullName.toLowerCase().includes("italic") ? "Italic" : "Regular");
1238
- console.log("=======");
1239
- };
1240
-
1241
- // src/utils/resolveExistingFont.js
1242
- var resolveExistingFont = async (font, client) => {
1243
- const result = {
1244
- exact: null,
1245
- candidates: [],
1246
- recommendation: RECOMMENDATION.CREATE,
1247
- lookupFailed: false
1248
- };
1249
- try {
1250
- const idMatches = await client.fetch(
1251
- `*[_type == 'font' && (_id == $id || _id == $draftId || slug.current == $id)]{
1252
- _id, title, weight, style, weightName, typefaceName, subfamily, variableFont,
1253
- fileInput, description, metaData, metrics, opentypeFeatures, characterSet,
1254
- scriptFileInput, variableInstanceReferences
1255
- }`,
1256
- { id: font._id, draftId: `drafts.${font._id}` }
1257
- );
1258
- if (idMatches.length > 0) {
1259
- result.exact = idMatches[0];
1260
- result.recommendation = RECOMMENDATION.USE_EXACT;
1261
- return result;
1262
- }
1263
- const subfamily = font.subfamily || "";
1264
- const contentMatches = await client.fetch(
1265
- `*[_type == 'font'
1266
- && lower(typefaceName) == lower($typefaceName)
1267
- && lower(weightName) == lower($weightName)
1268
- && lower(style) == lower($style)
1269
- && (variableFont == $variableFont || (!defined(variableFont) && $variableFont == false))
1270
- && (
1271
- lower(coalesce(subfamily, '')) == lower($subfamily)
1272
- || (lower(coalesce(subfamily, '')) in ['', 'regular'] && lower($subfamily) in ['', 'regular'])
1273
- )
1274
- ]{
1275
- _id, title, weight, style, weightName, typefaceName, subfamily, variableFont,
1276
- fileInput, description, metaData, metrics, opentypeFeatures, characterSet,
1277
- scriptFileInput, variableInstanceReferences
1278
- }`,
1279
- {
1280
- typefaceName: font.typefaceName,
1281
- weightName: font.weightName || "",
1282
- style: font.style || "Regular",
1283
- variableFont: font.variableFont || false,
1284
- subfamily: subfamily === "" ? "regular" : subfamily
1285
- }
1286
- );
1287
- if (contentMatches.length === 1) {
1288
- result.candidates = contentMatches;
1289
- result.recommendation = RECOMMENDATION.USE_CANDIDATE;
1290
- return result;
1291
- }
1292
- if (contentMatches.length > 1) {
1293
- result.candidates = contentMatches;
1294
- result.recommendation = RECOMMENDATION.AMBIGUOUS;
1295
- console.warn(
1296
- `Ambiguous font match for "${font.title}" \u2014 ${contentMatches.length} candidates found:`,
1297
- contentMatches.map((c) => c._id)
1298
- );
1299
- return result;
1300
- }
1301
- } catch (err) {
1302
- console.error("Error resolving existing font:", font._id, err.message);
1303
- result.lookupFailed = true;
1304
- }
1305
- return result;
1306
- };
1307
-
1308
- // src/utils/buildUploadPlan.js
1309
- async function buildUploadPlan({
1310
- files,
1311
- typefaceTitle,
1312
- docId,
1313
- settings = {},
1314
- client,
1315
- stylesObject = {},
1316
- weightKeywordList = [],
1317
- italicKeywordList = [],
1318
- onProgress
1319
- }) {
1320
- const plan = createEmptyPlan(settings);
1321
- plan.settings.typefaceTitle = typefaceTitle;
1322
- plan.phase = PLAN_PHASE.PROCESSING;
1323
- plan.processingProgress.total = files.length;
1324
- for (let i = 0; i < files.length; i++) {
1325
- const file = files[i];
1326
- plan.processingProgress.currentFile = file.name;
1327
- try {
1328
- const fontBuffer = await readFontFile(file);
1329
- const font = await parseFont(fontBuffer, file.name);
1330
- const entry = await buildFontPlanEntry({
1331
- file,
1332
- font,
1333
- typefaceTitle,
1334
- settings: plan.settings,
1335
- weightKeywordList,
1336
- italicKeywordList,
1337
- client
1338
- });
1339
- const existingKey = Object.keys(plan.fonts).find((k) => plan.fonts[k].documentId === entry.documentId);
1340
- if (existingKey) {
1341
- plan.fonts[existingKey].files = [...plan.fonts[existingKey].files, ...entry.files];
1342
- } else {
1343
- plan.fonts[entry.tempId] = entry;
1344
- if (!entry.variableFont || entry.subfamily) {
1345
- const sfName = entry.subfamily;
1346
- if (!plan.subfamilyGroups[sfName]) {
1347
- plan.subfamilyGroups[sfName] = { title: sfName, fontIds: [] };
1348
- }
1349
- plan.subfamilyGroups[sfName].fontIds.push(entry.tempId);
1350
- }
1351
- }
1352
- plan.processingProgress.completed++;
1353
- if (onProgress) {
1354
- onProgress({
1355
- type: "font-processed",
1356
- tempId: entry.tempId,
1357
- fontEntry: entry,
1358
- progress: { ...plan.processingProgress }
1359
- });
1360
- }
1361
- } catch (err) {
1362
- console.error(`Error processing ${file.name}:`, err.message);
1363
- plan.processingProgress.failed++;
1364
- const errorTempId = sanitizeForSanityId(file.name) + "-" + _nanoid.nanoid.call(void 0, 6);
1365
- plan.fonts[errorTempId] = {
1366
- tempId: errorTempId,
1367
- files: [file],
1368
- sourceFileName: file.name,
1369
- title: file.name,
1370
- documentId: "",
1371
- weight: 400,
1372
- weightName: "",
1373
- style: "Regular",
1374
- subfamily: "",
1375
- variableFont: false,
1376
- originalFilename: null,
1377
- decisions: createFontDecisions({}),
1378
- status: FONT_STATUS.ERROR,
1379
- error: err.message,
1380
- parsedMetadata: null,
1381
- glyphCount: 0,
1382
- opentypeFeatures: [],
1383
- variationAxes: null
1384
- };
1385
- if (onProgress) {
1386
- onProgress({
1387
- type: "font-error",
1388
- tempId: errorTempId,
1389
- error: err.message,
1390
- progress: { ...plan.processingProgress }
1391
- });
1392
- }
1393
- }
1394
- }
1395
- plan.processingProgress.currentFile = null;
1396
- plan.phase = PLAN_PHASE.REVIEWING;
1397
- if (onProgress) {
1398
- onProgress({
1399
- type: "processing-complete",
1400
- progress: { ...plan.processingProgress }
1401
- });
1402
- }
1403
- return plan;
1404
- }
1405
- async function buildFontPlanEntry({
1406
- file,
1407
- font,
1408
- typefaceTitle,
1409
- settings,
1410
- weightKeywordList,
1411
- italicKeywordList,
1412
- client
1413
- }) {
1414
- const { weightName, subfamilyName, fontTitle, style, italicKW, variableFont } = extractFontMetadata(
1415
- font,
1416
- typefaceTitle,
1417
- weightKeywordList,
1418
- italicKeywordList,
1419
- settings.preserveShortenedNames
1420
- );
1421
- let finalTitle = fontTitle;
1422
- let originalFilename = null;
1423
- if (settings.preserveFileNames) {
1424
- originalFilename = file.name.replace(/\.(ttf|otf|woff2?|eot|svg)$/i, "");
1425
- const normalizedName = originalFilename.replace(/-/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2").replace(/\s+/g, " ").trim();
1426
- finalTitle = normalizedName;
1427
- }
1428
- const documentId = sanitizeForSanityId(finalTitle);
1429
- const tempId = documentId + "-" + _nanoid.nanoid.call(void 0, 6);
1430
- const weight = Number(determineWeight(font, weightName));
1431
- const titleAlternatives = [
1432
- { value: getNameString(font, 1), source: "nameId1-familyName" },
1433
- { value: getNameString(font, 4), source: "nameId4-fullName" },
1434
- { value: getNameString(font, 6), source: "nameId6-postscriptName" },
1435
- { value: getNameString(font, 16), source: "nameId16-preferredFamily" },
1436
- { value: getNameString(font, 17), source: "nameId17-preferredSubfamily" },
1437
- { value: file.name.replace(/\.(ttf|otf|woff2?|eot|svg)$/i, ""), source: "filename" }
1438
- ].filter((alt) => alt.value);
1439
- const titleSource = settings.preserveFileNames ? "filename" : "fontkit-fullName";
1440
- const usWeightClass = getWeightClass(font);
1441
- const weightSource = usWeightClass ? "os2-usWeightClass" : "keyword-match";
1442
- const matchedKeyword = usWeightClass ? null : weightName;
1443
- const nameId17 = getNameString(font, 17);
1444
- const weightNameSource = variableFont ? "variable-font-empty" : nameId17 ? "nameId17-preferredSubfamily" : "nameId2-fontSubfamily";
1445
- const italicAngle = getItalicAngle(font);
1446
- const fullName = getNameString(font, 4);
1447
- let styleSource = "default-regular";
1448
- let styleReason = "";
1449
- if (style === "Italic") {
1450
- if (italicAngle !== 0) {
1451
- styleSource = "italic-angle";
1452
- styleReason = `italicAngle = ${italicAngle}`;
1453
- } else if (fullName.toLowerCase().includes("italic")) {
1454
- styleSource = "name-contains-italic";
1455
- styleReason = 'fullName contains "italic"';
1456
- }
1457
- }
1458
- const familyNameRaw = getNameString(font, 1);
1459
- const nameId4Remainder = fullName ? fullName.replace(typefaceTitle.trim(), "").trim() : "";
1460
- const nameId1Remainder = familyNameRaw ? familyNameRaw.replace(typefaceTitle.trim(), "").trim() : "";
1461
- let subfamilySource = "default-empty";
1462
- if (nameId4Remainder && subfamilyName) {
1463
- subfamilySource = "nameId4-subtraction";
1464
- } else if (nameId1Remainder && subfamilyName) {
1465
- subfamilySource = "nameId1-subtraction";
1466
- }
1467
- const decisions = createFontDecisions({
1468
- titleSource,
1469
- title: finalTitle,
1470
- titleOriginal: getNameString(font, 4),
1471
- documentId,
1472
- weight,
1473
- weightSource,
1474
- matchedKeyword,
1475
- weightName,
1476
- weightNameSource,
1477
- style,
1478
- styleSource,
1479
- styleReason,
1480
- subfamily: subfamilyName,
1481
- subfamilySource,
1482
- titleAlternatives
1483
- });
1484
- const fontForResolution = {
1485
- _id: documentId,
1486
- typefaceName: typefaceTitle,
1487
- weightName,
1488
- style,
1489
- subfamily: subfamilyName,
1490
- variableFont,
1491
- title: finalTitle
1492
- };
1493
- try {
1494
- const resolution = await resolveExistingFont(fontForResolution, client);
1495
- decisions.existingDocument = {
1496
- recommendation: resolution.recommendation,
1497
- exact: resolution.exact,
1498
- candidates: resolution.candidates,
1499
- userChoice: null,
1500
- selectedCandidate: null,
1501
- lookupFailed: resolution.lookupFailed || false
1502
- };
1503
- } catch (err) {
1504
- console.warn("Document resolution failed for", documentId, err.message);
1505
- decisions.existingDocument.lookupFailed = true;
1506
- }
1507
- const metadata = getFontMetadata(font);
1508
- const metrics = getFontMetrics(font);
1509
- const axes = getVariationAxes(font);
1510
- const finalSubfamily = variableFont ? "" : subfamilyName || "Regular";
1511
- console.log(`[buildFontPlanEntry] "${finalTitle}" \u2192 subfamily: "${subfamilyName}" \u2192 final: "${finalSubfamily}" (variableFont: ${variableFont})`);
1512
- return {
1513
- tempId,
1514
- files: [file],
1515
- sourceFileName: file.name,
1516
- title: finalTitle,
1517
- documentId,
1518
- weight,
1519
- weightName,
1520
- style,
1521
- subfamily: finalSubfamily,
1522
- variableFont,
1523
- originalFilename,
1524
- decisions,
1525
- status: FONT_STATUS.PROCESSED,
1526
- error: null,
1527
- parsedMetadata: { ...metadata, ...metrics },
1528
- glyphCount: getGlyphCount(font),
1529
- opentypeFeatures: getAllFeatureTags(font),
1530
- variationAxes: axes
1531
- };
1532
- }
1533
-
1534
- // src/components/UploadStep1Settings.jsx
1535
-
1536
-
1537
- var _icons = require('@sanity/icons');
1538
- var ACCEPTED_EXTENSIONS = ["ttf", "otf", "woff", "woff2", "eot", "svg"];
1539
- var TYPE_ORDER = ["ttf", "otf", "eot", "svg", "woff", "woff2"];
1540
- var filterFontFiles = (files) => Array.from(files).filter((f) => ACCEPTED_EXTENSIONS.includes(f.name.split(".").pop().toLowerCase()));
1541
- var sortFilesByType = (files) => Array.from(files).sort((a, b) => {
1542
- const aIdx = TYPE_ORDER.indexOf(a.name.split(".").pop().toLowerCase());
1543
- const bIdx = TYPE_ORDER.indexOf(b.name.split(".").pop().toLowerCase());
1544
- if (aIdx === bIdx) return a.name.localeCompare(b.name);
1545
- return aIdx - bIdx;
1546
- });
1547
- function UploadStep1Settings({ settings, onStartProcessing }) {
1548
- const [pendingFiles, setPendingFiles] = _react.useState.call(void 0, []);
1549
- const [isDragging, setIsDragging] = _react.useState.call(void 0, false);
1550
- const [filterType, setFilterType] = _react.useState.call(void 0, null);
1551
- const fileInputRef = _react.useRef.call(void 0, null);
1552
- const handleFileSelect = _react.useCallback.call(void 0, (e) => {
1553
- const files = filterFontFiles(e.target.files);
1554
- if (files.length > 0) setPendingFiles((prev) => [...prev, ...files]);
1555
- e.target.value = "";
1556
- }, []);
1557
- const handleRemoveFile = _react.useCallback.call(void 0, (file) => {
1558
- setPendingFiles((prev) => prev.filter((f) => f !== file));
1559
- }, []);
1560
- const handleDragEnter = _react.useCallback.call(void 0, (e) => {
1561
- e.preventDefault();
1562
- setIsDragging(true);
1563
- }, []);
1564
- const handleDragOver = _react.useCallback.call(void 0, (e) => {
1565
- e.preventDefault();
1566
- }, []);
1567
- const handleDragLeave = _react.useCallback.call(void 0, (e) => {
1568
- e.preventDefault();
1569
- setIsDragging(false);
1570
- }, []);
1571
- const handleDrop = _react.useCallback.call(void 0, (e) => {
1572
- e.preventDefault();
1573
- setIsDragging(false);
1574
- const files = filterFontFiles(e.dataTransfer.files);
1575
- if (files.length > 0) setPendingFiles((prev) => [...prev, ...files]);
1576
- }, []);
1577
- const handleProcess = _react.useCallback.call(void 0, () => {
1578
- const sorted = sortFilesByType(pendingFiles);
1579
- onStartProcessing(sorted, settings);
1580
- }, [pendingFiles, settings, onStartProcessing]);
1581
- const typeBreakdown = _react.useMemo.call(void 0, () => {
1582
- const counts = {};
1583
- pendingFiles.forEach((f) => {
1584
- const ext = f.name.split(".").pop().toLowerCase();
1585
- counts[ext] = (counts[ext] || 0) + 1;
1586
- });
1587
- const values = Object.values(counts);
1588
- if (values.length <= 1) return { counts, modeCount: 0, outlierExts: /* @__PURE__ */ new Set() };
1589
- const freq = {};
1590
- values.forEach((v) => {
1591
- freq[v] = (freq[v] || 0) + 1;
1592
- });
1593
- const modeCount = Number(Object.entries(freq).sort((a, b) => b[1] - a[1])[0][0]);
1594
- const outlierExts = /* @__PURE__ */ new Set();
1595
- Object.entries(counts).forEach(([ext, count]) => {
1596
- if (count !== modeCount) outlierExts.add(ext);
1597
- });
1598
- return { counts, modeCount, outlierExts };
1599
- }, [pendingFiles]);
1600
- const displayedFiles = _react.useMemo.call(void 0, () => {
1601
- if (!filterType) return pendingFiles;
1602
- return pendingFiles.filter((f) => f.name.split(".").pop().toLowerCase() === filterType);
1603
- }, [pendingFiles, filterType]);
1604
- const dropZoneStyle = {
1605
- border: `2px dashed ${isDragging ? "var(--card-focus-ring-color)" : "var(--card-border-color)"}`,
1606
- borderRadius: 4,
1607
- padding: pendingFiles.length > 0 ? "10px 16px" : "28px 16px",
1608
- textAlign: "center",
1609
- background: isDragging ? "rgba(100, 153, 255, 0.06)" : "transparent",
1610
- transition: "border-color 0.12s, background 0.12s"
1611
- };
1612
- return /* @__PURE__ */ _react2.default.createElement(_ui.Stack, { space: 4 }, /* @__PURE__ */ _react2.default.createElement(
1613
- _ui.Box,
1614
- {
1615
- onDragEnter: handleDragEnter,
1616
- onDragOver: handleDragOver,
1617
- onDragLeave: handleDragLeave,
1618
- onDrop: handleDrop,
1619
- style: dropZoneStyle
1620
- },
1621
- /* @__PURE__ */ _react2.default.createElement(
1622
- "input",
1623
- {
1624
- ref: fileInputRef,
1625
- type: "file",
1626
- multiple: true,
1627
- hidden: true,
1628
- accept: ".ttf,.otf,.woff,.woff2,.eot,.svg",
1629
- onChange: handleFileSelect
1630
- }
1631
- ),
1632
- pendingFiles.length === 0 ? /* @__PURE__ */ _react2.default.createElement(_ui.Stack, { space: 3 }, /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 1, muted: true }, isDragging ? "Release to add files" : "Drop font files here"), /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { justify: "center" }, /* @__PURE__ */ _react2.default.createElement(
1633
- _ui.Button,
1634
- {
1635
- mode: "ghost",
1636
- tone: "primary",
1637
- fontSize: 1,
1638
- padding: 2,
1639
- text: "Browse files",
1640
- onClick: () => {
1641
- var _a;
1642
- return (_a = fileInputRef.current) == null ? void 0 : _a.click();
1643
- }
1644
- }
1645
- ))) : /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { align: "center", justify: "center", gap: 2 }, /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 1, muted: true }, isDragging ? "Release to add" : "Drop more files or"), /* @__PURE__ */ _react2.default.createElement(
1646
- _ui.Button,
1647
- {
1648
- mode: "bleed",
1649
- tone: "primary",
1650
- fontSize: 1,
1651
- padding: 1,
1652
- text: "browse",
1653
- onClick: () => {
1654
- var _a;
1655
- return (_a = fileInputRef.current) == null ? void 0 : _a.click();
1656
- }
1657
- }
1658
- ))
1659
- ), pendingFiles.length > 0 && /* @__PURE__ */ _react2.default.createElement(_ui.Stack, { space: 3 }, /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { align: "center", justify: "space-between" }, /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { align: "center", gap: 2 }, /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 1, weight: "semibold" }, filterType ? `${displayedFiles.length} of ${pendingFiles.length} files (${filterType.toUpperCase()})` : `${pendingFiles.length} file${pendingFiles.length === 1 ? "" : "s"}`), /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { gap: 1 }, TYPE_ORDER.filter((ext) => typeBreakdown.counts[ext]).map((ext) => {
1660
- const count = typeBreakdown.counts[ext];
1661
- const isOutlier = typeBreakdown.outlierExts.has(ext);
1662
- const isActive = filterType === ext;
1663
- return /* @__PURE__ */ _react2.default.createElement(
1664
- _ui.Badge,
1665
- {
1666
- key: ext,
1667
- tone: isOutlier ? "critical" : isActive ? "primary" : "default",
1668
- mode: isActive ? "default" : isOutlier ? "default" : "outline",
1669
- fontSize: 0,
1670
- style: { cursor: "pointer" },
1671
- onClick: () => setFilterType(isActive ? null : ext)
1672
- },
1673
- count,
1674
- " ",
1675
- ext.toUpperCase()
1676
- );
1677
- }), filterType && /* @__PURE__ */ _react2.default.createElement(
1678
- _ui.Badge,
1679
- {
1680
- mode: "outline",
1681
- fontSize: 0,
1682
- style: { cursor: "pointer" },
1683
- onClick: () => setFilterType(null)
1684
- },
1685
- "Clear filter"
1686
- ))), /* @__PURE__ */ _react2.default.createElement(
1687
- _ui.Button,
1688
- {
1689
- mode: "bleed",
1690
- tone: "default",
1691
- fontSize: 1,
1692
- padding: 1,
1693
- text: "Clear all",
1694
- onClick: () => setPendingFiles([])
1695
- }
1696
- )), /* @__PURE__ */ _react2.default.createElement(_ui.Box, { style: { maxHeight: 350, overflowY: "auto" } }, /* @__PURE__ */ _react2.default.createElement(
1697
- _ui.Flex,
1698
- {
1699
- align: "center",
1700
- gap: 2,
1701
- paddingX: 2,
1702
- paddingY: 1,
1703
- style: { borderBottom: "1px solid var(--card-border-color)" }
1704
- },
1705
- /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 0, weight: "semibold", muted: true, style: { width: 56, flexShrink: 0 } }, "Type"),
1706
- /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 0, weight: "semibold", muted: true, style: { flex: 1 } }, "File Name"),
1707
- /* @__PURE__ */ _react2.default.createElement(_ui.Box, { style: { width: 32 } })
1708
- ), /* @__PURE__ */ _react2.default.createElement(_ui.Stack, { space: 0 }, displayedFiles.map((file, i) => {
1709
- const ext = file.name.split(".").pop().toLowerCase();
1710
- return /* @__PURE__ */ _react2.default.createElement(
1711
- _ui.Flex,
1712
- {
1713
- key: `${file.name}-${file.size}-${i}`,
1714
- align: "center",
1715
- gap: 2,
1716
- paddingX: 2,
1717
- paddingY: 2,
1718
- style: {
1719
- borderBottom: "1px solid var(--card-border-color)"
1720
- }
1721
- },
1722
- /* @__PURE__ */ _react2.default.createElement(
1723
- _ui.Badge,
1724
- {
1725
- tone: "primary",
1726
- mode: "outline",
1727
- fontSize: 0,
1728
- style: { width: 56, flexShrink: 0, textAlign: "center" }
1729
- },
1730
- ext.toUpperCase()
1731
- ),
1732
- /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 1, style: { flex: 1 } }, file.name),
1733
- /* @__PURE__ */ _react2.default.createElement(
1734
- _ui.Button,
1735
- {
1736
- mode: "bleed",
1737
- tone: "critical",
1738
- icon: _icons.TrashIcon,
1739
- padding: 1,
1740
- onClick: () => handleRemoveFile(file),
1741
- style: { flexShrink: 0 }
1742
- }
1743
- )
1744
- );
1745
- })))), pendingFiles.length > 0 && /* @__PURE__ */ _react2.default.createElement(
1746
- _ui.Button,
1747
- {
1748
- mode: "default",
1749
- tone: "primary",
1750
- icon: _icons.UploadIcon,
1751
- text: `Process ${pendingFiles.length} File${pendingFiles.length === 1 ? "" : "s"}`,
1752
- style: { width: "100%" },
1753
- fontSize: 2,
1754
- padding: 4,
1755
- onClick: handleProcess
1756
- }
1757
- ));
1758
- }
1759
-
1760
- // src/components/UploadStep2Review.jsx
1761
-
1762
-
1763
-
1764
-
1765
- // src/components/FontReviewCard.jsx
1766
-
1767
-
1768
-
1769
-
1770
- // src/components/ExistingDocumentResolver.jsx
1771
-
1772
-
1773
-
1774
- function ExistingDocumentResolver({ decision, tempId, dispatch }) {
1775
- if (!decision) return null;
1776
- const { recommendation, exact, candidates, userChoice, selectedCandidate, lookupFailed } = decision;
1777
- const effectiveAction = userChoice || (recommendation === RECOMMENDATION.USE_EXACT || recommendation === RECOMMENDATION.USE_CANDIDATE ? "update" : "create");
1778
- const isUpdating = effectiveAction === "update";
1779
- const hasMatch = exact || (candidates == null ? void 0 : candidates.length) > 0;
1780
- const handleToggle = () => {
1781
- dispatch({ type: "SET_FONT_ACTION", tempId, decision: isUpdating ? "create" : "update" });
1782
- };
1783
- const handleSelectCandidate = (candidate) => {
1784
- dispatch({ type: "SET_FONT_CANDIDATE", tempId, candidate });
1785
- };
1786
- if (lookupFailed) {
1787
- return /* @__PURE__ */ _react2.default.createElement(_ui.Card, { tone: "caution", border: true, padding: 2, radius: 1 }, /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 0 }, "Could not check for existing documents \u2014 will create new."));
1788
- }
1789
- if (recommendation === RECOMMENDATION.CREATE && !userChoice) {
1790
- return /* @__PURE__ */ _react2.default.createElement(_ui.Stack, { space: 2 }, /* @__PURE__ */ _react2.default.createElement(_ui.Label, { size: 0 }, "Existing Document"), /* @__PURE__ */ _react2.default.createElement(_ui.Card, { tone: "default", border: true, padding: 2, radius: 1 }, /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 1 }, "No existing document found \u2014 will create new.")));
1791
- }
1792
- if (hasMatch) {
1793
- const matchDoc = exact || selectedCandidate || (candidates == null ? void 0 : candidates[0]);
1794
- const matchType = exact ? "Exact Match" : (candidates == null ? void 0 : candidates.length) > 1 ? "Multiple Matches" : "Likely Match";
1795
- const matchTone = exact ? "positive" : "caution";
1796
- return /* @__PURE__ */ _react2.default.createElement(_ui.Stack, { space: 2 }, /* @__PURE__ */ _react2.default.createElement(_ui.Label, { size: 0 }, "Existing Document"), /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { align: "center", gap: 2 }, /* @__PURE__ */ _react2.default.createElement(
1797
- _ui.Switch,
1798
- {
1799
- checked: isUpdating,
1800
- onChange: handleToggle,
1801
- style: { cursor: "pointer" }
1802
- }
1803
- ), /* @__PURE__ */ _react2.default.createElement(_ui.Stack, { space: 1 }, /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 1, weight: "semibold" }, isUpdating ? "Update existing document" : "Create new document"), /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 0, muted: true }, isUpdating ? "Files will be uploaded to the matched document below." : "A new document will be created. You may need to update the Document ID above to avoid conflicts."))), /* @__PURE__ */ _react2.default.createElement(
1804
- _ui.Card,
1805
- {
1806
- tone: isUpdating ? matchTone : "default",
1807
- border: true,
1808
- padding: 2,
1809
- radius: 1,
1810
- style: { opacity: isUpdating ? 1 : 0.5, transition: "opacity 0.15s ease" }
1811
- },
1812
- /* @__PURE__ */ _react2.default.createElement(_ui.Stack, { space: 2 }, /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { align: "center", gap: 2 }, /* @__PURE__ */ _react2.default.createElement(_ui.Badge, { tone: isUpdating ? matchTone : "default", fontSize: 0 }, matchType), /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 1, style: { fontFamily: "monospace" } }, matchDoc == null ? void 0 : matchDoc._id)), /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 0, muted: true }, matchDoc == null ? void 0 : matchDoc.title, " \xB7 ", matchDoc == null ? void 0 : matchDoc.weightName, " \xB7 ", matchDoc == null ? void 0 : matchDoc.style, (matchDoc == null ? void 0 : matchDoc.subfamily) ? ` \xB7 ${matchDoc.subfamily}` : ""))
1813
- ), isUpdating && (candidates == null ? void 0 : candidates.length) > 1 && /* @__PURE__ */ _react2.default.createElement(_ui.Stack, { space: 1 }, /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 0, muted: true }, "Select which document to update:"), candidates.map((candidate) => /* @__PURE__ */ _react2.default.createElement(
1814
- _ui.Card,
1815
- {
1816
- key: candidate._id,
1817
- border: true,
1818
- radius: 1,
1819
- padding: 2,
1820
- tone: (selectedCandidate == null ? void 0 : selectedCandidate._id) === candidate._id ? "positive" : "default",
1821
- style: { cursor: "pointer" },
1822
- onClick: () => handleSelectCandidate(candidate)
1823
- },
1824
- /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { align: "center", gap: 2 }, /* @__PURE__ */ _react2.default.createElement(
1825
- "input",
1826
- {
1827
- type: "radio",
1828
- name: `candidate-${tempId}`,
1829
- checked: (selectedCandidate == null ? void 0 : selectedCandidate._id) === candidate._id,
1830
- onChange: () => handleSelectCandidate(candidate),
1831
- style: { cursor: "pointer" }
1832
- }
1833
- ), /* @__PURE__ */ _react2.default.createElement(_ui.Stack, { space: 1, style: { flex: 1 } }, /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 1, style: { fontFamily: "monospace" } }, candidate._id), /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 0, muted: true }, candidate.title, " \xB7 ", candidate.weightName, " \xB7 ", candidate.style, candidate.subfamily ? ` \xB7 ${candidate.subfamily}` : "")))
1834
- ))));
1835
- }
1836
- if (userChoice === "create") {
1837
- return /* @__PURE__ */ _react2.default.createElement(_ui.Stack, { space: 2 }, /* @__PURE__ */ _react2.default.createElement(_ui.Label, { size: 0 }, "Existing Document"), /* @__PURE__ */ _react2.default.createElement(_ui.Card, { border: true, padding: 2, radius: 1 }, /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 1 }, "Will create new document")));
1838
- }
1839
- return null;
1840
- }
1841
-
1842
- // src/components/FontReviewCard.jsx
1843
- var STANDARD_TYPES = ["ttf", "otf", "woff", "woff2"];
1844
- var EXTENDED_TYPES = ["eot", "svg", "css", "woff2_subset", "woff2_web"];
1845
- var FontReviewCard = _react.memo.call(void 0, function FontReviewCard2({ entry, dispatch, allExpanded, typefaceTitle, price }) {
1846
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o;
1847
- const [expanded, setExpanded] = _react.useState.call(void 0, false);
1848
- const [showAllFileTypes, setShowAllFileTypes] = _react.useState.call(void 0, false);
1849
- const [showDocPreview, setShowDocPreview] = _react.useState.call(void 0, false);
1850
- _react.useEffect.call(void 0, () => {
1851
- setExpanded(allExpanded);
1852
- }, [allExpanded]);
1853
- const [localTitle, setLocalTitle] = _react.useState.call(void 0, entry.title);
1854
- const [localWeight, setLocalWeight] = _react.useState.call(void 0, String(entry.weight));
1855
- const [localWeightName, setLocalWeightName] = _react.useState.call(void 0, entry.weightName);
1856
- const [localSubfamily, setLocalSubfamily] = _react.useState.call(void 0, entry.subfamily);
1857
- const [localDocId, setLocalDocId] = _react.useState.call(void 0, entry.documentId);
1858
- const isError = entry.status === FONT_STATUS.ERROR;
1859
- const hasConflict = entry._idConflict;
1860
- const resolution = (_a = entry.decisions) == null ? void 0 : _a.existingDocument;
1861
- const isUpdate = (resolution == null ? void 0 : resolution.userChoice) === "update" || !(resolution == null ? void 0 : resolution.userChoice) && ((resolution == null ? void 0 : resolution.recommendation) === RECOMMENDATION.USE_EXACT || (resolution == null ? void 0 : resolution.recommendation) === RECOMMENDATION.USE_CANDIDATE);
1862
- const isCreateNewOverride = (resolution == null ? void 0 : resolution.userChoice) === "create" && ((resolution == null ? void 0 : resolution.exact) || ((_b = resolution == null ? void 0 : resolution.candidates) == null ? void 0 : _b.length) > 0);
1863
- const docIdEditable = isCreateNewOverride || hasConflict;
1864
- const hasUserOverrides = _react.useMemo.call(void 0, () => {
1865
- var _a2, _b2, _c2, _d2, _e2, _f2;
1866
- const d = entry.decisions;
1867
- if (!d) return false;
1868
- return ((_a2 = d.title) == null ? void 0 : _a2.userOverride) != null || ((_b2 = d.weight) == null ? void 0 : _b2.userOverride) != null || ((_c2 = d.weightName) == null ? void 0 : _c2.userOverride) != null || ((_d2 = d.style) == null ? void 0 : _d2.userOverride) != null || ((_e2 = d.subfamily) == null ? void 0 : _e2.userOverride) != null || ((_f2 = d.documentId) == null ? void 0 : _f2.userOverride) != null;
1869
- }, [entry.decisions]);
1870
- const fileExtMap = _react.useMemo.call(void 0, () => {
1871
- const map = {};
1872
- (entry.files || []).forEach((f) => {
1873
- var _a2, _b2;
1874
- const ext = (_b2 = (_a2 = f.name) == null ? void 0 : _a2.split(".").pop()) == null ? void 0 : _b2.toLowerCase();
1875
- if (ext) map[ext] = f.name;
1876
- });
1877
- return map;
1878
- }, [entry.files]);
1879
- const fileCount = ((_c = entry.files) == null ? void 0 : _c.length) || 0;
1880
- const cardTone = isError ? "critical" : hasConflict ? "caution" : "default";
1881
- const handleTitleBlur = _react.useCallback.call(void 0, () => {
1882
- if (localTitle !== entry.title) {
1883
- dispatch({ type: "SET_FONT_TITLE", tempId: entry.tempId, title: localTitle });
1884
- }
1885
- }, [localTitle, entry.title, entry.tempId, dispatch]);
1886
- const handleDocIdBlur = _react.useCallback.call(void 0, () => {
1887
- if (localDocId !== entry.documentId) {
1888
- dispatch({ type: "SET_FONT_DOCUMENT_ID", tempId: entry.tempId, documentId: localDocId });
1889
- }
1890
- }, [localDocId, entry.documentId, entry.tempId, dispatch]);
1891
- const handleWeightBlur = _react.useCallback.call(void 0, () => {
1892
- const num = Number(localWeight);
1893
- if (!isNaN(num) && num !== entry.weight) {
1894
- dispatch({ type: "SET_FONT_WEIGHT", tempId: entry.tempId, weight: num });
1895
- }
1896
- }, [localWeight, entry.weight, entry.tempId, dispatch]);
1897
- const handleWeightNameBlur = _react.useCallback.call(void 0, () => {
1898
- if (localWeightName !== entry.weightName) {
1899
- dispatch({ type: "SET_FONT_WEIGHT_NAME", tempId: entry.tempId, weightName: localWeightName });
1900
- }
1901
- }, [localWeightName, entry.weightName, entry.tempId, dispatch]);
1902
- const handleStyleChange = _react.useCallback.call(void 0, (e) => {
1903
- dispatch({ type: "SET_FONT_STYLE", tempId: entry.tempId, style: e.target.value });
1904
- }, [entry.tempId, dispatch]);
1905
- const handleSubfamilyBlur = _react.useCallback.call(void 0, () => {
1906
- if (localSubfamily !== entry.subfamily) {
1907
- dispatch({ type: "SET_FONT_SUBFAMILY", tempId: entry.tempId, subfamily: localSubfamily });
1908
- }
1909
- }, [localSubfamily, entry.subfamily, entry.tempId, dispatch]);
1910
- const handleReset = _react.useCallback.call(void 0, () => {
1911
- dispatch({ type: "RESET_FONT_TO_SUGGESTIONS", tempId: entry.tempId });
1912
- setLocalTitle(entry.decisions.title.processed);
1913
- setLocalDocId(entry.decisions.documentId.generated);
1914
- setLocalWeight(String(entry.decisions.weight.detected));
1915
- setLocalWeightName(entry.decisions.weightName.detected);
1916
- setLocalSubfamily(entry.decisions.subfamily.detected || "Regular");
1917
- }, [entry, dispatch]);
1918
- const handleRemove = _react.useCallback.call(void 0, () => {
1919
- dispatch({ type: "REMOVE_FONT", tempId: entry.tempId });
1920
- }, [entry.tempId, dispatch]);
1921
- const formatSource = (decision) => {
1922
- if (!decision) return null;
1923
- let src = decision.source || "";
1924
- src = src.replace(/nameId(\d+)-(\w+)/g, "nameId$1 ($2)");
1925
- src = src.replace(/-/g, " ").replace("fontkit ", "");
1926
- if (src === "default empty" && decision.detected === "") {
1927
- src = 'empty \u2014 defaults to "Regular"';
1928
- }
1929
- const reason = decision.reason ? ` (${decision.reason})` : "";
1930
- const override = decision.userOverride != null ? " (user override)" : "";
1931
- return `Source: ${src}${reason}${override}`;
1932
- };
1933
- return /* @__PURE__ */ _react2.default.createElement(_ui.Card, { border: true, radius: 2, tone: cardTone, style: { marginBottom: -1 } }, /* @__PURE__ */ _react2.default.createElement(
1934
- _ui.Box,
1935
- {
1936
- as: "button",
1937
- onClick: () => !isError && setExpanded((v) => !v),
1938
- style: {
1939
- width: "100%",
1940
- background: expanded ? "var(--card-muted-bg-color)" : "none",
1941
- border: "none",
1942
- cursor: isError ? "default" : "pointer",
1943
- textAlign: "left",
1944
- padding: 0,
1945
- transition: "background 0.1s ease"
1946
- },
1947
- onMouseEnter: (e) => {
1948
- if (!isError && !expanded) e.currentTarget.style.background = "var(--card-muted-bg-color)";
1949
- },
1950
- onMouseLeave: (e) => {
1951
- if (!expanded) e.currentTarget.style.background = "none";
1952
- }
1953
- },
1954
- /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { align: "center", gap: 2, paddingX: 2, paddingY: 2 }, /* @__PURE__ */ _react2.default.createElement(_ui.Box, { style: { width: 20, flexShrink: 0 } }, !isError && (expanded ? /* @__PURE__ */ _react2.default.createElement(_icons.ChevronDownIcon, null) : /* @__PURE__ */ _react2.default.createElement(_icons.ChevronRightIcon, null))), /* @__PURE__ */ _react2.default.createElement(_ui.Box, { style: { flex: 1, whiteSpace: "nowrap" } }, /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 1, weight: "semibold", style: { whiteSpace: "nowrap" } }, entry.title || entry.sourceFileName, entry.variableFont && /* @__PURE__ */ _react2.default.createElement(_ui.Badge, { tone: "primary", fontSize: 0, style: { marginLeft: 6 } }, "VF"), hasConflict && /* @__PURE__ */ _react2.default.createElement(_ui.Badge, { tone: "caution", fontSize: 0, style: { marginLeft: 6 } }, "ID Conflict"))), /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 0, style: { width: 50, textAlign: "center", flexShrink: 0 } }, entry.weight), /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 0, style: { width: 50, textAlign: "center", flexShrink: 0 } }, entry.style), /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 0, style: { width: 40, textAlign: "center", flexShrink: 0 } }, fileCount), /* @__PURE__ */ _react2.default.createElement(_ui.Box, { style: { width: 55, textAlign: "center", flexShrink: 0 } }, /* @__PURE__ */ _react2.default.createElement(_ui.Badge, { tone: isError ? "critical" : isUpdate ? "caution" : "positive", fontSize: 0 }, isError ? "Error" : isUpdate ? "Update" : "Create")))
1955
- ), isError && /* @__PURE__ */ _react2.default.createElement(_ui.Box, { paddingX: 2, paddingBottom: 2 }, /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { justify: "space-between", align: "center" }, /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 0, muted: true }, entry.error), /* @__PURE__ */ _react2.default.createElement(_ui.Button, { mode: "bleed", tone: "critical", icon: _icons.TrashIcon, padding: 1, onClick: handleRemove }))), expanded && !isError && /* @__PURE__ */ _react2.default.createElement(_ui.Box, { padding: 3, style: { borderTop: "1px solid var(--card-border-color)", background: "var(--card-muted-bg-color)" } }, /* @__PURE__ */ _react2.default.createElement(_ui.Stack, { space: 4 }, /* @__PURE__ */ _react2.default.createElement(_ui.Stack, { space: 2 }, /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { align: "center", gap: 2 }, /* @__PURE__ */ _react2.default.createElement(_ui.Label, { size: 0 }, "Files (", fileCount, ")"), /* @__PURE__ */ _react2.default.createElement(
1956
- _ui.Button,
1957
- {
1958
- mode: "bleed",
1959
- fontSize: 0,
1960
- padding: 1,
1961
- text: showAllFileTypes ? "Hide extended types" : "Show all types",
1962
- onClick: () => setShowAllFileTypes((v) => !v),
1963
- style: { cursor: "pointer" }
1964
- }
1965
- )), /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { gap: 1, wrap: "wrap" }, STANDARD_TYPES.map((ext) => /* @__PURE__ */ _react2.default.createElement(
1966
- _ui.Badge,
1967
- {
1968
- key: ext,
1969
- fontSize: 0,
1970
- tone: fileExtMap[ext] ? "primary" : "default",
1971
- mode: fileExtMap[ext] ? "default" : "outline",
1972
- style: { opacity: fileExtMap[ext] ? 1 : 0.35 }
1973
- },
1974
- ext.toUpperCase(),
1975
- fileExtMap[ext] ? `: ${fileExtMap[ext]}` : ""
1976
- )), showAllFileTypes && EXTENDED_TYPES.map((ext) => /* @__PURE__ */ _react2.default.createElement(
1977
- _ui.Badge,
1978
- {
1979
- key: ext,
1980
- fontSize: 0,
1981
- tone: fileExtMap[ext] ? "primary" : "default",
1982
- mode: fileExtMap[ext] ? "default" : "outline",
1983
- style: { opacity: fileExtMap[ext] ? 1 : 0.35 }
1984
- },
1985
- ext.toUpperCase(),
1986
- fileExtMap[ext] ? `: ${fileExtMap[ext]}` : ""
1987
- )))), /* @__PURE__ */ _react2.default.createElement(_ui.Stack, { space: 2 }, /* @__PURE__ */ _react2.default.createElement(_ui.Label, { size: 0 }, "Font Title"), /* @__PURE__ */ _react2.default.createElement(
1988
- _ui.TextInput,
1989
- {
1990
- value: localTitle,
1991
- onChange: (e) => setLocalTitle(e.target.value),
1992
- onBlur: handleTitleBlur,
1993
- fontSize: 1
1994
- }
1995
- ), /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 0, muted: true }, formatSource((_d = entry.decisions) == null ? void 0 : _d.title)), ((_g = (_f = (_e = entry.decisions) == null ? void 0 : _e.title) == null ? void 0 : _f.alternatives) == null ? void 0 : _g.length) > 0 && /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { gap: 1, wrap: "wrap" }, entry.decisions.title.alternatives.filter((a) => a.value).map((alt, i) => /* @__PURE__ */ _react2.default.createElement(_ui.Tooltip, { key: i, content: /* @__PURE__ */ _react2.default.createElement(_ui.Box, { padding: 1 }, /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 0 }, alt.source.replace(/nameId(\d+)-(\w+)/g, "nameId$1 ($2)"))), portal: true }, /* @__PURE__ */ _react2.default.createElement(
1996
- _ui.Badge,
1997
- {
1998
- mode: "outline",
1999
- fontSize: 0,
2000
- style: { cursor: "pointer" },
2001
- onClick: () => {
2002
- setLocalTitle(alt.value);
2003
- dispatch({ type: "SET_FONT_TITLE", tempId: entry.tempId, title: alt.value, source: alt.source });
2004
- }
2005
- },
2006
- alt.value.length > 30 ? alt.value.slice(0, 30) + "..." : alt.value
2007
- ))))), /* @__PURE__ */ _react2.default.createElement(_ui.Stack, { space: 2 }, /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { align: "center", gap: 1 }, /* @__PURE__ */ _react2.default.createElement(_ui.Label, { size: 0 }, "Document ID"), !docIdEditable && /* @__PURE__ */ _react2.default.createElement(
2008
- _ui.Tooltip,
2009
- {
2010
- content: /* @__PURE__ */ _react2.default.createElement(_ui.Box, { padding: 2, style: { maxWidth: 260 } }, /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 1, style: { lineHeight: 1.6 } }, 'Document IDs must be unique. This field is auto-derived from the font title. It becomes editable when you choose "Create new instead" on a font with an existing match, or when there is a duplicate ID conflict.')),
2011
- placement: "top",
2012
- portal: true
2013
- },
2014
- /* @__PURE__ */ _react2.default.createElement(_icons.InfoOutlineIcon, { style: { opacity: 0.4, fontSize: 12 } })
2015
- )), /* @__PURE__ */ _react2.default.createElement(
2016
- _ui.TextInput,
2017
- {
2018
- value: docIdEditable ? localDocId : entry.documentId,
2019
- onChange: docIdEditable ? (e) => setLocalDocId(e.target.value) : void 0,
2020
- onBlur: docIdEditable ? handleDocIdBlur : void 0,
2021
- readOnly: !docIdEditable,
2022
- fontSize: 1,
2023
- style: { fontFamily: "monospace", opacity: docIdEditable ? 1 : 0.7 }
2024
- }
2025
- ), hasConflict && /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 0, tone: "caution" }, "This ID conflicts with another font in this batch \u2014 edit to make unique"), isCreateNewOverride && !hasConflict && /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 0, tone: "caution" }, "Creating new document \u2014 edit the ID to avoid overwriting the existing document"), !docIdEditable && /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 0, muted: true }, "Auto-derived from font title")), /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { gap: 3 }, /* @__PURE__ */ _react2.default.createElement(_ui.Box, { style: { flex: 1 } }, /* @__PURE__ */ _react2.default.createElement(_ui.Stack, { space: 2 }, /* @__PURE__ */ _react2.default.createElement(_ui.Label, { size: 0 }, "Weight"), /* @__PURE__ */ _react2.default.createElement(
2026
- _ui.TextInput,
2027
- {
2028
- type: "number",
2029
- value: localWeight,
2030
- onChange: (e) => setLocalWeight(e.target.value),
2031
- onBlur: handleWeightBlur,
2032
- fontSize: 1
2033
- }
2034
- ), ((_h = entry.decisions) == null ? void 0 : _h.weight) && /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 0, muted: true }, formatSource(entry.decisions.weight)))), /* @__PURE__ */ _react2.default.createElement(_ui.Box, { style: { flex: 1 } }, /* @__PURE__ */ _react2.default.createElement(_ui.Stack, { space: 2 }, /* @__PURE__ */ _react2.default.createElement(_ui.Label, { size: 0 }, "Weight Name"), /* @__PURE__ */ _react2.default.createElement(
2035
- _ui.TextInput,
2036
- {
2037
- value: localWeightName,
2038
- onChange: (e) => setLocalWeightName(e.target.value),
2039
- onBlur: handleWeightNameBlur,
2040
- fontSize: 1
2041
- }
2042
- ), ((_i = entry.decisions) == null ? void 0 : _i.weightName) && /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 0, muted: true }, formatSource(entry.decisions.weightName))))), /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { gap: 3 }, /* @__PURE__ */ _react2.default.createElement(_ui.Box, { style: { flex: 1 } }, /* @__PURE__ */ _react2.default.createElement(_ui.Stack, { space: 2 }, /* @__PURE__ */ _react2.default.createElement(_ui.Label, { size: 0 }, "Style"), /* @__PURE__ */ _react2.default.createElement(_ui.Select, { value: entry.style, onChange: handleStyleChange, fontSize: 1 }, /* @__PURE__ */ _react2.default.createElement("option", { value: "Regular" }, "Regular"), /* @__PURE__ */ _react2.default.createElement("option", { value: "Italic" }, "Italic")), ((_j = entry.decisions) == null ? void 0 : _j.style) && /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 0, muted: true }, formatSource(entry.decisions.style)))), /* @__PURE__ */ _react2.default.createElement(_ui.Box, { style: { flex: 1 } }, /* @__PURE__ */ _react2.default.createElement(_ui.Stack, { space: 2 }, /* @__PURE__ */ _react2.default.createElement(_ui.Label, { size: 0 }, "Subfamily"), /* @__PURE__ */ _react2.default.createElement(
2043
- _ui.TextInput,
2044
- {
2045
- value: localSubfamily,
2046
- onChange: (e) => setLocalSubfamily(e.target.value),
2047
- onBlur: handleSubfamilyBlur,
2048
- fontSize: 1
2049
- }
2050
- ), ((_k = entry.decisions) == null ? void 0 : _k.subfamily) && /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 0, muted: true }, formatSource(entry.decisions.subfamily))))), entry.variableFont && entry.variationAxes && /* @__PURE__ */ _react2.default.createElement(_ui.Stack, { space: 2 }, /* @__PURE__ */ _react2.default.createElement(_ui.Label, { size: 0 }, "Variable Font Axes"), /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { gap: 1, wrap: "wrap" }, Object.entries(entry.variationAxes).map(([tag, axis]) => /* @__PURE__ */ _react2.default.createElement(_ui.Badge, { key: tag, mode: "outline", fontSize: 0 }, tag, " ", axis.min, "\u2013", axis.max))), ((_m = (_l = entry.decisions) == null ? void 0 : _l.weight) == null ? void 0 : _m.userOverride) != null && ((_n = entry.variationAxes) == null ? void 0 : _n.wght) && ((entry.weight < entry.variationAxes.wght.min || entry.weight > entry.variationAxes.wght.max) && /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 0, tone: "caution" }, "Weight ", entry.weight, " is outside the wght axis range (", entry.variationAxes.wght.min, "\u2013", entry.variationAxes.wght.max, ")"))), /* @__PURE__ */ _react2.default.createElement(
2051
- ExistingDocumentResolver,
2052
- {
2053
- decision: (_o = entry.decisions) == null ? void 0 : _o.existingDocument,
2054
- tempId: entry.tempId,
2055
- dispatch
2056
- }
2057
- ), /* @__PURE__ */ _react2.default.createElement(_ui.Stack, { space: 2 }, /* @__PURE__ */ _react2.default.createElement(
2058
- _ui.Button,
2059
- {
2060
- mode: "bleed",
2061
- fontSize: 0,
2062
- padding: 1,
2063
- text: showDocPreview ? "Hide document preview" : "Show document preview",
2064
- onClick: () => setShowDocPreview((v) => !v),
2065
- style: { cursor: "pointer", alignSelf: "flex-start" }
2066
- }
2067
- ), showDocPreview && /* @__PURE__ */ _react2.default.createElement(_ui.Card, { border: true, padding: 3, radius: 1, style: { fontFamily: "monospace", fontSize: 12 } }, /* @__PURE__ */ _react2.default.createElement(_ui.Stack, { space: 2 }, [
2068
- ["_id", entry.documentId],
2069
- ["_type", "font"],
2070
- ["title", entry.title],
2071
- ["slug", entry.documentId],
2072
- ["typefaceName", typefaceTitle || "\u2014"],
2073
- ["weightName", entry.weightName || "\u2014"],
2074
- ["weight", entry.weight],
2075
- ["style", entry.style],
2076
- ["subfamily", entry.subfamily || "\u2014"],
2077
- ["variableFont", String(entry.variableFont)],
2078
- ["price", _nullishCoalesce(price, () => ( "\u2014"))],
2079
- ["sell", price > 0 ? "true" : "false"],
2080
- ["normalWeight", "true"],
2081
- ["files", (entry.files || []).map((f) => f.name).join(", ") || "\u2014"]
2082
- ].map(([key, value]) => /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { key, gap: 2 }, /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 0, muted: true, style: { width: 120, flexShrink: 0 } }, key), /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 0, style: { wordBreak: "break-all" } }, String(value))))))), /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { justify: "flex-end", gap: 2 }, hasUserOverrides && /* @__PURE__ */ _react2.default.createElement(
2083
- _ui.Button,
2084
- {
2085
- mode: "ghost",
2086
- tone: "default",
2087
- icon: _icons.ResetIcon,
2088
- text: "Reset to Suggestions",
2089
- fontSize: 1,
2090
- padding: 2,
2091
- onClick: handleReset
2092
- }
2093
- ), /* @__PURE__ */ _react2.default.createElement(
2094
- _ui.Button,
2095
- {
2096
- mode: "ghost",
2097
- tone: "critical",
2098
- icon: _icons.TrashIcon,
2099
- text: "Remove",
2100
- fontSize: 1,
2101
- padding: 2,
2102
- onClick: handleRemove
2103
- }
2104
- )))));
2105
- });
2106
- var FontReviewCard_default = FontReviewCard;
2107
-
2108
- // src/components/BulkActions.jsx
2109
-
2110
-
2111
-
2112
- function isUpdateEntry(entry) {
2113
- var _a;
2114
- const d = (_a = entry.decisions) == null ? void 0 : _a.existingDocument;
2115
- const choice = d == null ? void 0 : d.userChoice;
2116
- const rec = d == null ? void 0 : d.recommendation;
2117
- return choice === "update" || !choice && (rec === RECOMMENDATION.USE_EXACT || rec === RECOMMENDATION.USE_CANDIDATE);
2118
- }
2119
- function BulkActions({
2120
- fonts,
2121
- dispatch,
2122
- searchQuery,
2123
- onSearchChange,
2124
- filterBy,
2125
- onFilterChange,
2126
- allExpanded,
2127
- onToggleExpandAll,
2128
- visibleTempIds
2129
- }) {
2130
- const fontEntries = _react.useMemo.call(void 0, () => Object.values(fonts), [fonts]);
2131
- const fontCount = fontEntries.length;
2132
- const visibleCount = visibleTempIds.length;
2133
- const filterCounts = _react.useMemo.call(void 0, () => {
2134
- const createCount = fontEntries.filter((f) => f.status !== FONT_STATUS.ERROR && !isUpdateEntry(f)).length;
2135
- const updateCount = fontEntries.filter((f) => f.status !== FONT_STATUS.ERROR && isUpdateEntry(f)).length;
2136
- const errorCount = fontEntries.filter((f) => f.status === FONT_STATUS.ERROR).length;
2137
- const conflictCount = fontEntries.filter((f) => f._idConflict).length;
2138
- const italicCount = fontEntries.filter((f) => f.style === "Italic" && f.status !== FONT_STATUS.ERROR).length;
2139
- const regularCount = fontEntries.filter((f) => f.style === "Regular" && f.status !== FONT_STATUS.ERROR).length;
2140
- const subfamilyCounts = {};
2141
- fontEntries.forEach((f) => {
2142
- if (f.status === FONT_STATUS.ERROR) return;
2143
- const sf = f.subfamily || "Regular";
2144
- subfamilyCounts[sf] = (subfamilyCounts[sf] || 0) + 1;
2145
- });
2146
- return { createCount, updateCount, errorCount, conflictCount, italicCount, regularCount, subfamilyCounts };
2147
- }, [fontEntries]);
2148
- const subfamilies = _react.useMemo.call(void 0,
2149
- () => Object.keys(filterCounts.subfamilyCounts).sort((a, b) => {
2150
- if (a === "Regular") return -1;
2151
- if (b === "Regular") return 1;
2152
- return a.localeCompare(b);
2153
- }),
2154
- [filterCounts]
2155
- );
2156
- return /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { gap: 2, align: "center", wrap: "wrap", style: { position: "sticky", top: 0, zIndex: 10, background: "var(--card-bg-color)", paddingBottom: 8, paddingTop: 4 } }, /* @__PURE__ */ _react2.default.createElement(_ui.Box, { style: { flex: 1, minWidth: 150 } }, /* @__PURE__ */ _react2.default.createElement(
2157
- _ui.TextInput,
2158
- {
2159
- icon: _icons.SearchIcon,
2160
- placeholder: "Search fonts...",
2161
- value: searchQuery,
2162
- onChange: (e) => onSearchChange(e.target.value),
2163
- fontSize: 1
2164
- }
2165
- )), /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { align: "center", gap: 1 }, /* @__PURE__ */ _react2.default.createElement(_ui.Label, { size: 0, style: { whiteSpace: "nowrap" } }, "Filter"), /* @__PURE__ */ _react2.default.createElement(_ui.Select, { value: filterBy, onChange: (e) => onFilterChange(e.target.value), fontSize: 1, style: { minWidth: 140 } }, /* @__PURE__ */ _react2.default.createElement("option", { value: "all" }, "All (", fontCount, ")"), filterCounts.createCount > 0 && /* @__PURE__ */ _react2.default.createElement("option", { value: "create" }, "Create (", filterCounts.createCount, ")"), filterCounts.updateCount > 0 && /* @__PURE__ */ _react2.default.createElement("option", { value: "update" }, "Update (", filterCounts.updateCount, ")"), filterCounts.regularCount > 0 && /* @__PURE__ */ _react2.default.createElement("option", { value: "style:regular" }, "Regular (", filterCounts.regularCount, ")"), filterCounts.italicCount > 0 && /* @__PURE__ */ _react2.default.createElement("option", { value: "style:italic" }, "Italic (", filterCounts.italicCount, ")"), filterCounts.errorCount > 0 && /* @__PURE__ */ _react2.default.createElement("option", { value: "error" }, "Errors (", filterCounts.errorCount, ")"), filterCounts.conflictCount > 0 && /* @__PURE__ */ _react2.default.createElement("option", { value: "conflict" }, "Conflicts (", filterCounts.conflictCount, ")"), subfamilies.length > 1 && subfamilies.map((sf) => /* @__PURE__ */ _react2.default.createElement("option", { key: sf, value: `sf:${sf}` }, sf, " (", filterCounts.subfamilyCounts[sf], ")")))), visibleCount !== fontCount && /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 0, muted: true }, visibleCount, " of ", fontCount));
2166
- }
2167
-
2168
- // src/components/PriceInput.jsx
2169
-
2170
-
2171
- var PriceInput = ({ inputPrice, handleInputChange }) => /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { align: "center", gap: 2 }, /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 1, muted: true }, "Price:"), /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 1, muted: true }, "$"), /* @__PURE__ */ _react2.default.createElement(
2172
- "input",
2173
- {
2174
- value: inputPrice,
2175
- onChange: handleInputChange,
2176
- type: "number",
2177
- style: { textAlign: "end", padding: "5px", maxWidth: "75px" }
2178
- }
2179
- ), /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 1, muted: true }, "per style"));
2180
- var PriceInput_default = PriceInput;
2181
-
2182
- // src/components/UploadStep2Review.jsx
2183
- function isUpdateEntry2(entry) {
2184
- var _a;
2185
- const d = (_a = entry.decisions) == null ? void 0 : _a.existingDocument;
2186
- const choice = d == null ? void 0 : d.userChoice;
2187
- const rec = d == null ? void 0 : d.recommendation;
2188
- return choice === "update" || !choice && (rec === RECOMMENDATION.USE_EXACT || rec === RECOMMENDATION.USE_CANDIDATE);
2189
- }
2190
- function UploadStep2Review({
2191
- plan,
2192
- dispatch,
2193
- onCancelProcessing,
2194
- onReadyToUpload,
2195
- onStartExecution,
2196
- processingCancelled
2197
- }) {
2198
- var _a, _b, _c;
2199
- const isProcessing = plan.phase === PLAN_PHASE.PROCESSING;
2200
- const isReviewing = plan.phase === PLAN_PHASE.REVIEWING || plan.phase === PLAN_PHASE.READY;
2201
- const [localPrice, setLocalPrice] = _react.useState.call(void 0, String(((_a = plan.settings) == null ? void 0 : _a.price) || 0));
2202
- const [localPreserveShortenedNames, setLocalPreserveShortenedNames] = _react.useState.call(void 0, _nullishCoalesce(((_b = plan.settings) == null ? void 0 : _b.preserveShortenedNames), () => ( true)));
2203
- const [localPreserveFileNames, setLocalPreserveFileNames] = _react.useState.call(void 0, _nullishCoalesce(((_c = plan.settings) == null ? void 0 : _c.preserveFileNames), () => ( false)));
2204
- const [searchQuery, setSearchQuery] = _react.useState.call(void 0, "");
2205
- const [filterBy, setFilterBy] = _react.useState.call(void 0, "all");
2206
- const [allExpanded, setAllExpanded] = _react.useState.call(void 0, false);
2207
- const [sortBy, setSortBy] = _react.useState.call(void 0, "weight");
2208
- const [sortDir, setSortDir] = _react.useState.call(void 0, "asc");
2209
- const fontEntries = _react.useMemo.call(void 0, () => Object.values(plan.fonts), [plan.fonts]);
2210
- const processedCount = fontEntries.filter((f) => f.status === FONT_STATUS.PROCESSED).length;
2211
- const errorCount = fontEntries.filter((f) => f.status === FONT_STATUS.ERROR).length;
2212
- const totalCount = plan.processingProgress.total;
2213
- _react.useEffect.call(void 0, () => {
2214
- if (!isReviewing) return;
2215
- const processed = fontEntries.filter((f) => f.status !== FONT_STATUS.ERROR);
2216
- if (processed.length === 0) return;
2217
- const bySubfamily = {};
2218
- processed.forEach((f) => {
2219
- const sf = f.subfamily || "(none)";
2220
- if (!bySubfamily[sf]) bySubfamily[sf] = [];
2221
- bySubfamily[sf].push(f);
2222
- });
2223
- console.group("[UploadStep2Review] Subfamily assignments");
2224
- Object.entries(bySubfamily).forEach(([sf, fonts]) => {
2225
- console.group(`Subfamily: "${sf}" (${fonts.length} font${fonts.length === 1 ? "" : "s"})`);
2226
- fonts.forEach((f) => {
2227
- var _a2, _b2, _c2, _d, _e;
2228
- console.log(` "${f.title}" (id: ${f.documentId})`, {
2229
- sourceFile: f.sourceFileName,
2230
- weightName: f.weightName,
2231
- style: f.style,
2232
- subfamily: f.subfamily,
2233
- subfamilyDecision: (_a2 = f.decisions) == null ? void 0 : _a2.subfamily,
2234
- parsedMetadata: {
2235
- familyName: (_b2 = f.parsedMetadata) == null ? void 0 : _b2.familyName,
2236
- fullName: (_c2 = f.parsedMetadata) == null ? void 0 : _c2.fullName,
2237
- preferredFamily: (_d = f.parsedMetadata) == null ? void 0 : _d.preferredFamily,
2238
- preferredSubfamily: (_e = f.parsedMetadata) == null ? void 0 : _e.preferredSubfamily
2239
- }
2240
- });
2241
- });
2242
- console.groupEnd();
2243
- });
2244
- console.groupEnd();
2245
- }, [isReviewing, fontEntries]);
2246
- const createCount = _react.useMemo.call(void 0,
2247
- () => fontEntries.filter((f) => f.status !== FONT_STATUS.ERROR && !isUpdateEntry2(f)).length,
2248
- [fontEntries]
2249
- );
2250
- const updateCount = _react.useMemo.call(void 0,
2251
- () => fontEntries.filter((f) => f.status !== FONT_STATUS.ERROR && isUpdateEntry2(f)).length,
2252
- [fontEntries]
2253
- );
2254
- const visibleEntries = _react.useMemo.call(void 0, () => {
2255
- let entries = fontEntries;
2256
- if (filterBy === "create") {
2257
- entries = entries.filter((f) => !isUpdateEntry2(f) && f.status !== FONT_STATUS.ERROR);
2258
- } else if (filterBy === "update") {
2259
- entries = entries.filter((f) => isUpdateEntry2(f));
2260
- } else if (filterBy === "error") {
2261
- entries = entries.filter((f) => f.status === FONT_STATUS.ERROR);
2262
- } else if (filterBy === "conflict") {
2263
- entries = entries.filter((f) => f._idConflict);
2264
- } else if (filterBy === "style:italic") {
2265
- entries = entries.filter((f) => f.style === "Italic" && f.status !== FONT_STATUS.ERROR);
2266
- } else if (filterBy === "style:regular") {
2267
- entries = entries.filter((f) => f.style === "Regular" && f.status !== FONT_STATUS.ERROR);
2268
- } else if (filterBy.startsWith("sf:")) {
2269
- const sf = filterBy.slice(3);
2270
- entries = entries.filter((f) => f.subfamily === sf);
2271
- }
2272
- if (searchQuery.trim()) {
2273
- const q = searchQuery.toLowerCase().trim();
2274
- entries = entries.filter(
2275
- (f) => {
2276
- var _a2, _b2, _c2, _d;
2277
- return ((_a2 = f.title) == null ? void 0 : _a2.toLowerCase().includes(q)) || ((_b2 = f.documentId) == null ? void 0 : _b2.toLowerCase().includes(q)) || ((_c2 = f.sourceFileName) == null ? void 0 : _c2.toLowerCase().includes(q)) || ((_d = f.weightName) == null ? void 0 : _d.toLowerCase().includes(q));
2278
- }
2279
- );
2280
- }
2281
- return entries;
2282
- }, [fontEntries, filterBy, searchQuery]);
2283
- const visibleTempIds = _react.useMemo.call(void 0, () => visibleEntries.map((e) => e.tempId), [visibleEntries]);
2284
- const hasConflicts = fontEntries.some((f) => f._idConflict);
2285
- const sortEntries = _react.useCallback.call(void 0, (a, b) => {
2286
- var _a2, _b2;
2287
- const dir = sortDir === "asc" ? 1 : -1;
2288
- let cmp = 0;
2289
- switch (sortBy) {
2290
- case "title":
2291
- cmp = (a.title || "").localeCompare(b.title || "");
2292
- break;
2293
- case "weight":
2294
- cmp = a.weight - b.weight;
2295
- if (cmp === 0) {
2296
- if (a.style === "Italic" && b.style !== "Italic") cmp = 1;
2297
- else if (a.style !== "Italic" && b.style === "Italic") cmp = -1;
2298
- }
2299
- break;
2300
- case "style":
2301
- cmp = (a.style || "").localeCompare(b.style || "");
2302
- break;
2303
- case "files":
2304
- cmp = (((_a2 = a.files) == null ? void 0 : _a2.length) || 0) - (((_b2 = b.files) == null ? void 0 : _b2.length) || 0);
2305
- break;
2306
- case "action": {
2307
- const aUpdate = isUpdateEntry2(a) ? 1 : 0;
2308
- const bUpdate = isUpdateEntry2(b) ? 1 : 0;
2309
- cmp = aUpdate - bUpdate;
2310
- break;
2311
- }
2312
- default:
2313
- cmp = a.weight - b.weight;
2314
- }
2315
- return cmp * dir;
2316
- }, [sortBy, sortDir]);
2317
- const handleSort = _react.useCallback.call(void 0, (column) => {
2318
- if (sortBy === column) {
2319
- setSortDir((d) => d === "asc" ? "desc" : "asc");
2320
- } else {
2321
- setSortBy(column);
2322
- setSortDir("asc");
2323
- }
2324
- }, [sortBy]);
2325
- const groupedEntries = _react.useMemo.call(void 0, () => {
2326
- const groups = {};
2327
- for (const entry of visibleEntries) {
2328
- const sf = entry.subfamily || "Regular";
2329
- if (!groups[sf]) groups[sf] = [];
2330
- groups[sf].push(entry);
2331
- }
2332
- Object.values(groups).forEach((g) => g.sort(sortEntries));
2333
- const sorted = {};
2334
- const keys = Object.keys(groups).sort((a, b) => {
2335
- if (a === "Regular") return -1;
2336
- if (b === "Regular") return 1;
2337
- return a.localeCompare(b);
2338
- });
2339
- keys.forEach((k) => {
2340
- sorted[k] = groups[k];
2341
- });
2342
- return sorted;
2343
- }, [visibleEntries]);
2344
- const validationErrors = _react.useMemo.call(void 0, () => {
2345
- const errors = [];
2346
- const uploadable = fontEntries.filter((f) => f.status !== FONT_STATUS.ERROR);
2347
- const missingTitles = uploadable.filter((f) => !f.title || f.title.trim() === "");
2348
- if (missingTitles.length > 0) {
2349
- errors.push(`${missingTitles.length} font${missingTitles.length === 1 ? "" : "s"} missing a title`);
2350
- }
2351
- const missingIds = uploadable.filter((f) => !f.documentId || f.documentId.trim() === "");
2352
- if (missingIds.length > 0) {
2353
- errors.push(`${missingIds.length} font${missingIds.length === 1 ? "" : "s"} missing a document ID`);
2354
- }
2355
- if (hasConflicts) {
2356
- const conflictCount = uploadable.filter((f) => f._idConflict).length;
2357
- errors.push(`${conflictCount} font${conflictCount === 1 ? "" : "s"} with duplicate document IDs`);
2358
- }
2359
- return errors;
2360
- }, [fontEntries, hasConflicts]);
2361
- const canUploadValidation = isReviewing && processedCount > 0 && validationErrors.length === 0;
2362
- const handleUpload = _react.useCallback.call(void 0, () => {
2363
- if (validationErrors.length > 0) {
2364
- window.alert("Please fix the following before uploading:\n\n\u2022 " + validationErrors.join("\n\u2022 "));
2365
- return;
2366
- }
2367
- onStartExecution();
2368
- }, [validationErrors, onStartExecution]);
2369
- const handleToggleExpandAll = _react.useCallback.call(void 0, () => {
2370
- setAllExpanded((v) => !v);
2371
- }, []);
2372
- return /* @__PURE__ */ _react2.default.createElement(_ui.Stack, { space: 3 }, isProcessing && /* @__PURE__ */ _react2.default.createElement(_ui.Card, { border: true, padding: 3, radius: 2 }, /* @__PURE__ */ _react2.default.createElement(_ui.Stack, { space: 3 }, /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { align: "center", gap: 3 }, /* @__PURE__ */ _react2.default.createElement(_ui.Spinner, null), /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 1 }, "Processing ", plan.processingProgress.completed, " of ", totalCount, " files...")), /* @__PURE__ */ _react2.default.createElement(_ui.Box, { style: { height: 4, background: "var(--card-border-color)", borderRadius: 2, overflow: "hidden" } }, /* @__PURE__ */ _react2.default.createElement(
2373
- _ui.Box,
2374
- {
2375
- style: {
2376
- height: "100%",
2377
- width: "100%",
2378
- transformOrigin: "left",
2379
- transform: `scaleX(${totalCount > 0 ? plan.processingProgress.completed / totalCount : 0})`,
2380
- background: "#43b649",
2381
- transition: "transform 0.3s ease-out",
2382
- borderRadius: 2
2383
- }
2384
- }
2385
- )), plan.processingProgress.currentFile && /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 0, muted: true, style: { fontFamily: "monospace" } }, plan.processingProgress.currentFile), /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { justify: "flex-end" }, /* @__PURE__ */ _react2.default.createElement(
2386
- _ui.Button,
2387
- {
2388
- mode: "ghost",
2389
- tone: "caution",
2390
- text: "Cancel Processing",
2391
- fontSize: 1,
2392
- padding: 2,
2393
- onClick: onCancelProcessing
2394
- }
2395
- )))), isReviewing && /* @__PURE__ */ _react2.default.createElement(_ui.Card, { tone: errorCount > 0 ? "caution" : "positive", border: true, padding: 3, radius: 2 }, /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { align: "center", gap: 3 }, /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 1, weight: "semibold" }, processedCount, " document", processedCount === 1 ? "" : "s"), /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { gap: 1 }, createCount > 0 && /* @__PURE__ */ _react2.default.createElement(_ui.Badge, { tone: "positive", fontSize: 0 }, createCount, " create"), updateCount > 0 && /* @__PURE__ */ _react2.default.createElement(_ui.Badge, { tone: "caution", fontSize: 0 }, updateCount, " update"), errorCount > 0 && /* @__PURE__ */ _react2.default.createElement(_ui.Badge, { tone: "critical", fontSize: 0 }, errorCount, " error", errorCount === 1 ? "" : "s")))), isReviewing && /* @__PURE__ */ _react2.default.createElement(_react2.default.Fragment, null, /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 1, weight: "semibold" }, "Settings"), /* @__PURE__ */ _react2.default.createElement(_ui.Card, { border: true, padding: 3, radius: 2 }, /* @__PURE__ */ _react2.default.createElement(_ui.Stack, { space: 3 }, /* @__PURE__ */ _react2.default.createElement(_ui.Grid, { columns: [2], gap: 4 }, /* @__PURE__ */ _react2.default.createElement(_ui.Box, null, /* @__PURE__ */ _react2.default.createElement(
2396
- PriceInput_default,
2397
- {
2398
- inputPrice: localPrice,
2399
- handleInputChange: (e) => {
2400
- setLocalPrice(e.target.value);
2401
- dispatch({ type: "SET_SETTINGS", settings: { price: Number(e.target.value) || 0 } });
2402
- }
2403
- }
2404
- )), /* @__PURE__ */ _react2.default.createElement(_ui.Stack, { space: 3 }, /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { align: "center", gap: 2 }, /* @__PURE__ */ _react2.default.createElement(
2405
- _ui.Switch,
2406
- {
2407
- checked: localPreserveShortenedNames,
2408
- onChange: (e) => {
2409
- setLocalPreserveShortenedNames(e.target.checked);
2410
- dispatch({ type: "SET_SETTINGS", settings: { preserveShortenedNames: e.target.checked } });
2411
- }
2412
- }
2413
- ), /* @__PURE__ */ _react2.default.createElement(
2414
- _ui.Tooltip,
2415
- {
2416
- content: /* @__PURE__ */ _react2.default.createElement(_ui.Box, { padding: 2, style: { maxWidth: 260 } }, /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 1, style: { lineHeight: 1.6 } }, 'Abbreviations in font names are kept as-is (e.g. "XNarrow" stays "XNarrow").')),
2417
- placement: "top",
2418
- portal: true
2419
- },
2420
- /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { align: "center", gap: 1, style: { cursor: "default" } }, /* @__PURE__ */ _react2.default.createElement(_ui.Label, null, "Preserve shortened names"), /* @__PURE__ */ _react2.default.createElement(_icons.InfoOutlineIcon, { style: { opacity: 0.5, display: "block" } }))
2421
- )), /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { align: "center", gap: 2 }, /* @__PURE__ */ _react2.default.createElement(
2422
- _ui.Switch,
2423
- {
2424
- checked: localPreserveFileNames,
2425
- onChange: (e) => {
2426
- setLocalPreserveFileNames(e.target.checked);
2427
- dispatch({ type: "SET_SETTINGS", settings: { preserveFileNames: e.target.checked } });
2428
- }
2429
- }
2430
- ), /* @__PURE__ */ _react2.default.createElement(
2431
- _ui.Tooltip,
2432
- {
2433
- content: /* @__PURE__ */ _react2.default.createElement(_ui.Box, { padding: 2, style: { maxWidth: 260 } }, /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 1, style: { lineHeight: 1.6 } }, "Original filename is used for the font title and document ID instead of embedded font metadata.")),
2434
- placement: "top",
2435
- portal: true
2436
- },
2437
- /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { align: "center", gap: 1, style: { cursor: "default" } }, /* @__PURE__ */ _react2.default.createElement(_ui.Label, null, "Preserve file names"), /* @__PURE__ */ _react2.default.createElement(_icons.InfoOutlineIcon, { style: { opacity: 0.5, display: "block" } }))
2438
- ))))))), fontEntries.length > 0 && /* @__PURE__ */ _react2.default.createElement(
2439
- BulkActions,
2440
- {
2441
- fonts: plan.fonts,
2442
- dispatch,
2443
- searchQuery,
2444
- onSearchChange: setSearchQuery,
2445
- filterBy,
2446
- onFilterChange: setFilterBy,
2447
- allExpanded,
2448
- onToggleExpandAll: handleToggleExpandAll,
2449
- visibleTempIds
2450
- }
2451
- ), fontEntries.length > 0 && isReviewing && /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { justify: "flex-start", paddingY: 1 }, /* @__PURE__ */ _react2.default.createElement(
2452
- _ui.Button,
2453
- {
2454
- mode: "ghost",
2455
- fontSize: 0,
2456
- padding: 2,
2457
- text: allExpanded ? "Collapse All" : "Expand All",
2458
- onClick: handleToggleExpandAll,
2459
- style: { cursor: "pointer" }
2460
- }
2461
- )), fontEntries.length > 0 && isReviewing && /* @__PURE__ */ _react2.default.createElement(
2462
- _ui.Flex,
2463
- {
2464
- align: "center",
2465
- gap: 2,
2466
- paddingX: 2,
2467
- paddingY: 1,
2468
- style: { borderBottom: "1px solid var(--card-border-color)", userSelect: "none" }
2469
- },
2470
- /* @__PURE__ */ _react2.default.createElement(_ui.Box, { style: { width: 20 } }),
2471
- [
2472
- { key: "title", label: "Font Title", style: { flex: 1, cursor: "pointer" } },
2473
- { key: "weight", label: "Weight", style: { width: 50, textAlign: "center", cursor: "pointer" } },
2474
- { key: "style", label: "Style", style: { width: 50, textAlign: "center", cursor: "pointer" } },
2475
- { key: "files", label: "Files", style: { width: 40, textAlign: "center", cursor: "pointer" } },
2476
- { key: "action", label: "Action", style: { width: 55, textAlign: "center", cursor: "pointer" } }
2477
- ].map((col) => /* @__PURE__ */ _react2.default.createElement(
2478
- _ui.Text,
2479
- {
2480
- key: col.key,
2481
- size: 0,
2482
- weight: "semibold",
2483
- muted: sortBy !== col.key,
2484
- style: col.style,
2485
- onClick: () => handleSort(col.key)
2486
- },
2487
- col.label,
2488
- " ",
2489
- sortBy === col.key ? sortDir === "asc" ? "\u2191" : "\u2193" : ""
2490
- ))
2491
- ), Object.entries(groupedEntries).map(([subfamily, entries]) => /* @__PURE__ */ _react2.default.createElement(_ui.Stack, { key: subfamily, space: 1 }, /* @__PURE__ */ _react2.default.createElement(_ui.Card, { padding: 2, radius: 1, style: { background: "var(--card-muted-bg-color)" } }, /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { align: "center", gap: 2 }, /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 1, weight: "semibold" }, subfamily), /* @__PURE__ */ _react2.default.createElement(_ui.Badge, { mode: "outline", fontSize: 0 }, entries.length))), /* @__PURE__ */ _react2.default.createElement(_ui.Stack, { space: 0 }, entries.map((entry) => {
2492
- var _a2, _b2;
2493
- return /* @__PURE__ */ _react2.default.createElement(
2494
- FontReviewCard_default,
2495
- {
2496
- key: entry.tempId,
2497
- entry,
2498
- dispatch,
2499
- allExpanded,
2500
- typefaceTitle: (_a2 = plan.settings) == null ? void 0 : _a2.typefaceTitle,
2501
- price: (_b2 = plan.settings) == null ? void 0 : _b2.price
2502
- }
2503
- );
2504
- })))), visibleEntries.length === 0 && fontEntries.length > 0 && /* @__PURE__ */ _react2.default.createElement(_ui.Card, { border: true, padding: 4, radius: 2 }, /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 1, muted: true, align: "center" }, "No fonts match the current filter")), isReviewing && validationErrors.length > 0 && /* @__PURE__ */ _react2.default.createElement(_ui.Card, { tone: "caution", border: true, padding: 2, radius: 2 }, /* @__PURE__ */ _react2.default.createElement(_ui.Stack, { space: 1 }, validationErrors.map((err, i) => /* @__PURE__ */ _react2.default.createElement(_ui.Text, { key: i, size: 0, tone: "caution" }, "\u2022 ", err)))), isReviewing && processedCount > 0 && /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { justify: "flex-end", gap: 2, style: { position: "sticky", bottom: 0, background: "var(--card-bg-color)", paddingTop: 8, paddingBottom: 4 } }, /* @__PURE__ */ _react2.default.createElement(
2505
- _ui.Button,
2506
- {
2507
- mode: "default",
2508
- tone: "primary",
2509
- text: `Upload ${processedCount} Font${processedCount === 1 ? "" : "s"} to Sanity`,
2510
- disabled: !canUploadValidation,
2511
- onClick: handleUpload
2512
- }
2513
- )));
2514
- }
2515
-
2516
- // src/components/UploadStep3Execute.jsx
2517
-
2518
-
2519
-
2520
-
2521
- // src/utils/executeUploadPlan.js
2522
-
2523
-
2524
- // src/utils/generateCssFile.js
2525
- var _base64 = require('base-64'); var _base642 = _interopRequireDefault(_base64);
2526
- function _arrayBufferToBase64(buffer) {
2527
- var binary = "";
2528
- var bytes = new Uint8Array(buffer);
2529
- var len = bytes.byteLength;
2530
- for (var i = 0; i < len; i++) {
2531
- binary += String.fromCharCode(bytes[i]);
2532
- }
2533
- return _base642.default.encode(binary);
2534
- }
2535
- function buildVFDescriptors(axisMap) {
2536
- const cssAxes = {};
2537
- const skipped = [];
2538
- if (!axisMap) return { descriptors: "", skipped: [] };
2539
- try {
2540
- for (const [tag, axis] of Object.entries(axisMap)) {
2541
- const lo = Math.min(axis.min, axis.max);
2542
- const hi = Math.max(axis.min, axis.max);
2543
- if (lo === hi) {
2544
- skipped.push(tag);
2545
- continue;
2546
- }
2547
- if (tag === "wght") {
2548
- cssAxes["font-weight"] = `${lo} ${hi}`;
2549
- } else if (tag === "wdth") {
2550
- cssAxes["font-stretch"] = `${Math.max(50, lo)}% ${Math.min(200, hi)}%`;
2551
- } else if (tag === "slnt") {
2552
- cssAxes["font-style"] = `oblique ${-hi}deg ${-lo}deg`;
2553
- } else if (tag === "ital" && !cssAxes["font-style"]) {
2554
- if (hi > 0) cssAxes["font-style"] = "italic";
2555
- else skipped.push(tag);
2556
- } else {
2557
- skipped.push(tag);
2558
- }
2559
- }
2560
- } catch (_) {
2561
- }
2562
- const descriptors = Object.entries(cssAxes).map(([k, v]) => `${k}:${v}`).join(";") + (Object.keys(cssAxes).length ? ";" : "");
2563
- return { descriptors, skipped };
2564
- }
2565
- var FALLBACK_STACKS = {
2566
- "sans-serif": "local('Arial'), local('Helvetica Neue'), local('Roboto'), local('Liberation Sans')",
2567
- "serif": "local('Georgia'), local('Times New Roman'), local('Times')",
2568
- "monospace": "local('Courier New'), local('Courier'), local('Menlo'), local('Monaco')",
2569
- "default": "local('Arial'), local('Helvetica Neue'), local('Roboto'), local('Liberation Sans')"
2570
- };
2571
- var FAMILY_CLASS_MAP = {
2572
- 1: "serif",
2573
- 2: "serif",
2574
- 3: "serif",
2575
- 4: "serif",
2576
- 5: "serif",
2577
- 7: "serif",
2578
- 8: "sans-serif"
2579
- };
2580
- var SERIF_NAMES = /jubilat|corundum|dapifer|birra|daith/i;
2581
- var SANS_NAMES = /halyard|gamay|omnes|kit/i;
2582
- function detectFontCategory(font, fontName) {
2583
- if (fontName && SERIF_NAMES.test(fontName)) return "serif";
2584
- if (fontName && SANS_NAMES.test(fontName)) return "sans-serif";
2585
- try {
2586
- const familyClass = getFamilyClass(font);
2587
- const highByte = familyClass >> 8 & 255;
2588
- return _nullishCoalesce(FAMILY_CLASS_MAP[highByte], () => ( "default"));
2589
- } catch (e3) {
2590
- return "default";
2591
- }
2592
- }
2593
- function calcFallbackData(font, fontName) {
2594
- try {
2595
- const metrics = getFontMetrics(font);
2596
- const upm = metrics.unitsPerEm;
2597
- const category = detectFontCategory(font, fontName);
2598
- return {
2599
- fallbackSrc: FALLBACK_STACKS[category],
2600
- ascentOverride: `${(metrics.ascender / upm * 100).toFixed(2)}%`,
2601
- descentOverride: `${(Math.abs(metrics.descender) / upm * 100).toFixed(2)}%`,
2602
- lineGapOverride: `${(metrics.lineGap / upm * 100).toFixed(2)}%`
2603
- };
2604
- } catch (err) {
2605
- console.error("Failed to extract fallback font data:", err);
2606
- return {
2607
- fallbackSrc: FALLBACK_STACKS["default"],
2608
- ascentOverride: "100%",
2609
- descentOverride: "0%",
2610
- lineGapOverride: "0%"
2611
- };
2612
- }
2613
- }
2614
- async function generateCssFile({
2615
- woff2File,
2616
- fileInput,
2617
- language = null,
2618
- fileName,
2619
- fontName,
2620
- variableFont,
2621
- weight,
2622
- style = "Normal",
2623
- client
2624
- }) {
2625
- try {
2626
- let arrayBuffer = await woff2File.arrayBuffer();
2627
- let b64 = _arrayBufferToBase64(arrayBuffer);
2628
- let font = await parseFont(arrayBuffer, fileName + ".woff2");
2629
- let { fallbackSrc, ascentOverride, descentOverride, lineGapOverride } = calcFallbackData(font, fontName);
2630
- const safeFontName = escapeCssFontName(fontName);
2631
- let cssString;
2632
- if (variableFont) {
2633
- const axisMap = getVariationAxes(font);
2634
- let { descriptors, skipped } = buildVFDescriptors(axisMap);
2635
- let skipComment = skipped.length ? `/* axes present but have no @font-face descriptor: ${skipped.join(", ")}` + (skipped.includes("opsz") ? " \u2014 add font-optical-sizing:auto to your element CSS" : "") + " */" : "";
2636
- cssString = `${skipComment}@font-face{font-family:'${safeFontName}';src:url(data:application/font-woff2;charset=utf-8;base64,${b64})format('woff2');${descriptors}font-display:swap;}`;
2637
- } else {
2638
- let fontStyle = style === "Italic" ? "italic" : "normal";
2639
- cssString = `@font-face{font-family:'${safeFontName}';src:url(data:application/font-woff2;charset=utf-8;base64,${b64})format('woff2');font-weight:${weight};font-style:${fontStyle};font-display:swap;}`;
2640
- }
2641
- let fallbackCssString = `@font-face{font-family:'${safeFontName} Fallback';src:${fallbackSrc};ascent-override:${ascentOverride};descent-override:${descentOverride};line-gap-override:${lineGapOverride};}`;
2642
- const cssBytes = new TextEncoder().encode(cssString + fallbackCssString);
2643
- let doc = await client.assets.upload("file", new Blob([cssBytes]), { filename: fileName + ".css" });
2644
- let newFileInput = language == null ? {
2645
- ...fileInput,
2646
- css: {
2647
- _type: "file",
2648
- asset: {
2649
- _type: "reference",
2650
- _ref: doc._id
2651
- }
2652
- }
2653
- } : {
2654
- ...fileInput,
2655
- [language]: {
2656
- ...fileInput[language],
2657
- css: {
2658
- _type: "file",
2659
- asset: {
2660
- _type: "reference",
2661
- _ref: doc._id
2662
- }
2663
- }
2664
- }
2665
- };
2666
- return newFileInput;
2667
- } catch (err) {
2668
- console.error(err);
2669
- throw err;
2670
- }
2671
- }
2672
-
2673
- // src/utils/generateFontData.js
2674
- function buildFontMetadata(font) {
2675
- const metaData = getFontMetadata(font);
2676
- if (metaData.version) {
2677
- metaData.version = String(metaData.version).replaceAll("Version ", "");
2678
- }
2679
- const metrics = getFontMetrics(font);
2680
- return { metaData, metrics };
2681
- }
2682
- async function generateFontData({ fileInput, url, fontKit, fontId, client, commit = true }) {
2683
- if (fontId.startsWith("drafts.")) {
2684
- fontId = fontId.replace("drafts.", "");
2685
- }
2686
- console.log("Generate font data:", fontId, commit);
2687
- let srcUrl;
2688
- if (!url || url == null) {
2689
- srcUrl = await client.fetch(`*[_id == $id]{url}`, { id: fileInput.ttf.asset._ref });
2690
- srcUrl = srcUrl[0].url;
2691
- } else {
2692
- srcUrl = url;
2693
- }
2694
- let font = fontKit;
2695
- if (!fontKit || fontKit == null) {
2696
- let buffer = await fetch(srcUrl);
2697
- buffer = await buffer.arrayBuffer();
2698
- font = await parseFont(buffer, `${fontId}.ttf`);
2699
- }
2700
- const variableAxes = getVariationAxes(font);
2701
- const namedInstances = getNamedInstances(font);
2702
- let variableInstances = null;
2703
- if (namedInstances.length > 0 && variableAxes) {
2704
- variableInstances = {};
2705
- const axisTags = Object.keys(variableAxes);
2706
- for (const inst of namedInstances) {
2707
- const key = inst.name || inst.postScriptName || "Unknown";
2708
- const coord = {};
2709
- axisTags.forEach((tag, index) => {
2710
- coord[tag] = inst.coordinates[index];
2711
- });
2712
- variableInstances[key] = coord;
2713
- }
2714
- }
2715
- console.log("Variable instances:", variableInstances);
2716
- console.log("Variable axes:", variableAxes);
2717
- const opentypeFeatures = getAllFeatureTags(font);
2718
- const glyphCount = getGlyphCount(font);
2719
- const characterSet = getCharacterSet(font);
2720
- const { metaData, metrics } = buildFontMetadata(font);
2721
- let variableFont = false;
2722
- if (variableAxes && variableInstances && Object.keys(variableInstances).length > 0) {
2723
- variableFont = true;
2724
- }
2725
- let patch = {
2726
- metrics,
2727
- metaData,
2728
- variableFont,
2729
- variableAxes: JSON.stringify(variableAxes),
2730
- variableInstances: JSON.stringify(variableInstances),
2731
- glyphCount,
2732
- opentypeFeatures: { chars: opentypeFeatures },
2733
- characterSet: { chars: characterSet }
2734
- };
2735
- console.log("Font data patch:", Object.keys(patch));
2736
- if (commit) patch = await client.patch(fontId).set(patch).commit({ autoGenerateArrayKeys: true });
2737
- return patch;
2738
- }
2739
-
2740
- // src/utils/parseVariableFontInstances.js
2741
-
2742
- var WIDTH_PREFIXES = [
2743
- "XXXWide",
2744
- "XXWide",
2745
- "XWide",
2746
- "Wide",
2747
- "XXXNarrow",
2748
- "XXNarrow",
2749
- "XNarrow",
2750
- "Narrow"
2751
- ];
2752
- function parseInstanceName(instanceName) {
2753
- let subfamily = "";
2754
- let remaining = instanceName.trim();
2755
- for (const prefix of WIDTH_PREFIXES) {
2756
- if (remaining.toLowerCase().startsWith(prefix.toLowerCase() + " ") || remaining.toLowerCase() === prefix.toLowerCase()) {
2757
- subfamily = prefix;
2758
- remaining = remaining.substring(prefix.length).trim();
2759
- break;
2760
- }
2761
- }
2762
- let style = "";
2763
- for (const suffix of ["Backslant", "Slant", "Italic", "Oblique"]) {
2764
- if (remaining.toLowerCase().endsWith(" " + suffix.toLowerCase()) || remaining.toLowerCase() === suffix.toLowerCase()) {
2765
- style = suffix;
2766
- remaining = remaining.substring(0, remaining.length - suffix.length).trim();
2767
- break;
2768
- }
2769
- }
2770
- return { subfamily, weight: remaining || "Regular", style };
2771
- }
2772
- function filterBySubfamily(staticFonts, instanceSubfamily, typefaceName) {
2773
- if (!instanceSubfamily) {
2774
- return staticFonts.filter((sf) => {
2775
- const sub = (sf.subfamily || "").toLowerCase();
2776
- if (sub === "" || sub === "regular") return true;
2777
- const afterTypeface = (sf.title || "").replace(typefaceName, "").trim();
2778
- return !WIDTH_PREFIXES.some((p) => afterTypeface.toLowerCase().startsWith(p.toLowerCase()));
2779
- });
2780
- }
2781
- const lowerSf = instanceSubfamily.toLowerCase();
2782
- const expanded = (expandAbbreviations(instanceSubfamily) || "").toLowerCase();
2783
- return staticFonts.filter((sf) => {
2784
- const sub = (sf.subfamily || "").toLowerCase();
2785
- if (sub === lowerSf || expanded && sub === expanded) return true;
2786
- const afterTypeface = (sf.title || "").replace(typefaceName, "").trim().toLowerCase();
2787
- if (afterTypeface.startsWith(lowerSf)) return true;
2788
- if (expanded && afterTypeface.startsWith(expanded)) return true;
2789
- return false;
2790
- });
2791
- }
2792
- var WEIGHT_MAP = [
2793
- { term: "ultra", weight: 950 },
2794
- { term: "xxlight", weight: 200 },
2795
- { term: "xlight", weight: 250 },
2796
- { term: "extralight", weight: 200 },
2797
- { term: "extra light", weight: 200 },
2798
- { term: "thin", weight: 100 },
2799
- { term: "hairline", weight: 100 },
2800
- { term: "light", weight: 300 },
2801
- { term: "regular", weight: 400 },
2802
- { term: "normal", weight: 400 },
2803
- { term: "medium", weight: 500 },
2804
- { term: "semibold", weight: 600 },
2805
- { term: "semi bold", weight: 600 },
2806
- { term: "extrabold", weight: 800 },
2807
- { term: "extra bold", weight: 800 },
2808
- { term: "xbold", weight: 800 },
2809
- { term: "bold", weight: 700 },
2810
- { term: "black", weight: 900 },
2811
- { term: "heavy", weight: 900 }
2812
- ];
2813
- function weightFromName(name) {
2814
- const lower = name.toLowerCase();
2815
- for (const { term, weight } of WEIGHT_MAP) {
2816
- if (lower === term || lower.includes(term)) return weight;
2817
- }
2818
- return 400;
2819
- }
2820
- var STRATEGIES = [
2821
- // Pass 1: Exact title match (with typeface prefix)
2822
- {
2823
- name: "exact-title",
2824
- match: (instanceName, parsed, candidates, typefaceName) => {
2825
- const withPrefix = `${typefaceName} ${instanceName}`;
2826
- return candidates.find((sf) => sf.title === instanceName || sf.title === withPrefix) || null;
2827
- }
2828
- },
2829
- // Pass 2: Title normalisation — strip typeface name and compare remainder
2830
- {
2831
- name: "title-normalised",
2832
- match: (instanceName, parsed, candidates, typefaceName) => {
2833
- return candidates.find((sf) => {
2834
- const sfName = (sf.title || "").replace(typefaceName, "").trim();
2835
- if (sfName.toLowerCase() === instanceName.toLowerCase()) return true;
2836
- if (parsed.weight === "Regular" && !parsed.style) {
2837
- if (sfName.toLowerCase() === parsed.subfamily.toLowerCase()) return true;
2838
- }
2839
- return false;
2840
- }) || null;
2841
- }
2842
- },
2843
- // Pass 3: Abbreviation expansion (XLight → ExtraLight, XBold → ExtraBold)
2844
- {
2845
- name: "abbreviation",
2846
- match: (instanceName, parsed, candidates, typefaceName) => {
2847
- const expandedFull = instanceName.split(" ").map((w) => expandAbbreviations(w) || w).join(" ");
2848
- let found = candidates.find((sf) => {
2849
- const sfName = (sf.title || "").replace(typefaceName, "").trim();
2850
- return sfName.toLowerCase() === expandedFull.toLowerCase();
2851
- });
2852
- if (found) return found;
2853
- const expandedWeight = expandAbbreviations(parsed.weight) || parsed.weight;
2854
- const target = [parsed.subfamily, expandedWeight, parsed.style].filter(Boolean).join(" ");
2855
- return candidates.find((sf) => {
2856
- const sfName = (sf.title || "").replace(typefaceName, "").trim();
2857
- return sfName.toLowerCase() === target.toLowerCase();
2858
- }) || null;
2859
- }
2860
- },
2861
- // Pass 4: fullName metadata comparison
2862
- {
2863
- name: "metadata-fullName",
2864
- match: (instanceName, parsed, candidates, typefaceName) => {
2865
- return candidates.find((sf) => {
2866
- var _a;
2867
- if (!((_a = sf.metaData) == null ? void 0 : _a.fullName)) return false;
2868
- const typefacePattern = new RegExp(`^${typefaceName}\\s+`, "i");
2869
- const stylePart = sf.metaData.fullName.replace(typefacePattern, "").trim();
2870
- return instanceName.toLowerCase() === stylePart.toLowerCase();
2871
- }) || null;
2872
- }
2873
- },
2874
- // Pass 5: Weight + style matching (numeric, within subfamily)
2875
- {
2876
- name: "weight-style",
2877
- match: (instanceName, parsed, candidates) => {
2878
- const instanceWeight = weightFromName(parsed.weight);
2879
- const isBackslant = parsed.style.toLowerCase() === "backslant";
2880
- const isSlant = parsed.style.toLowerCase() === "slant";
2881
- const isItalic = parsed.style.toLowerCase() === "italic";
2882
- return candidates.find((sf) => {
2883
- var _a, _b;
2884
- if (Number(sf.weight) !== instanceWeight) return false;
2885
- if (isBackslant) return sf.style === "Italic" && ((_a = sf.title) == null ? void 0 : _a.toLowerCase().includes("backslant"));
2886
- if (isSlant) return sf.style === "Italic" && !((_b = sf.title) == null ? void 0 : _b.toLowerCase().includes("backslant"));
2887
- if (isItalic) return sf.style === "Italic";
2888
- return sf.style === "Regular";
2889
- }) || null;
2890
- }
2891
- },
2892
- // Pass 6: weightName string comparison
2893
- {
2894
- name: "weightName",
2895
- match: (instanceName, parsed, candidates) => {
2896
- const cleanInstance = parsed.weight.toLowerCase().trim();
2897
- return candidates.find((sf) => {
2898
- if (!sf.weightName) return false;
2899
- const cleanWeight = sf.weightName.toLowerCase().replace(/italic|slant|backslant/gi, "").trim();
2900
- return cleanInstance === cleanWeight;
2901
- }) || null;
2902
- }
2903
- }
2904
- ];
2905
- var parseVariableFontInstances = async (font, client) => {
2906
- if (!font.variableFont || !font.variableInstances) return [];
2907
- let variableInstances;
2908
- try {
2909
- variableInstances = JSON.parse(font.variableInstances);
2910
- } catch (err) {
2911
- console.error("Error parsing variable instances:", err);
2912
- variableInstances = {};
2913
- }
2914
- if (Object.keys(variableInstances).length === 0) return [];
2915
- let staticFonts;
2916
- const typeface = await client.fetch(
2917
- `*[_type == 'typeface' && title == $typefaceName][0]{
2918
- 'fonts': styles.fonts[]-> {
2919
- _id, title, subfamily, style, weight, weightName, metaData, variableFont
2920
- }
2921
- }`,
2922
- { typefaceName: font.typefaceName }
2923
- );
2924
- if ((typeface == null ? void 0 : typeface.fonts) && typeface.fonts.length > 0) {
2925
- staticFonts = typeface.fonts.filter((f) => !f.variableFont);
2926
- console.log("Using curated typeface fonts list:", staticFonts.length, "fonts");
2927
- } else {
2928
- console.warn("Typeface not found or no fonts in curated list, falling back to all fonts query");
2929
- staticFonts = await client.fetch(
2930
- `*[_type == 'font' && typefaceName == $typefaceName && variableFont != true]{
2931
- _id, title, subfamily, style, weight, weightName, metaData
2932
- }`,
2933
- { typefaceName: font.typefaceName }
2934
- );
2935
- }
2936
- const instanceNames = Object.keys(variableInstances);
2937
- console.log("Variable font instances:", instanceNames.length);
2938
- console.log("Available static fonts:", staticFonts.length);
2939
- const parsedInstances = instanceNames.map((name) => ({
2940
- name,
2941
- parsed: parseInstanceName(name)
2942
- }));
2943
- const results = /* @__PURE__ */ new Map();
2944
- const claimedFontIds = /* @__PURE__ */ new Set();
2945
- for (const strategy of STRATEGIES) {
2946
- const unmatched = parsedInstances.filter((inst) => !results.has(inst.name));
2947
- if (unmatched.length === 0) break;
2948
- const passMatches = [];
2949
- for (const inst of unmatched) {
2950
- const subfamilyCandidates = filterBySubfamily(staticFonts, inst.parsed.subfamily, font.typefaceName).filter((sf) => !claimedFontIds.has(sf._id));
2951
- const match = strategy.match(inst.name, inst.parsed, subfamilyCandidates, font.typefaceName, font);
2952
- if (match) {
2953
- passMatches.push({ instanceName: inst.name, font: match, strategy: strategy.name });
2954
- }
2955
- }
2956
- for (const m of passMatches) {
2957
- if (!claimedFontIds.has(m.font._id) && !results.has(m.instanceName)) {
2958
- results.set(m.instanceName, { fontId: m.font._id, strategy: m.strategy });
2959
- claimedFontIds.add(m.font._id);
2960
- }
2961
- }
2962
- }
2963
- const matched = [...results.values()].length;
2964
- console.log(`[parseVariableFontInstances] Matched ${matched}/${instanceNames.length} instances across ${STRATEGIES.length} passes`);
2965
- const instanceMappings = instanceNames.map((instanceName) => {
2966
- const result = results.get(instanceName);
2967
- const matchedFont = result ? staticFonts.find((sf) => sf._id === result.fontId) : null;
2968
- console.log(`Instance "${instanceName}" \u2192 ${matchedFont ? `${matchedFont.title} (${result.strategy})` : "No match"}`);
2969
- return {
2970
- key: instanceName,
2971
- value: matchedFont ? { _type: "reference", _ref: matchedFont._id, _weak: true } : null,
2972
- _key: _nanoid.nanoid.call(void 0, )
2973
- };
2974
- });
2975
- return instanceMappings;
2976
- };
2977
- var parseVariableFontInstances_default = parseVariableFontInstances;
2978
-
2979
- // src/utils/updateTypefaceDocument.js
2980
-
2981
- var updateTypefaceDocument = async (doc_id, fontRefs, variableRefs, subfamilies, uniqueSubfamilies, subfamiliesArray, preferredStyleRef, newPreferredStyle, stylesObject, client, setStatus, setError) => {
2982
- console.log("Updating typeface document with new fonts:", { fontRefs, variableRefs, subfamilies, uniqueSubfamilies });
2983
- setStatus("Updating typeface references...");
2984
- const dedupeRefs = (existing, incoming) => {
2985
- const merged = [...existing || []];
2986
- const existingRefs = new Set(merged.map((r) => r._ref).filter(Boolean));
2987
- incoming.forEach((ref) => {
2988
- if (ref._ref && !existingRefs.has(ref._ref)) {
2989
- merged.push(ref);
2990
- existingRefs.add(ref._ref);
2991
- }
2992
- });
2993
- return merged;
2994
- };
2995
- let patch = {
2996
- "styles.fonts": dedupeRefs(stylesObject.fonts, fontRefs),
2997
- "styles.variableFont": dedupeRefs(stylesObject == null ? void 0 : stylesObject.variableFont, variableRefs)
2998
- };
2999
- setStatus("Organising font subfamilies...");
3000
- subfamiliesArray = subfamiliesArray || [];
3001
- uniqueSubfamilies.forEach((subfamilyName) => {
3002
- if (!subfamiliesArray.find((sf) => sf.title === subfamilyName)) {
3003
- subfamiliesArray.push({
3004
- title: subfamilyName,
3005
- _key: _nanoid.nanoid.call(void 0, ),
3006
- _type: "object",
3007
- fonts: []
3008
- });
3009
- }
3010
- });
3011
- if (subfamiliesArray.length > 0) {
3012
- Object.entries(subfamilies).forEach(([id, subfamilyName]) => {
3013
- if (id.toLowerCase().includes("vf")) return;
3014
- const subfamilyIndex = subfamiliesArray.findIndex((sf) => sf.title === subfamilyName);
3015
- if (subfamilyIndex !== -1) {
3016
- subfamiliesArray[subfamilyIndex].fonts.push({
3017
- _ref: id,
3018
- _key: _nanoid.nanoid.call(void 0, ),
3019
- _type: "reference",
3020
- _weak: true
3021
- });
3022
- }
3023
- });
3024
- subfamiliesArray = subfamiliesArray.map((subfamily) => ({
3025
- ...subfamily,
3026
- fonts: subfamily.fonts.filter(
3027
- (font, index, self) => index === self.findIndex((f) => f._ref === font._ref)
3028
- )
3029
- }));
3030
- }
3031
- patch["styles.subfamilies"] = subfamiliesArray;
3032
- await updatePreferredStyle(doc_id, preferredStyleRef, newPreferredStyle, patch, client);
3033
- console.log("doc_id: ", doc_id);
3034
- console.log("Typeface patch: ", patch);
3035
- console.log("New preferred style: ", newPreferredStyle);
3036
- console.log("SubfamiliesArray:", subfamiliesArray);
3037
- try {
3038
- await client.patch(doc_id).set(patch).commit();
3039
- console.log(`Updated document: ${doc_id}`);
3040
- if (doc_id.startsWith("drafts.")) {
3041
- await updatePublishedDocument(doc_id, patch, client);
3042
- }
3043
- } catch (err) {
3044
- console.error("Error updating document:", err.message);
3045
- setStatus("Error updating typeface");
3046
- setError(true);
3047
- }
3048
- };
3049
- var updatePreferredStyle = async (doc_id, preferredStyleRef, newPreferredStyle, patch, client) => {
3050
- const isCurrentlyEmpty = !(preferredStyleRef == null ? void 0 : preferredStyleRef._ref) || preferredStyleRef._ref === "" || preferredStyleRef._ref === null;
3051
- const hasCandidate = (newPreferredStyle == null ? void 0 : newPreferredStyle._ref) && newPreferredStyle._ref !== "";
3052
- if (isCurrentlyEmpty && hasCandidate) {
3053
- patch.preferredStyle = {
3054
- _type: "reference",
3055
- _ref: newPreferredStyle._ref,
3056
- _weak: true
3057
- };
3058
- }
3059
- };
3060
- var updatePublishedDocument = async (doc_id, patch, client) => {
3061
- const publishedId = doc_id.replace("drafts.", "");
3062
- const publishedDoc = await client.fetch(`*[_id == $publishedId]`, { publishedId }).then((res) => res[0]);
3063
- if (publishedDoc) {
3064
- await client.patch(publishedId).set(patch).commit();
3065
- console.log(`Updated published document: ${publishedId}`);
3066
- } else {
3067
- console.log(`No published document found for ${publishedId}, skipping`);
3068
- }
3069
- };
3070
-
3071
- // src/utils/executeUploadPlan.js
3072
- async function executeUploadPlan({
3073
- plan,
3074
- client,
3075
- docId,
3076
- stylesObject = {},
3077
- preferredStyleRef = {},
3078
- onProgress
3079
- }) {
3080
- var _a, _b, _c;
3081
- const result = {
3082
- success: true,
3083
- created: 0,
3084
- updated: 0,
3085
- failed: 0,
3086
- skipped: 0,
3087
- failedFonts: [],
3088
- fontRefs: [],
3089
- variableRefs: [],
3090
- typefacePatchError: null
3091
- };
3092
- const fontEntries = Object.values(plan.fonts);
3093
- const queue = fontEntries.filter((entry) => entry.status !== FONT_STATUS.ERROR);
3094
- const skipped = fontEntries.filter((entry) => entry.status === FONT_STATUS.ERROR);
3095
- result.skipped = skipped.length;
3096
- const progress = {};
3097
- for (const entry of queue) {
3098
- progress[entry.tempId] = {
3099
- status: EXECUTION_STATUS.QUEUED,
3100
- currentFile: null,
3101
- filesComplete: 0,
3102
- filesTotal: entry.files.length,
3103
- assetRefs: {},
3104
- error: null
3105
- };
3106
- }
3107
- if (onProgress) {
3108
- onProgress({ type: "execution-start", totalFonts: queue.length, skippedFonts: result.skipped });
3109
- }
3110
- const chunks = [];
3111
- for (let i = 0; i < queue.length; i += CONCURRENCY_LIMIT) {
3112
- chunks.push(queue.slice(i, i + CONCURRENCY_LIMIT));
3113
- }
3114
- let newPreferredStyle = { weight: -100, style: "Italic", _ref: "" };
3115
- const subfamilies = {};
3116
- const uniqueSubfamilies = /* @__PURE__ */ new Set();
3117
- for (const chunk of chunks) {
3118
- const chunkResults = await Promise.allSettled(
3119
- chunk.map((entry) => executeSingleFont({
3120
- entry,
3121
- plan,
3122
- client,
3123
- progress,
3124
- onProgress
3125
- }))
3126
- );
3127
- for (let i = 0; i < chunkResults.length; i++) {
3128
- const chunkResult = chunkResults[i];
3129
- const entry = chunk[i];
3130
- if (chunkResult.status === "fulfilled" && chunkResult.value) {
3131
- const fontResult = chunkResult.value;
3132
- if (fontResult.isNew) result.created++;
3133
- else result.updated++;
3134
- if (entry.variableFont) {
3135
- result.variableRefs.push(fontResult.ref);
3136
- } else {
3137
- result.fontRefs.push(fontResult.ref);
3138
- }
3139
- subfamilies[entry.documentId] = entry.subfamily;
3140
- if (entry.subfamily) uniqueSubfamilies.add(entry.subfamily);
3141
- if (entry.weight > newPreferredStyle.weight) {
3142
- newPreferredStyle = {
3143
- weight: entry.weight,
3144
- style: entry.style,
3145
- _ref: fontResult.ref._ref
3146
- };
3147
- }
3148
- } else {
3149
- result.failed++;
3150
- result.success = false;
3151
- const errorMsg = ((_a = chunkResult.reason) == null ? void 0 : _a.message) || ((_b = chunkResult.value) == null ? void 0 : _b.error) || "Unknown error";
3152
- result.failedFonts.push({
3153
- tempId: entry.tempId,
3154
- title: entry.title,
3155
- error: errorMsg,
3156
- failedAt: ((_c = progress[entry.tempId]) == null ? void 0 : _c.status) || "unknown"
3157
- });
3158
- }
3159
- }
3160
- }
3161
- if (result.fontRefs.length > 0 || result.variableRefs.length > 0) {
3162
- try {
3163
- if (onProgress) {
3164
- onProgress({ type: "typeface-patching" });
3165
- }
3166
- await updateTypefaceDocument(
3167
- docId,
3168
- result.fontRefs,
3169
- result.variableRefs,
3170
- subfamilies,
3171
- [...uniqueSubfamilies],
3172
- (stylesObject == null ? void 0 : stylesObject.subfamilies) || [],
3173
- preferredStyleRef,
3174
- newPreferredStyle,
3175
- stylesObject,
3176
- client,
3177
- (msg) => {
3178
- if (onProgress) onProgress({ type: "typeface-status", message: msg });
3179
- },
3180
- (err) => {
3181
- if (err) console.error("Typeface patch error flag set");
3182
- }
3183
- );
3184
- if (onProgress) {
3185
- onProgress({ type: "typeface-patched" });
3186
- }
3187
- } catch (err) {
3188
- result.typefacePatchError = err.message;
3189
- result.success = false;
3190
- console.error("Typeface patch failed:", err.message);
3191
- if (onProgress) {
3192
- onProgress({ type: "typeface-error", error: err.message });
3193
- }
3194
- }
3195
- }
3196
- if (onProgress) {
3197
- onProgress({ type: "execution-complete", result });
3198
- }
3199
- return result;
3200
- }
3201
- async function executeSingleFont({ entry, plan, client, progress, onProgress }) {
3202
- var _a, _b, _c, _d;
3203
- const fontProgress = progress[entry.tempId];
3204
- fontProgress.status = EXECUTION_STATUS.UPLOADING_ASSETS;
3205
- if (onProgress) {
3206
- onProgress({ type: "font-upload-start", tempId: entry.tempId, fontProgress: { ...fontProgress } });
3207
- }
3208
- const decision = entry.decisions.existingDocument;
3209
- const userChoice = decision.userChoice;
3210
- const recommendation = decision.recommendation;
3211
- const shouldUpdate = userChoice === "update" || !userChoice && (recommendation === RECOMMENDATION.USE_EXACT || recommendation === RECOMMENDATION.USE_CANDIDATE);
3212
- const existingDoc = shouldUpdate ? decision.selectedCandidate || decision.exact || decision.candidates[0] : null;
3213
- const fileInput = {};
3214
- for (let j = 0; j < entry.files.length; j++) {
3215
- const file = entry.files[j];
3216
- const fileType = determineFileType(file);
3217
- if (!fileType) continue;
3218
- if (fontProgress.assetRefs[fileType]) {
3219
- fileInput[fileType] = {
3220
- _type: "file",
3221
- asset: { _type: "reference", _ref: fontProgress.assetRefs[fileType] }
3222
- };
3223
- fontProgress.filesComplete++;
3224
- continue;
3225
- }
3226
- fontProgress.currentFile = fileType;
3227
- try {
3228
- const assetFilename = plan.settings.preserveFileNames && entry.originalFilename ? `${entry.originalFilename}.${fileType}` : `${entry.documentId}.${fileType}`;
3229
- const baseAsset = await uploadWithRetry(
3230
- () => client.assets.upload("file", file, { filename: assetFilename })
3231
- );
3232
- if (plan.settings.preserveFileNames && baseAsset.originalFilename !== assetFilename) {
3233
- try {
3234
- await client.patch(baseAsset._id).set({ originalFilename: assetFilename }).commit();
3235
- } catch (renameErr) {
3236
- console.warn("Could not rename asset:", renameErr.message);
3237
- }
3238
- }
3239
- fileInput[fileType] = {
3240
- _type: "file",
3241
- asset: { _type: "reference", _ref: baseAsset._id }
3242
- };
3243
- fontProgress.assetRefs[fileType] = baseAsset._id;
3244
- fontProgress.filesComplete++;
3245
- if (onProgress) {
3246
- onProgress({ type: "file-uploaded", tempId: entry.tempId, fileType, fontProgress: { ...fontProgress } });
3247
- }
3248
- } catch (err) {
3249
- fontProgress.status = EXECUTION_STATUS.ERROR;
3250
- fontProgress.error = err.message;
3251
- throw new Error(`Asset upload failed for ${fileType}: ${err.message}`);
3252
- }
3253
- }
3254
- if (fileInput.woff2 || fileInput.woff) {
3255
- fontProgress.status = EXECUTION_STATUS.GENERATING_CSS;
3256
- try {
3257
- const woff2File = entry.files.find((f) => f.name.endsWith(".woff2") || f.name.endsWith(".woff"));
3258
- if (woff2File) {
3259
- const updatedFileInput = await generateCssFile({
3260
- woff2File,
3261
- fileInput,
3262
- fileName: entry.documentId,
3263
- fontName: entry.title,
3264
- variableFont: entry.variableFont,
3265
- weight: entry.weight,
3266
- style: entry.style,
3267
- client
3268
- });
3269
- Object.assign(fileInput, updatedFileInput);
3270
- if (onProgress) {
3271
- onProgress({ type: "css-generated", tempId: entry.tempId, fontProgress: { ...fontProgress } });
3272
- }
3273
- }
3274
- } catch (err) {
3275
- console.warn("CSS generation failed for", entry.title, "\u2014 document created without CSS:", err.message);
3276
- }
3277
- }
3278
- if (fileInput.ttf || fileInput.otf) {
3279
- fontProgress.status = EXECUTION_STATUS.GENERATING_METADATA;
3280
- try {
3281
- const ttfAssetRef = ((_b = (_a = fileInput.ttf) == null ? void 0 : _a.asset) == null ? void 0 : _b._ref) || ((_d = (_c = fileInput.otf) == null ? void 0 : _c.asset) == null ? void 0 : _d._ref);
3282
- if (ttfAssetRef) {
3283
- const metadata = await generateFontData({
3284
- fileInput,
3285
- fontKit: null,
3286
- // Will re-parse from URL
3287
- fontId: entry.documentId,
3288
- client,
3289
- commit: false
3290
- // Don't patch yet — we'll include in the document creation
3291
- });
3292
- Object.assign(entry, {
3293
- metaData: metadata.metaData,
3294
- metrics: metadata.metrics,
3295
- variableAxes: metadata.variableAxes,
3296
- variableInstances: metadata.variableInstances,
3297
- opentypeFeatures: metadata.opentypeFeatures,
3298
- characterSet: metadata.characterSet,
3299
- glyphCount: metadata.glyphCount,
3300
- variableFont: metadata.variableFont
3301
- });
3302
- if (onProgress) {
3303
- onProgress({ type: "metadata-generated", tempId: entry.tempId, fontProgress: { ...fontProgress } });
3304
- }
3305
- }
3306
- } catch (err) {
3307
- console.warn("Metadata generation failed for", entry.title, ":", err.message);
3308
- }
3309
- }
3310
- fontProgress.status = EXECUTION_STATUS.CREATING_DOCUMENT;
3311
- const fontDocId = shouldUpdate && existingDoc ? existingDoc._id : entry.documentId;
3312
- const isNew = !shouldUpdate;
3313
- const fontDoc = {
3314
- _id: fontDocId,
3315
- _type: "font",
3316
- _key: _nanoid.nanoid.call(void 0, ),
3317
- title: entry.title,
3318
- slug: { _type: "slug", current: fontDocId },
3319
- typefaceName: plan.settings.typefaceTitle || entry.title,
3320
- style: entry.style,
3321
- variableFont: entry.variableFont,
3322
- weightName: entry.weightName,
3323
- subfamily: entry.subfamily,
3324
- weight: entry.weight,
3325
- price: plan.settings.price,
3326
- sell: plan.settings.price > 0,
3327
- normalWeight: true,
3328
- fileInput
3329
- };
3330
- if (entry.metaData) fontDoc.metaData = entry.metaData;
3331
- if (entry.metrics) fontDoc.metrics = entry.metrics;
3332
- if (entry.variableAxes) fontDoc.variableAxes = entry.variableAxes;
3333
- if (entry.variableInstances) fontDoc.variableInstances = entry.variableInstances;
3334
- if (entry.opentypeFeatures) fontDoc.opentypeFeatures = entry.opentypeFeatures;
3335
- if (entry.characterSet) fontDoc.characterSet = entry.characterSet;
3336
- if (entry.glyphCount) fontDoc.glyphCount = entry.glyphCount;
3337
- try {
3338
- if (shouldUpdate && existingDoc) {
3339
- if (existingDoc.fileInput) {
3340
- Object.keys(existingDoc.fileInput).forEach((key) => {
3341
- if (!fontDoc.fileInput[key]) fontDoc.fileInput[key] = existingDoc.fileInput[key];
3342
- });
3343
- }
3344
- if (!fontDoc.metaData && existingDoc.metaData) fontDoc.metaData = existingDoc.metaData;
3345
- if (!fontDoc.metrics && existingDoc.metrics) fontDoc.metrics = existingDoc.metrics;
3346
- if (existingDoc.scriptFileInput) fontDoc.scriptFileInput = existingDoc.scriptFileInput;
3347
- if (existingDoc.variableInstanceReferences) {
3348
- fontDoc.variableInstanceReferences = existingDoc.variableInstanceReferences;
3349
- }
3350
- await client.patch(fontDocId).set(fontDoc).commit();
3351
- console.log("Updated existing font:", fontDocId, entry.title);
3352
- } else {
3353
- await client.createOrReplace(fontDoc);
3354
- console.log("Created new font:", fontDocId, entry.title);
3355
- }
3356
- fontProgress.status = EXECUTION_STATUS.COMPLETE;
3357
- if (onProgress) {
3358
- onProgress({ type: "document-created", tempId: entry.tempId, isNew, fontProgress: { ...fontProgress } });
3359
- }
3360
- return {
3361
- ref: {
3362
- _key: _nanoid.nanoid.call(void 0, ),
3363
- _type: "reference",
3364
- _ref: fontDocId,
3365
- _weak: true
3366
- },
3367
- isNew
3368
- };
3369
- } catch (err) {
3370
- fontProgress.status = EXECUTION_STATUS.ERROR;
3371
- fontProgress.error = err.message;
3372
- throw new Error(`Document creation failed: ${err.message}`);
3373
- }
3374
- }
3375
- function determineFileType(file) {
3376
- if (file.name.endsWith(".ttf")) return "ttf";
3377
- if (file.name.endsWith(".otf")) return "otf";
3378
- if (file.name.endsWith(".woff")) return "woff";
3379
- if (file.name.endsWith(".woff2")) return "woff2";
3380
- if (file.name.endsWith(".eot")) return "eot";
3381
- if (file.name.endsWith(".svg")) return "svg";
3382
- return "";
3383
- }
3384
- async function uploadWithRetry(uploadFn) {
3385
- for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
3386
- try {
3387
- return await uploadFn();
3388
- } catch (err) {
3389
- const is429 = err.statusCode === 429 || err.status === 429;
3390
- if (is429 && attempt < MAX_RETRIES) {
3391
- const delay = backoffWithJitter(attempt);
3392
- console.warn(`Rate limited (429), retrying in ${delay}ms (attempt ${attempt + 1}/${MAX_RETRIES})`);
3393
- await new Promise((resolve) => setTimeout(resolve, delay));
3394
- } else {
3395
- throw err;
3396
- }
3397
- }
3398
- }
3399
- }
3400
-
3401
- // src/utils/executionReducer.js
3402
- function createInitialExecutionState() {
3403
- return {
3404
- status: "idle",
3405
- progress: {},
3406
- error: null
3407
- };
3408
- }
3409
- function executionReducer(state, action) {
3410
- switch (action.type) {
3411
- case "SET_EXECUTION_STATUS": {
3412
- return { ...state, status: action.status };
3413
- }
3414
- case "SET_FONT_EXECUTION_PROGRESS": {
3415
- const { tempId, progress } = action;
3416
- return {
3417
- ...state,
3418
- progress: {
3419
- ...state.progress,
3420
- [tempId]: {
3421
- ...state.progress[tempId] || {},
3422
- ...progress
3423
- }
3424
- }
3425
- };
3426
- }
3427
- case "SET_EXECUTION_ERROR": {
3428
- return {
3429
- ...state,
3430
- status: "error",
3431
- error: action.error
3432
- };
3433
- }
3434
- default:
3435
- return state;
3436
- }
3437
- }
3438
-
3439
- // src/components/UploadStep3Execute.jsx
3440
- var formatElapsed = (s) => {
3441
- const m = Math.floor(s / 60);
3442
- const sec = s % 60;
3443
- return m > 0 ? `${m}m ${sec}s` : `${sec}s`;
3444
- };
3445
- function UploadStep3Execute({
3446
- plan,
3447
- client,
3448
- docId,
3449
- stylesObject,
3450
- preferredStyleRef,
3451
- retryTempIds,
3452
- onComplete
3453
- }) {
3454
- const [execState, execDispatch] = _react.useReducer.call(void 0, executionReducer, null, createInitialExecutionState);
3455
- const [result, setResult] = _react.useState.call(void 0, null);
3456
- const [elapsedSeconds, setElapsedSeconds] = _react.useState.call(void 0, 0);
3457
- const startedRef = _react.useRef.call(void 0, false);
3458
- const timerRef = _react.useRef.call(void 0, null);
3459
- const wakeLockRef = _react.useRef.call(void 0, null);
3460
- const executionPlan = _react.useMemo.call(void 0, () => {
3461
- if (!retryTempIds) return plan;
3462
- return {
3463
- ...plan,
3464
- fonts: Object.fromEntries(
3465
- Object.entries(plan.fonts).filter(([tempId]) => retryTempIds.includes(tempId))
3466
- )
3467
- };
3468
- }, [plan, retryTempIds]);
3469
- const fontEntries = _react.useMemo.call(void 0,
3470
- () => Object.values(executionPlan.fonts).filter((f) => f.status !== "error"),
3471
- [executionPlan]
3472
- );
3473
- _react.useEffect.call(void 0, () => {
3474
- var _a;
3475
- if (startedRef.current) return;
3476
- startedRef.current = true;
3477
- timerRef.current = setInterval(() => setElapsedSeconds((s) => s + 1), 1e3);
3478
- (_a = navigator.wakeLock) == null ? void 0 : _a.request("screen").then((lock) => {
3479
- wakeLockRef.current = lock;
3480
- }).catch(() => {
3481
- });
3482
- execDispatch({ type: "SET_EXECUTION_STATUS", status: "uploading" });
3483
- executeUploadPlan({
3484
- plan: executionPlan,
3485
- client,
3486
- docId,
3487
- stylesObject,
3488
- preferredStyleRef,
3489
- onProgress: (event) => {
3490
- if (event.type === "font-upload-start" || event.type === "file-uploaded" || event.type === "css-generated" || event.type === "metadata-generated" || event.type === "document-created") {
3491
- if (event.fontProgress) {
3492
- execDispatch({
3493
- type: "SET_FONT_EXECUTION_PROGRESS",
3494
- tempId: event.tempId,
3495
- progress: event.fontProgress
3496
- });
3497
- }
3498
- } else if (event.type === "typeface-patching") {
3499
- execDispatch({ type: "SET_EXECUTION_STATUS", status: "patching-typeface" });
3500
- } else if (event.type === "typeface-error") {
3501
- execDispatch({ type: "SET_EXECUTION_ERROR", error: event.error });
3502
- }
3503
- }
3504
- }).then((executionResult) => {
3505
- var _a2;
3506
- setResult(executionResult);
3507
- clearInterval(timerRef.current);
3508
- (_a2 = wakeLockRef.current) == null ? void 0 : _a2.release().catch(() => {
3509
- });
3510
- execDispatch({ type: "SET_EXECUTION_STATUS", status: executionResult.success ? "complete" : "error" });
3511
- onComplete(executionResult);
3512
- }).catch((err) => {
3513
- var _a2;
3514
- clearInterval(timerRef.current);
3515
- (_a2 = wakeLockRef.current) == null ? void 0 : _a2.release().catch(() => {
3516
- });
3517
- execDispatch({ type: "SET_EXECUTION_ERROR", error: err.message });
3518
- const errorResult = {
3519
- success: false,
3520
- created: 0,
3521
- updated: 0,
3522
- failed: 1,
3523
- skipped: 0,
3524
- failedFonts: [{ title: "Unknown", tempId: "unknown", error: err.message, failedAt: "unknown" }],
3525
- fontRefs: [],
3526
- variableRefs: [],
3527
- typefacePatchError: err.message
3528
- };
3529
- setResult(errorResult);
3530
- onComplete(errorResult);
3531
- });
3532
- return () => {
3533
- var _a2;
3534
- clearInterval(timerRef.current);
3535
- (_a2 = wakeLockRef.current) == null ? void 0 : _a2.release().catch(() => {
3536
- });
3537
- };
3538
- }, []);
3539
- const completedCount = Object.values(execState.progress).filter(
3540
- (p) => p.status === EXECUTION_STATUS.COMPLETE || p.status === EXECUTION_STATUS.ERROR
3541
- ).length;
3542
- return /* @__PURE__ */ _react2.default.createElement(_ui.Stack, { space: 4 }, /* @__PURE__ */ _react2.default.createElement(_ui.Card, { border: true, padding: 3, radius: 2 }, /* @__PURE__ */ _react2.default.createElement(_ui.Stack, { space: 3 }, /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { align: "center", gap: 3 }, execState.status === "complete" ? /* @__PURE__ */ _react2.default.createElement(_icons.CheckmarkCircleIcon, { style: { color: "#43b649", fontSize: 20 } }) : execState.status === "error" ? /* @__PURE__ */ _react2.default.createElement(_icons.WarningOutlineIcon, { style: { color: "var(--card-badge-critical-bg-color)", fontSize: 20 } }) : /* @__PURE__ */ _react2.default.createElement(_ui.Spinner, null), /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 1, weight: "semibold" }, execState.status === "patching-typeface" ? "Updating typeface document..." : execState.status === "complete" ? "Upload complete" : execState.status === "error" ? "Upload failed" : `Uploading ${completedCount} of ${fontEntries.length} fonts...`), /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 1, muted: true, style: { marginLeft: "auto" } }, formatElapsed(elapsedSeconds))), /* @__PURE__ */ _react2.default.createElement(_ui.Box, { style: { height: 4, background: "var(--card-border-color)", borderRadius: 2, overflow: "hidden" } }, /* @__PURE__ */ _react2.default.createElement(
3543
- _ui.Box,
3544
- {
3545
- style: {
3546
- height: "100%",
3547
- width: "100%",
3548
- transformOrigin: "left",
3549
- transform: `scaleX(${fontEntries.length > 0 ? completedCount / fontEntries.length : 0})`,
3550
- background: execState.status === "error" ? "var(--card-badge-critical-bg-color)" : "var(--card-badge-positive-bg-color)",
3551
- transition: "transform 0.3s ease-out",
3552
- borderRadius: 2
3553
- }
3554
- }
3555
- )))), execState.status !== "complete" && execState.status !== "error" && /* @__PURE__ */ _react2.default.createElement(_ui.Card, { tone: "caution", border: true, radius: 2, padding: 2 }, /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { align: "center", gap: 2 }, /* @__PURE__ */ _react2.default.createElement(_icons.WarningOutlineIcon, { style: { flexShrink: 0 } }), /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 1, weight: "semibold" }, "Do not close or reload this tab"))), /* @__PURE__ */ _react2.default.createElement(_ui.Box, { style: { maxHeight: 400, overflowY: "auto" } }, /* @__PURE__ */ _react2.default.createElement(_ui.Stack, { space: 1 }, fontEntries.map((entry) => {
3556
- const progress = execState.progress[entry.tempId];
3557
- const status = (progress == null ? void 0 : progress.status) || EXECUTION_STATUS.QUEUED;
3558
- return /* @__PURE__ */ _react2.default.createElement(_ui.Card, { key: entry.tempId, border: true, radius: 1, padding: 2 }, /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { align: "center", gap: 2 }, /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 1, style: { flex: 1 } }, entry.title), /* @__PURE__ */ _react2.default.createElement(_ui.Box, { style: { width: 120, flexShrink: 0, textAlign: "right" } }, status === EXECUTION_STATUS.QUEUED && /* @__PURE__ */ _react2.default.createElement(_ui.Badge, { mode: "outline", fontSize: 0 }, "Queued"), status === EXECUTION_STATUS.UPLOADING_ASSETS && /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { gap: 1, align: "center", justify: "flex-end" }, /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 0, muted: true }, (progress == null ? void 0 : progress.currentFile) || "Uploading..."), /* @__PURE__ */ _react2.default.createElement(_ui.Spinner, { style: { width: 12, height: 12 } })), (status === EXECUTION_STATUS.GENERATING_CSS || status === EXECUTION_STATUS.GENERATING_METADATA) && /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { gap: 1, align: "center", justify: "flex-end" }, /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 0, muted: true }, status === EXECUTION_STATUS.GENERATING_CSS ? "CSS" : "Metadata"), /* @__PURE__ */ _react2.default.createElement(_ui.Spinner, { style: { width: 12, height: 12 } })), status === EXECUTION_STATUS.CREATING_DOCUMENT && /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { gap: 1, align: "center", justify: "flex-end" }, /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 0, muted: true }, "Creating doc"), /* @__PURE__ */ _react2.default.createElement(_ui.Spinner, { style: { width: 12, height: 12 } })), status === EXECUTION_STATUS.COMPLETE && /* @__PURE__ */ _react2.default.createElement(_ui.Badge, { tone: "positive", fontSize: 0 }, "Done"), status === EXECUTION_STATUS.ERROR && /* @__PURE__ */ _react2.default.createElement(_ui.Badge, { tone: "critical", fontSize: 0 }, "Failed"))), status === EXECUTION_STATUS.ERROR && (progress == null ? void 0 : progress.error) && /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 0, muted: true, style: { marginTop: 4 } }, progress.error));
3559
- }))), execState.error && /* @__PURE__ */ _react2.default.createElement(_ui.Card, { tone: "critical", border: true, padding: 3, radius: 2 }, /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 1 }, execState.error)));
3560
- }
3561
-
3562
- // src/components/UploadStep3bInstances.jsx
3563
-
3564
-
3565
-
3566
-
3567
- function UploadStep3bInstances({
3568
- plan,
3569
- executionResult,
3570
- client,
3571
- typefaceTitle,
3572
- onComplete
3573
- }) {
3574
- const [loading, setLoading] = _react.useState.call(void 0, true);
3575
- const [saving, setSaving] = _react.useState.call(void 0, false);
3576
- const [vfMappings, setVfMappings] = _react.useState.call(void 0, {});
3577
- const [allStaticFonts, setAllStaticFonts] = _react.useState.call(void 0, []);
3578
- const [filterUnmatched, setFilterUnmatched] = _react.useState.call(void 0, false);
3579
- const vfEntries = _react.useMemo.call(void 0,
3580
- () => Object.values(plan.fonts).filter((f) => f.variableFont && f.status !== "error"),
3581
- [plan.fonts]
3582
- );
3583
- const runMatching = _react.useCallback.call(void 0, async () => {
3584
- var _a;
3585
- setLoading(true);
3586
- const mappings = {};
3587
- let staticFonts = [];
3588
- try {
3589
- const typeface = await client.fetch(
3590
- `*[_type == 'typeface' && title == $typefaceTitle][0]{
3591
- 'fonts': styles.fonts[]-> {
3592
- _id, title, subfamily, style, weight, weightName, metaData, variableFont
3593
- }
3594
- }`,
3595
- { typefaceTitle }
3596
- );
3597
- if (((_a = typeface == null ? void 0 : typeface.fonts) == null ? void 0 : _a.length) > 0) {
3598
- staticFonts = typeface.fonts.filter((f) => !f.variableFont);
3599
- }
3600
- if (staticFonts.length === 0) {
3601
- staticFonts = await client.fetch(
3602
- `*[_type == 'font' && typefaceName == $typefaceTitle && variableFont != true]{
3603
- _id, title, subfamily, style, weight, weightName, metaData
3604
- }`,
3605
- { typefaceTitle }
3606
- );
3607
- }
3608
- } catch (err) {
3609
- console.error("[InstanceMapper] Failed to fetch static fonts:", err);
3610
- }
3611
- const deduped = /* @__PURE__ */ new Map();
3612
- staticFonts.forEach((f) => {
3613
- if (f._id && !deduped.has(f._id)) deduped.set(f._id, f);
3614
- });
3615
- staticFonts = [...deduped.values()];
3616
- console.log(`[InstanceMapper] Found ${staticFonts.length} static fonts for "${typefaceTitle}"`);
3617
- setAllStaticFonts(staticFonts);
3618
- for (const vf of vfEntries) {
3619
- try {
3620
- const vfDoc = await client.fetch(
3621
- `*[_id == $id][0]{
3622
- _id, title, typefaceName, variableFont, variableInstances, metaData
3623
- }`,
3624
- { id: vf.documentId }
3625
- );
3626
- if (!vfDoc) {
3627
- console.warn(`[InstanceMapper] VF document not found: ${vf.documentId}`);
3628
- mappings[vf.tempId] = [];
3629
- continue;
3630
- }
3631
- console.log(`[InstanceMapper] Running matcher for VF: ${vfDoc.title}, variableInstances: ${vfDoc.variableInstances ? "present" : "missing"}`);
3632
- const instanceMappings = await parseVariableFontInstances(vfDoc, client);
3633
- console.log(`[InstanceMapper] Matched ${instanceMappings.filter((m) => m.value).length}/${instanceMappings.length} instances for ${vfDoc.title}`);
3634
- mappings[vf.tempId] = instanceMappings.map((m) => {
3635
- var _a2;
3636
- return {
3637
- instanceName: m.key,
3638
- matchedFontId: ((_a2 = m.value) == null ? void 0 : _a2._ref) || "",
3639
- matchedFontTitle: "",
3640
- _key: m._key || _nanoid.nanoid.call(void 0, )
3641
- };
3642
- });
3643
- } catch (err) {
3644
- console.error(`[InstanceMapper] Error matching VF ${vf.documentId}:`, err);
3645
- mappings[vf.tempId] = [];
3646
- }
3647
- }
3648
- const allMatchedIds = /* @__PURE__ */ new Set();
3649
- Object.values(mappings).forEach((m) => m.forEach((i) => {
3650
- if (i.matchedFontId) allMatchedIds.add(i.matchedFontId);
3651
- }));
3652
- if (allMatchedIds.size > 0) {
3653
- try {
3654
- const titles = await client.fetch(`*[_id in $ids]{ _id, title }`, { ids: [...allMatchedIds] });
3655
- const titleMap = {};
3656
- titles.forEach((t) => {
3657
- titleMap[t._id] = t.title;
3658
- });
3659
- Object.values(mappings).forEach((m) => {
3660
- m.forEach((i) => {
3661
- if (i.matchedFontId) i.matchedFontTitle = titleMap[i.matchedFontId] || i.matchedFontId;
3662
- });
3663
- });
3664
- } catch (err) {
3665
- console.warn("[InstanceMapper] Failed to resolve font titles:", err);
3666
- }
3667
- }
3668
- setVfMappings(mappings);
3669
- setLoading(false);
3670
- }, [vfEntries, client, typefaceTitle]);
3671
- _react.useEffect.call(void 0, () => {
3672
- runMatching();
3673
- }, [runMatching]);
3674
- const claimedFontIds = _react.useMemo.call(void 0, () => {
3675
- const claimed = /* @__PURE__ */ new Set();
3676
- Object.values(vfMappings).forEach((mappings) => {
3677
- mappings.forEach((m) => {
3678
- if (m.matchedFontId) claimed.add(m.matchedFontId);
3679
- });
3680
- });
3681
- return claimed;
3682
- }, [vfMappings]);
3683
- const handleMappingChange = _react.useCallback.call(void 0, (vfTempId, instanceKey, fontId) => {
3684
- const font = allStaticFonts.find((sf) => sf._id === fontId);
3685
- setVfMappings((prev) => ({
3686
- ...prev,
3687
- [vfTempId]: prev[vfTempId].map(
3688
- (m) => m._key === instanceKey ? { ...m, matchedFontId: fontId, matchedFontTitle: (font == null ? void 0 : font.title) || fontId } : m
3689
- )
3690
- }));
3691
- }, [allStaticFonts]);
3692
- const handleSave = _react.useCallback.call(void 0, async () => {
3693
- setSaving(true);
3694
- const errors = [];
3695
- for (const vf of vfEntries) {
3696
- const mappings = vfMappings[vf.tempId] || [];
3697
- const references = mappings.filter((m) => m.matchedFontId).map((m) => ({
3698
- _key: _nanoid.nanoid.call(void 0, ),
3699
- _type: "object",
3700
- key: m.instanceName,
3701
- value: {
3702
- _type: "reference",
3703
- _ref: m.matchedFontId,
3704
- _weak: true
3705
- }
3706
- }));
3707
- try {
3708
- await client.patch(vf.documentId).set({
3709
- variableInstanceReferences: references
3710
- }).commit();
3711
- console.log(`Patched VF instance mappings: ${vf.documentId} (${references.length} instances)`);
3712
- } catch (err) {
3713
- console.error(`Failed to patch VF ${vf.documentId}:`, err);
3714
- errors.push({ vfTitle: vf.title, error: err.message });
3715
- }
3716
- }
3717
- setSaving(false);
3718
- onComplete({ success: errors.length === 0, errors });
3719
- }, [vfEntries, vfMappings, client, onComplete]);
3720
- const totalInstances = Object.values(vfMappings).reduce((sum, m) => sum + m.length, 0);
3721
- const matchedInstances = Object.values(vfMappings).reduce(
3722
- (sum, m) => sum + m.filter((i) => i.matchedFontId).length,
3723
- 0
3724
- );
3725
- const unmatchedInstances = totalInstances - matchedInstances;
3726
- const getAutocompleteOptions = _react.useCallback.call(void 0, (currentFontId) => {
3727
- return allStaticFonts.filter((sf) => !claimedFontIds.has(sf._id) || sf._id === currentFontId).map((sf) => ({
3728
- value: sf._id,
3729
- payload: sf
3730
- }));
3731
- }, [allStaticFonts, claimedFontIds]);
3732
- if (loading) {
3733
- return /* @__PURE__ */ _react2.default.createElement(_ui.Card, { border: true, padding: 4, radius: 2 }, /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { align: "center", gap: 3, justify: "center" }, /* @__PURE__ */ _react2.default.createElement(_ui.Spinner, null), /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 1 }, "Matching variable font instances to static fonts...")));
3734
- }
3735
- if (vfEntries.length === 0) {
3736
- onComplete({ success: true, errors: [] });
3737
- return null;
3738
- }
3739
- return /* @__PURE__ */ _react2.default.createElement(_ui.Stack, { space: 4 }, /* @__PURE__ */ _react2.default.createElement(_ui.Stack, { space: 2 }, /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 2, weight: "semibold" }, "Map Variable Font Instances"), /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 1, muted: true }, "Review the auto-matched instances below. Each named instance should map to its corresponding static font document.")), /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { gap: 2, align: "center" }, /* @__PURE__ */ _react2.default.createElement(
3740
- _ui.Button,
3741
- {
3742
- mode: "ghost",
3743
- tone: "primary",
3744
- icon: _icons.SearchIcon,
3745
- text: "Re-run Matching",
3746
- fontSize: 0,
3747
- padding: 2,
3748
- onClick: runMatching,
3749
- disabled: loading,
3750
- style: { cursor: "pointer" }
3751
- }
3752
- ), /* @__PURE__ */ _react2.default.createElement(_ui.Badge, { tone: "positive", fontSize: 0 }, matchedInstances, " matched"), unmatchedInstances > 0 && /* @__PURE__ */ _react2.default.createElement(
3753
- _ui.Badge,
3754
- {
3755
- tone: "critical",
3756
- fontSize: 0,
3757
- style: { cursor: "pointer" },
3758
- onClick: () => setFilterUnmatched((v) => !v)
3759
- },
3760
- unmatchedInstances,
3761
- " unmatched ",
3762
- filterUnmatched ? "(showing)" : ""
3763
- ), filterUnmatched && /* @__PURE__ */ _react2.default.createElement(
3764
- _ui.Badge,
3765
- {
3766
- mode: "outline",
3767
- fontSize: 0,
3768
- style: { cursor: "pointer" },
3769
- onClick: () => setFilterUnmatched(false)
3770
- },
3771
- "Clear filter"
3772
- ), /* @__PURE__ */ _react2.default.createElement(_ui.Badge, { mode: "outline", fontSize: 0 }, allStaticFonts.length, " fonts available")), vfEntries.map((vf) => {
3773
- const mappings = vfMappings[vf.tempId] || [];
3774
- const displayMappings = filterUnmatched ? mappings.filter((m) => !m.matchedFontId) : mappings;
3775
- const vfMatched = mappings.filter((m) => m.matchedFontId).length;
3776
- return /* @__PURE__ */ _react2.default.createElement(_ui.Card, { key: vf.tempId, border: true, padding: 3, radius: 2 }, /* @__PURE__ */ _react2.default.createElement(_ui.Stack, { space: 3 }, /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { align: "center", gap: 2 }, /* @__PURE__ */ _react2.default.createElement(_ui.Badge, { tone: "primary", fontSize: 0 }, "VF"), /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 1, weight: "semibold" }, vf.title), /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 0, muted: true, style: { marginLeft: "auto" } }, vfMatched, "/", mappings.length, " matched")), /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { align: "center", gap: 2, paddingY: 1, style: { borderBottom: "1px solid var(--card-border-color)" } }, /* @__PURE__ */ _react2.default.createElement(_ui.Box, { style: { width: 20 } }), /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 0, weight: "semibold", muted: true, style: { flex: 1 } }, "Instance"), /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 0, weight: "semibold", muted: true, style: { flex: 2 } }, "Static Font Document")), /* @__PURE__ */ _react2.default.createElement(_ui.Stack, { space: 1 }, displayMappings.map((mapping) => {
3777
- const isMatched = !!mapping.matchedFontId;
3778
- const options = getAutocompleteOptions(mapping.matchedFontId);
3779
- return /* @__PURE__ */ _react2.default.createElement(
3780
- _ui.Flex,
3781
- {
3782
- key: mapping._key,
3783
- align: "center",
3784
- gap: 2,
3785
- paddingY: 2,
3786
- style: { borderBottom: "1px solid var(--card-border-color)" }
3787
- },
3788
- /* @__PURE__ */ _react2.default.createElement(_ui.Box, { style: { width: 20, flexShrink: 0 } }, isMatched ? /* @__PURE__ */ _react2.default.createElement(_icons.CheckmarkCircleIcon, { style: { color: "#43b649", fontSize: 16 } }) : /* @__PURE__ */ _react2.default.createElement(_icons.CloseCircleIcon, { style: { color: "#f03e2f", fontSize: 16 } })),
3789
- /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 1, style: { flex: 1, whiteSpace: "nowrap" } }, mapping.instanceName),
3790
- /* @__PURE__ */ _react2.default.createElement(_ui.Box, { style: { flex: 2 } }, /* @__PURE__ */ _react2.default.createElement(
3791
- _ui.Autocomplete,
3792
- {
3793
- id: `instance-${mapping._key}`,
3794
- options,
3795
- value: mapping.matchedFontId,
3796
- placeholder: "Search for a font...",
3797
- icon: _icons.SearchIcon,
3798
- fontSize: 1,
3799
- filterOption: (query, option) => {
3800
- var _a, _b, _c, _d;
3801
- const sf = option.payload;
3802
- const q = query.toLowerCase();
3803
- return ((_a = sf.title) == null ? void 0 : _a.toLowerCase().includes(q)) || ((_b = sf._id) == null ? void 0 : _b.toLowerCase().includes(q)) || ((_c = sf.weightName) == null ? void 0 : _c.toLowerCase().includes(q)) || String(sf.weight).includes(q) || ((_d = sf.subfamily) == null ? void 0 : _d.toLowerCase().includes(q));
3804
- },
3805
- renderOption: (option) => {
3806
- const sf = option.payload;
3807
- const isClaimed = claimedFontIds.has(sf._id) && sf._id !== mapping.matchedFontId;
3808
- return /* @__PURE__ */ _react2.default.createElement(_ui.Card, { as: "button", padding: 2, style: { opacity: isClaimed ? 0.4 : 1 } }, /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { align: "center", gap: 2 }, /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 1 }, sf.title), /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 0, muted: true }, sf.weight, " ", sf.style), sf.subfamily && sf.subfamily !== "Regular" && /* @__PURE__ */ _react2.default.createElement(_ui.Badge, { mode: "outline", fontSize: 0 }, sf.subfamily)));
3809
- },
3810
- renderValue: (value, option) => {
3811
- if (option == null ? void 0 : option.payload) return option.payload.title;
3812
- if (mapping.matchedFontTitle) return mapping.matchedFontTitle;
3813
- const font = allStaticFonts.find((sf) => sf._id === value);
3814
- return (font == null ? void 0 : font.title) || value;
3815
- },
3816
- onSelect: (value) => handleMappingChange(vf.tempId, mapping._key, value),
3817
- openButton: true
3818
- }
3819
- ))
3820
- );
3821
- })), mappings.length === 0 && /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 1, muted: true }, "No named instances found in this variable font.")));
3822
- }), /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { justify: "flex-end", gap: 2, style: { position: "sticky", bottom: 0, background: "var(--card-bg-color)", paddingTop: 8, paddingBottom: 4 } }, /* @__PURE__ */ _react2.default.createElement(
3823
- _ui.Button,
3824
- {
3825
- mode: "ghost",
3826
- text: "Skip \u2014 I'll map instances later",
3827
- fontSize: 1,
3828
- padding: 3,
3829
- onClick: () => onComplete({ success: true, errors: [], skipped: true }),
3830
- style: { cursor: "pointer" }
3831
- }
3832
- ), /* @__PURE__ */ _react2.default.createElement(
3833
- _ui.Button,
3834
- {
3835
- mode: "default",
3836
- tone: "positive",
3837
- text: saving ? "Saving..." : `Save Mappings (${matchedInstances}/${totalInstances})`,
3838
- fontSize: 1,
3839
- padding: 3,
3840
- disabled: saving,
3841
- onClick: handleSave,
3842
- style: { cursor: "pointer" }
3843
- }
3844
- )));
3845
- }
3846
-
3847
- // src/components/UploadSummary.jsx
3848
-
3849
-
3850
-
3851
- function UploadSummary({
3852
- plan,
3853
- result,
3854
- onClose,
3855
- onRetry,
3856
- client,
3857
- docId,
3858
- stylesObject,
3859
- preferredStyleRef
3860
- }) {
3861
- var _a;
3862
- const [retryingPatch, setRetryingPatch] = _react.useState.call(void 0, false);
3863
- const [patchRetryResult, setPatchRetryResult] = _react.useState.call(void 0, null);
3864
- const hasFailedFonts = ((_a = result == null ? void 0 : result.failedFonts) == null ? void 0 : _a.length) > 0;
3865
- const hasTypefacePatchError = (result == null ? void 0 : result.typefacePatchError) && !(patchRetryResult == null ? void 0 : patchRetryResult.success);
3866
- const allSuccess = (result == null ? void 0 : result.success) && !hasTypefacePatchError;
3867
- const handleRetryTypefacePatch = _react.useCallback.call(void 0, async () => {
3868
- var _a2, _b;
3869
- if (!result || !client || !docId) return;
3870
- setRetryingPatch(true);
3871
- setPatchRetryResult(null);
3872
- try {
3873
- const subfamilies = {};
3874
- const uniqueSubfamilies = /* @__PURE__ */ new Set();
3875
- for (const entry of Object.values(plan.fonts)) {
3876
- if (entry.status === "error") continue;
3877
- subfamilies[entry.documentId] = entry.subfamily;
3878
- if (entry.subfamily) uniqueSubfamilies.add(entry.subfamily);
3879
- }
3880
- await updateTypefaceDocument(
3881
- docId,
3882
- result.fontRefs || [],
3883
- result.variableRefs || [],
3884
- subfamilies,
3885
- [...uniqueSubfamilies],
3886
- (stylesObject == null ? void 0 : stylesObject.subfamilies) || [],
3887
- preferredStyleRef || {},
3888
- { weight: -100, style: "Italic", _ref: ((_b = (_a2 = result.fontRefs) == null ? void 0 : _a2[0]) == null ? void 0 : _b._ref) || "" },
3889
- stylesObject || {},
3890
- client,
3891
- () => {
3892
- },
3893
- () => {
3894
- }
3895
- );
3896
- setPatchRetryResult({ success: true });
3897
- } catch (err) {
3898
- console.error("Typeface patch retry failed:", err);
3899
- setPatchRetryResult({ success: false, error: err.message });
3900
- }
3901
- setRetryingPatch(false);
3902
- }, [result, client, docId, plan, stylesObject, preferredStyleRef]);
3903
- return /* @__PURE__ */ _react2.default.createElement(_ui.Stack, { space: 4 }, /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { align: "center", gap: 3, ref: (el) => {
3904
- var _a2;
3905
- return (_a2 = el == null ? void 0 : el.focus) == null ? void 0 : _a2.call(el);
3906
- }, tabIndex: -1 }, allSuccess ? /* @__PURE__ */ _react2.default.createElement(_icons.CheckmarkCircleIcon, { style: { color: "#43b649", fontSize: 28 } }) : /* @__PURE__ */ _react2.default.createElement(_icons.WarningOutlineIcon, { style: { color: "#f03e2f", fontSize: 28 } }), /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 2, weight: "semibold" }, allSuccess ? "Upload Complete" : "Upload Completed with Issues")), result && /* @__PURE__ */ _react2.default.createElement(_ui.Card, { border: true, padding: 4, radius: 2 }, /* @__PURE__ */ _react2.default.createElement(_ui.Stack, { space: 3 }, /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { gap: 2, wrap: "wrap" }, result.created > 0 && /* @__PURE__ */ _react2.default.createElement(_ui.Badge, { tone: "positive", fontSize: 1 }, result.created, " created"), result.updated > 0 && /* @__PURE__ */ _react2.default.createElement(_ui.Badge, { tone: "caution", fontSize: 1 }, result.updated, " updated"), result.failed > 0 && /* @__PURE__ */ _react2.default.createElement(_ui.Badge, { tone: "critical", fontSize: 1 }, result.failed, " failed"), result.skipped > 0 && /* @__PURE__ */ _react2.default.createElement(_ui.Badge, { mode: "outline", fontSize: 1 }, result.skipped, " skipped")))), hasFailedFonts && /* @__PURE__ */ _react2.default.createElement(_ui.Stack, { space: 3 }, /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { align: "center", justify: "space-between" }, /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { align: "center", gap: 2 }, /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 1, weight: "semibold" }, "Failed Fonts"), /* @__PURE__ */ _react2.default.createElement(_ui.Badge, { tone: "critical", fontSize: 0 }, result.failedFonts.length)), /* @__PURE__ */ _react2.default.createElement(
3907
- _ui.Button,
3908
- {
3909
- mode: "ghost",
3910
- tone: "primary",
3911
- icon: _icons.ResetIcon,
3912
- text: "Retry Failed",
3913
- fontSize: 1,
3914
- padding: 2,
3915
- onClick: () => onRetry(result.failedFonts.map((f) => f.tempId).filter(Boolean))
3916
- }
3917
- )), /* @__PURE__ */ _react2.default.createElement(_ui.Stack, { space: 2 }, result.failedFonts.map((f, i) => /* @__PURE__ */ _react2.default.createElement(_ui.Card, { key: i, tone: "critical", border: true, padding: 3, radius: 2 }, /* @__PURE__ */ _react2.default.createElement(_ui.Stack, { space: 2 }, /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { align: "center", gap: 2 }, /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 1, weight: "semibold" }, f.title), f.failedAt && f.failedAt !== "unknown" && /* @__PURE__ */ _react2.default.createElement(_ui.Badge, { tone: "critical", fontSize: 0, mode: "outline" }, "Failed at: ", f.failedAt)), /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 1 }, f.error)))))), hasTypefacePatchError && /* @__PURE__ */ _react2.default.createElement(_ui.Card, { tone: "caution", border: true, padding: 4, radius: 2 }, /* @__PURE__ */ _react2.default.createElement(_ui.Stack, { space: 3 }, /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 1, weight: "semibold" }, "Typeface Document Not Updated"), /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 1 }, result.created + result.updated, " font document", result.created + result.updated === 1 ? "" : "s", " created/updated successfully, but the typeface document could not be patched to reference them."), /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 1, muted: true }, result.typefacePatchError), /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { gap: 2 }, /* @__PURE__ */ _react2.default.createElement(
3918
- _ui.Button,
3919
- {
3920
- mode: "default",
3921
- tone: "primary",
3922
- icon: retryingPatch ? void 0 : _icons.ResetIcon,
3923
- text: retryingPatch ? "Retrying..." : "Retry Typeface Patch",
3924
- disabled: retryingPatch,
3925
- onClick: handleRetryTypefacePatch
3926
- }
3927
- ), retryingPatch && /* @__PURE__ */ _react2.default.createElement(_ui.Spinner, null)))), (patchRetryResult == null ? void 0 : patchRetryResult.success) && /* @__PURE__ */ _react2.default.createElement(_ui.Card, { tone: "positive", border: true, padding: 3, radius: 2 }, /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 1 }, "Typeface document updated successfully on retry.")), patchRetryResult && !patchRetryResult.success && /* @__PURE__ */ _react2.default.createElement(_ui.Card, { tone: "critical", border: true, padding: 3, radius: 2 }, /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 1 }, "Retry failed: ", patchRetryResult.error)), /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { justify: "flex-end" }, /* @__PURE__ */ _react2.default.createElement(
3928
- _ui.Button,
3929
- {
3930
- mode: "default",
3931
- tone: "primary",
3932
- text: "Close",
3933
- onClick: onClose
3934
- }
3935
- )));
3936
- }
3937
-
3938
- // src/components/UploadModal.jsx
3939
- var STEPS = [
3940
- { key: 1, label: "Upload Files" },
3941
- { key: 2, label: "Review" },
3942
- { key: 3, label: "Upload" },
3943
- { key: 4, label: "Map Instances" }
3944
- ];
3945
- function phaseToStep(phase) {
3946
- switch (phase) {
3947
- case PLAN_PHASE.IDLE:
3948
- return 1;
3949
- case PLAN_PHASE.PROCESSING:
3950
- return 2;
3951
- case PLAN_PHASE.REVIEWING:
3952
- return 2;
3953
- case PLAN_PHASE.READY:
3954
- return 2;
3955
- case PLAN_PHASE.EXECUTING:
3956
- return 3;
3957
- case PLAN_PHASE.COMPLETE:
3958
- return 3;
3959
- case PLAN_PHASE.ERROR:
3960
- return 3;
3961
- default:
3962
- return 1;
3963
- }
3964
- }
3965
- function UploadModal({
3966
- open,
3967
- onClose,
3968
- client,
3969
- docId,
3970
- typefaceTitle,
3971
- stylesObject,
3972
- preferredStyleRef,
3973
- slug
3974
- }) {
3975
- const [plan, dispatch] = _react.useReducer.call(void 0, planReducer, null, () => createEmptyPlan());
3976
- const [processingCancelled, setProcessingCancelled] = _react.useState.call(void 0, false);
3977
- const [executionResult, setExecutionResult] = _react.useState.call(void 0, null);
3978
- const [retryTempIds, setRetryTempIds] = _react.useState.call(void 0, null);
3979
- const [instanceMappingPhase, setInstanceMappingPhase] = _react.useState.call(void 0, false);
3980
- const [instanceMappingResult, setInstanceMappingResult] = _react.useState.call(void 0, null);
3981
- const cancelRef = _react.useRef.call(void 0, false);
3982
- const focusRef = _react.useRef.call(void 0, null);
3983
- const { weightKeywordList, italicKeywordList } = _react.useMemo.call(void 0, () => generateStyleKeywords(), []);
3984
- const hasVFs = _react.useMemo.call(void 0,
3985
- () => Object.values(plan.fonts).some((f) => f.variableFont && f.status !== "error"),
3986
- [plan.fonts]
3987
- );
3988
- const baseStep = phaseToStep(plan.phase);
3989
- const currentStep = instanceMappingPhase ? 4 : plan.phase === PLAN_PHASE.COMPLETE && !instanceMappingResult ? baseStep : baseStep;
3990
- const isExecuting = plan.phase === PLAN_PHASE.EXECUTING;
3991
- _react.useEffect.call(void 0, () => {
3992
- if (!open || !isExecuting) return;
3993
- const handler = (e) => {
3994
- e.preventDefault();
3995
- e.returnValue = "";
3996
- };
3997
- window.addEventListener("beforeunload", handler);
3998
- return () => window.removeEventListener("beforeunload", handler);
3999
- }, [open, isExecuting]);
4000
- _react.useEffect.call(void 0, () => {
4001
- if (focusRef.current) {
4002
- focusRef.current.focus();
4003
- }
4004
- }, [currentStep]);
4005
- const handleClose = _react.useCallback.call(void 0, () => {
4006
- if (isExecuting) return;
4007
- const hasFonts = Object.keys(plan.fonts).length > 0;
4008
- if (hasFonts && plan.phase !== PLAN_PHASE.COMPLETE) {
4009
- if (!window.confirm("Close the upload modal? All progress will be lost.")) return;
4010
- }
4011
- dispatch({ type: "SET_PHASE", phase: PLAN_PHASE.IDLE });
4012
- setExecutionResult(null);
4013
- onClose();
4014
- }, [plan, isExecuting, onClose]);
4015
- const handleStartProcessing = _react.useCallback.call(void 0, async (files, settings) => {
4016
- dispatch({ type: "SET_SETTINGS", settings: { ...settings, typefaceTitle } });
4017
- dispatch({ type: "SET_PHASE", phase: PLAN_PHASE.PROCESSING, totalFiles: files.length });
4018
- cancelRef.current = false;
4019
- setProcessingCancelled(false);
4020
- setExecutionResult(null);
4021
- try {
4022
- const builtPlan = await buildUploadPlan({
4023
- files,
4024
- typefaceTitle,
4025
- docId,
4026
- settings,
4027
- client,
4028
- stylesObject,
4029
- weightKeywordList,
4030
- italicKeywordList,
4031
- onProgress: (event) => {
4032
- if (cancelRef.current) return;
4033
- if (event.type === "font-processed" || event.type === "font-error") {
4034
- dispatch({ type: "UPDATE_PROCESSING_PROGRESS", progress: event.progress });
4035
- }
4036
- }
4037
- });
4038
- if (!cancelRef.current) {
4039
- for (const [tempId, entry] of Object.entries(builtPlan.fonts)) {
4040
- dispatch({ type: "ADD_PROCESSED_FONT", tempId, fontEntry: entry });
4041
- }
4042
- dispatch({ type: "SET_PHASE", phase: PLAN_PHASE.REVIEWING });
4043
- }
4044
- } catch (err) {
4045
- console.error("Processing failed:", err);
4046
- dispatch({ type: "SET_PHASE", phase: PLAN_PHASE.REVIEWING });
4047
- }
4048
- }, [typefaceTitle, docId, client, stylesObject, weightKeywordList, italicKeywordList]);
4049
- const handleCancelProcessing = _react.useCallback.call(void 0, () => {
4050
- cancelRef.current = true;
4051
- setProcessingCancelled(true);
4052
- dispatch({ type: "SET_PHASE", phase: PLAN_PHASE.IDLE });
4053
- }, []);
4054
- const handleStartExecution = _react.useCallback.call(void 0, () => {
4055
- dispatch({ type: "SET_PHASE", phase: PLAN_PHASE.EXECUTING });
4056
- }, []);
4057
- const handleExecutionComplete = _react.useCallback.call(void 0, (result) => {
4058
- setExecutionResult(result);
4059
- if (hasVFs && result.success !== false) {
4060
- setInstanceMappingPhase(true);
4061
- dispatch({ type: "SET_PHASE", phase: PLAN_PHASE.COMPLETE });
4062
- } else {
4063
- dispatch({ type: "SET_PHASE", phase: PLAN_PHASE.COMPLETE });
4064
- }
4065
- }, [hasVFs]);
4066
- const handleInstanceMappingComplete = _react.useCallback.call(void 0, (result) => {
4067
- setInstanceMappingResult(result);
4068
- setInstanceMappingPhase(false);
4069
- }, []);
4070
- if (!open) return null;
4071
- const handleStepClick = _react.useCallback.call(void 0, (stepKey) => {
4072
- if (isExecuting) return;
4073
- if (stepKey === currentStep) return;
4074
- if (stepKey === 1 && currentStep > 1) {
4075
- if (Object.keys(plan.fonts).length > 0) {
4076
- if (!window.confirm("Go back to settings? Current review progress will be lost.")) return;
4077
- }
4078
- dispatch({ type: "SET_PHASE", phase: PLAN_PHASE.IDLE });
4079
- }
4080
- }, [currentStep, isExecuting, plan.fonts, dispatch]);
4081
- return /* @__PURE__ */ _react2.default.createElement(
4082
- _ui.Dialog,
4083
- {
4084
- id: "upload-modal",
4085
- header: /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { direction: "column", gap: 3, style: { width: "100%" } }, /* @__PURE__ */ _react2.default.createElement(_ui.Text, { weight: "semibold", size: 2 }, "Upload Fonts"), /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { gap: 1, style: { width: "100%" } }, STEPS.filter((step) => step.key !== 4 || hasVFs).map((step, i) => {
4086
- const isActive = currentStep === step.key;
4087
- const isCompleted = currentStep > step.key;
4088
- const isClickable = !isExecuting && step.key < currentStep;
4089
- return /* @__PURE__ */ _react2.default.createElement(
4090
- _ui.Box,
4091
- {
4092
- key: step.key,
4093
- as: isClickable ? "button" : "div",
4094
- onClick: isClickable ? () => handleStepClick(step.key) : void 0,
4095
- style: {
4096
- flex: 1,
4097
- padding: "10px 12px",
4098
- border: "none",
4099
- borderRadius: 4,
4100
- cursor: isClickable ? "pointer" : "default",
4101
- background: isActive ? "var(--card-badge-primary-bg-color)" : isCompleted ? "var(--card-badge-positive-bg-color)" : "var(--card-muted-bg-color)",
4102
- color: isActive || isCompleted ? "#fff" : "var(--card-muted-fg-color)",
4103
- textAlign: "center",
4104
- transition: "background 0.15s ease",
4105
- opacity: !isActive && !isCompleted ? 0.6 : 1
4106
- }
4107
- },
4108
- /* @__PURE__ */ _react2.default.createElement(
4109
- _ui.Text,
4110
- {
4111
- size: 1,
4112
- weight: isActive ? "bold" : "medium",
4113
- style: { color: "inherit" }
4114
- },
4115
- step.key,
4116
- ". ",
4117
- step.label
4118
- )
4119
- );
4120
- }))),
4121
- width: 2,
4122
- onClose: isExecuting ? void 0 : handleClose,
4123
- onClickOutside: () => {
4124
- }
4125
- },
4126
- /* @__PURE__ */ _react2.default.createElement(_ui.Box, { padding: 4 }, currentStep === 1 && /* @__PURE__ */ _react2.default.createElement(
4127
- UploadStep1Settings,
4128
- {
4129
- settings: plan.settings,
4130
- onStartProcessing: handleStartProcessing
4131
- }
4132
- ), currentStep === 2 && /* @__PURE__ */ _react2.default.createElement(
4133
- UploadStep2Review,
4134
- {
4135
- plan,
4136
- dispatch,
4137
- onCancelProcessing: handleCancelProcessing,
4138
- onStartExecution: handleStartExecution,
4139
- processingCancelled
4140
- }
4141
- ), currentStep === 3 && plan.phase !== PLAN_PHASE.COMPLETE && /* @__PURE__ */ _react2.default.createElement(
4142
- UploadStep3Execute,
4143
- {
4144
- key: retryTempIds ? "retry" : "initial",
4145
- plan,
4146
- client,
4147
- docId,
4148
- stylesObject,
4149
- preferredStyleRef,
4150
- retryTempIds,
4151
- onComplete: (result) => {
4152
- setRetryTempIds(null);
4153
- handleExecutionComplete(result);
4154
- }
4155
- }
4156
- ), plan.phase === PLAN_PHASE.COMPLETE && instanceMappingPhase && /* @__PURE__ */ _react2.default.createElement(
4157
- UploadStep3bInstances,
4158
- {
4159
- plan,
4160
- executionResult,
4161
- client,
4162
- typefaceTitle,
4163
- onComplete: handleInstanceMappingComplete
4164
- }
4165
- ), plan.phase === PLAN_PHASE.COMPLETE && !instanceMappingPhase && /* @__PURE__ */ _react2.default.createElement(
4166
- UploadSummary,
4167
- {
4168
- plan,
4169
- result: executionResult,
4170
- instanceMappingResult,
4171
- onClose: handleClose,
4172
- onRetry: (failedTempIds) => {
4173
- setRetryTempIds(failedTempIds || null);
4174
- setExecutionResult(null);
4175
- setInstanceMappingPhase(false);
4176
- setInstanceMappingResult(null);
4177
- dispatch({ type: "SET_PHASE", phase: PLAN_PHASE.EXECUTING });
4178
- },
4179
- client,
4180
- docId,
4181
- stylesObject,
4182
- preferredStyleRef
4183
- }
4184
- ))
4185
- );
4186
- }
4187
-
4188
-
4189
-
4190
-
4191
-
4192
-
4193
-
4194
-
4195
-
4196
-
4197
-
4198
-
4199
-
4200
-
4201
-
4202
-
4203
-
4204
-
4205
-
4206
-
4207
-
4208
-
4209
-
4210
-
4211
-
4212
-
4213
-
4214
-
4215
-
4216
-
4217
-
4218
-
4219
-
4220
-
4221
-
4222
-
4223
-
4224
-
4225
-
4226
-
4227
-
4228
-
4229
-
4230
-
4231
-
4232
-
4233
-
4234
-
4235
-
4236
-
4237
-
4238
-
4239
-
4240
-
4241
-
4242
-
4243
-
4244
-
4245
-
4246
-
4247
-
4248
-
4249
-
4250
- exports.parseFont = parseFont; exports.getNameString = getNameString; exports.getAllFeatureTags = getAllFeatureTags; exports.getCharacterSet = getCharacterSet; exports.getVariationAxes = getVariationAxes; exports.getNamedInstances = getNamedInstances; exports.getFontMetrics = getFontMetrics; exports.getFontMetadata = getFontMetadata; exports.getWeightClass = getWeightClass; exports.getFsSelection = getFsSelection; exports.getMacStyle = getMacStyle; exports.getItalicAngle = getItalicAngle; exports.getGlyphCount = getGlyphCount; exports.getFamilyClass = getFamilyClass; exports.escapeCssFontName = escapeCssFontName; exports.reverseSpellingLookup = reverseSpellingLookup; exports.expandAbbreviations = expandAbbreviations; exports.removeWeightNames = removeWeightNames; exports.generateStyleKeywords = generateStyleKeywords; exports.sanitizeForSanityId = sanitizeForSanityId; exports.readFontFile = readFontFile; exports.processFontFiles = processFontFiles; exports.extractFontMetadata = extractFontMetadata; exports.extractWeightName = extractWeightName; exports.extractWeightFromFullName = extractWeightFromFullName; exports.processSubfamilyName = processSubfamilyName; exports.processItalicKeywords = processItalicKeywords; exports.formatFontTitle = formatFontTitle; exports.addItalicToFontTitle = addItalicToFontTitle; exports.createFontObject = createFontObject; exports.determineWeight = determineWeight; exports.sortFontObjects = sortFontObjects; exports.logFontInfo = logFontInfo; exports.generateCssFile = generateCssFile; exports.generateFontData = generateFontData; exports.parseVariableFontInstances = parseVariableFontInstances; exports.parseVariableFontInstances_default = parseVariableFontInstances_default; exports.updateTypefaceDocument = updateTypefaceDocument; exports.PriceInput_default = PriceInput_default; exports.FONT_STATUS = FONT_STATUS; exports.PLAN_PHASE = PLAN_PHASE; exports.RECOMMENDATION = RECOMMENDATION; exports.EXECUTION_STATUS = EXECUTION_STATUS; exports.PLAN_VERSION = PLAN_VERSION; exports.createFontDecisions = createFontDecisions; exports.createEmptyPlan = createEmptyPlan; exports.planReducer = planReducer; exports.resolveExistingFont = resolveExistingFont; exports.buildUploadPlan = buildUploadPlan; exports.UploadStep1Settings = UploadStep1Settings; exports.ExistingDocumentResolver = ExistingDocumentResolver; exports.FontReviewCard_default = FontReviewCard_default; exports.BulkActions = BulkActions; exports.UploadStep2Review = UploadStep2Review; exports.executeUploadPlan = executeUploadPlan; exports.createInitialExecutionState = createInitialExecutionState; exports.executionReducer = executionReducer; exports.UploadStep3Execute = UploadStep3Execute; exports.UploadStep3bInstances = UploadStep3bInstances; exports.UploadSummary = UploadSummary; exports.UploadModal = UploadModal;