@package-uploader/ui 1.0.14 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/index-C19M6liw.css +1 -0
- package/dist/assets/index-Ca5beg0c.js +71 -0
- package/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/api/client.ts +129 -3
- package/src/components/CourseStructureStep.tsx +211 -0
- package/src/components/UploadModal.tsx +286 -101
- package/src/index.css +352 -1
- package/src/main.tsx +28 -1
- package/dist/assets/index-8HHKr2PX.css +0 -1
- package/dist/assets/index-Cur4iArP.js +0 -71
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useState } from 'react';
|
|
2
|
-
import { api } from '../api/client';
|
|
2
|
+
import { api, type CourseClassMappings } from '../api/client';
|
|
3
3
|
import DropZone from './DropZone';
|
|
4
|
+
import CourseStructureStep, { type CourseStructure } from './CourseStructureStep';
|
|
4
5
|
|
|
5
6
|
interface FileItem {
|
|
6
7
|
file: File;
|
|
@@ -26,15 +27,27 @@ export default function UploadModal({
|
|
|
26
27
|
const [files, setFiles] = useState<FileItem[]>([]);
|
|
27
28
|
const [uploading, setUploading] = useState(false);
|
|
28
29
|
|
|
30
|
+
// Course structure — on-demand, collapsible
|
|
31
|
+
const [courseStructure, setCourseStructure] = useState<CourseStructure | null>(null);
|
|
32
|
+
const [classMappings, setClassMappings] = useState<CourseClassMappings>({});
|
|
33
|
+
const [parsingStructure, setParsingStructure] = useState(false);
|
|
34
|
+
const [structurePanelOpen, setStructurePanelOpen] = useState(false);
|
|
35
|
+
const [parseFailed, setParseFailed] = useState(false);
|
|
36
|
+
|
|
29
37
|
// LMS Thin Pack options
|
|
30
|
-
const [createThinPack, setCreateThinPack] = useState(true);
|
|
38
|
+
const [createThinPack, setCreateThinPack] = useState(true);
|
|
31
39
|
const [thinPackName, setThinPackName] = useState('');
|
|
32
40
|
|
|
33
41
|
// Shared Link options
|
|
34
|
-
const [createSharedLink, setCreateSharedLink] = useState(false);
|
|
42
|
+
const [createSharedLink, setCreateSharedLink] = useState(false);
|
|
35
43
|
const [sharedLinkName, setSharedLinkName] = useState('');
|
|
36
44
|
|
|
37
|
-
//
|
|
45
|
+
// Skin option
|
|
46
|
+
const [skinName, setSkinName] = useState('');
|
|
47
|
+
|
|
48
|
+
// Wrap & Download mode
|
|
49
|
+
const [wrapOnly, setWrapOnly] = useState(false);
|
|
50
|
+
|
|
38
51
|
function generateSlug(name: string): string {
|
|
39
52
|
return name.trim().replace(/\s+/g, '_');
|
|
40
53
|
}
|
|
@@ -45,10 +58,54 @@ export default function UploadModal({
|
|
|
45
58
|
status: 'pending',
|
|
46
59
|
}));
|
|
47
60
|
setFiles((prev) => [...prev, ...fileItems]);
|
|
61
|
+
|
|
62
|
+
// Reset structure state when files change
|
|
63
|
+
setCourseStructure(null);
|
|
64
|
+
setClassMappings({});
|
|
65
|
+
setStructurePanelOpen(false);
|
|
66
|
+
setParseFailed(false);
|
|
48
67
|
}
|
|
49
68
|
|
|
50
69
|
function handleRemoveFile(index: number) {
|
|
51
|
-
setFiles((prev) =>
|
|
70
|
+
setFiles((prev) => {
|
|
71
|
+
const next = prev.filter((_, i) => i !== index);
|
|
72
|
+
if (next.length !== 1) {
|
|
73
|
+
setCourseStructure(null);
|
|
74
|
+
setClassMappings({});
|
|
75
|
+
setStructurePanelOpen(false);
|
|
76
|
+
setParseFailed(false);
|
|
77
|
+
}
|
|
78
|
+
return next;
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function handleCustomizeClick() {
|
|
83
|
+
// Already parsed — just toggle
|
|
84
|
+
if (courseStructure) {
|
|
85
|
+
setStructurePanelOpen((prev) => !prev);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Already failed — don't retry
|
|
90
|
+
if (parseFailed) return;
|
|
91
|
+
|
|
92
|
+
// Parse on first click
|
|
93
|
+
if (files.length !== 1) return;
|
|
94
|
+
|
|
95
|
+
setParsingStructure(true);
|
|
96
|
+
try {
|
|
97
|
+
const structure = await api.parseCourseStructure(files[0].file);
|
|
98
|
+
if (structure) {
|
|
99
|
+
setCourseStructure(structure);
|
|
100
|
+
setStructurePanelOpen(true);
|
|
101
|
+
setParseFailed(false);
|
|
102
|
+
} else {
|
|
103
|
+
setParseFailed(true);
|
|
104
|
+
}
|
|
105
|
+
} catch {
|
|
106
|
+
setParseFailed(true);
|
|
107
|
+
}
|
|
108
|
+
setParsingStructure(false);
|
|
52
109
|
}
|
|
53
110
|
|
|
54
111
|
function formatSize(bytes: number): string {
|
|
@@ -63,65 +120,91 @@ export default function UploadModal({
|
|
|
63
120
|
|
|
64
121
|
setUploading(true);
|
|
65
122
|
|
|
123
|
+
const hasClassAssignments =
|
|
124
|
+
classMappings.course ||
|
|
125
|
+
Object.keys(classMappings.lessons || {}).length > 0 ||
|
|
126
|
+
Object.keys(classMappings.blocks || {}).length > 0;
|
|
127
|
+
|
|
66
128
|
for (let i = 0; i < files.length; i++) {
|
|
67
129
|
if (files[i].status !== 'pending') continue;
|
|
68
130
|
|
|
69
|
-
// Mark as uploading
|
|
70
131
|
setFiles((prev) =>
|
|
71
132
|
prev.map((f, idx) => (idx === i ? { ...f, status: 'uploading' } : f))
|
|
72
133
|
);
|
|
73
134
|
|
|
74
135
|
try {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
// 2. Create LMS Thin Pack if enabled
|
|
80
|
-
if (createThinPack) {
|
|
81
|
-
const name = thinPackName.trim() || `${result.documentName} - LMS Thin Pack`;
|
|
82
|
-
try {
|
|
83
|
-
await api.createSharedLink(result.documentId, {
|
|
84
|
-
name,
|
|
85
|
-
token: generateSlug(name),
|
|
86
|
-
isPublic: true,
|
|
87
|
-
isForThinPackage: true,
|
|
88
|
-
});
|
|
89
|
-
} catch (err) {
|
|
90
|
-
console.error('Failed to create thin pack:', err);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
136
|
+
if (wrapOnly) {
|
|
137
|
+
const wrapOptions: { skin?: string; classMappings?: CourseClassMappings } = {};
|
|
138
|
+
if (skinName.trim()) wrapOptions.skin = skinName.trim();
|
|
139
|
+
if (hasClassAssignments) wrapOptions.classMappings = classMappings;
|
|
93
140
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
await api.createSharedLink(result.documentId, {
|
|
99
|
-
name,
|
|
100
|
-
token: generateSlug(name),
|
|
101
|
-
isPublic: false,
|
|
102
|
-
isForThinPackage: false,
|
|
103
|
-
});
|
|
104
|
-
} catch (err) {
|
|
105
|
-
console.error('Failed to create shared link:', err);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
141
|
+
await api.wrapAndDownload(
|
|
142
|
+
files[i].file,
|
|
143
|
+
Object.keys(wrapOptions).length > 0 ? wrapOptions : undefined
|
|
144
|
+
);
|
|
108
145
|
|
|
109
146
|
setFiles((prev) =>
|
|
110
147
|
prev.map((f, idx) =>
|
|
111
|
-
idx === i
|
|
112
|
-
? {
|
|
113
|
-
...f,
|
|
114
|
-
status: 'success',
|
|
115
|
-
documentId: result.documentId ?? undefined,
|
|
116
|
-
documentName: result.documentName,
|
|
117
|
-
}
|
|
118
|
-
: f
|
|
148
|
+
idx === i ? { ...f, status: 'success' } : f
|
|
119
149
|
)
|
|
120
150
|
);
|
|
121
|
-
|
|
122
|
-
onUploadComplete(result.documentId);
|
|
123
151
|
} else {
|
|
124
|
-
|
|
152
|
+
const uploadOptions: { title?: string; skin?: string; classMappings?: CourseClassMappings } = {};
|
|
153
|
+
if (skinName.trim()) uploadOptions.skin = skinName.trim();
|
|
154
|
+
if (hasClassAssignments) uploadOptions.classMappings = classMappings;
|
|
155
|
+
|
|
156
|
+
const result = await api.uploadCourse(
|
|
157
|
+
files[i].file,
|
|
158
|
+
folderId,
|
|
159
|
+
Object.keys(uploadOptions).length > 0 ? uploadOptions : undefined
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
if (result.success && result.documentId) {
|
|
163
|
+
if (createThinPack) {
|
|
164
|
+
const name = thinPackName.trim() || `${result.documentName} - LMS Thin Pack`;
|
|
165
|
+
try {
|
|
166
|
+
await api.createSharedLink(result.documentId, {
|
|
167
|
+
name,
|
|
168
|
+
token: generateSlug(name),
|
|
169
|
+
isPublic: true,
|
|
170
|
+
isForThinPackage: true,
|
|
171
|
+
});
|
|
172
|
+
} catch (err) {
|
|
173
|
+
console.error('Failed to create thin pack:', err);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (createSharedLink) {
|
|
178
|
+
const name = sharedLinkName.trim() || `${result.documentName} - Private`;
|
|
179
|
+
try {
|
|
180
|
+
await api.createSharedLink(result.documentId, {
|
|
181
|
+
name,
|
|
182
|
+
token: generateSlug(name),
|
|
183
|
+
isPublic: false,
|
|
184
|
+
isForThinPackage: false,
|
|
185
|
+
});
|
|
186
|
+
} catch (err) {
|
|
187
|
+
console.error('Failed to create shared link:', err);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
setFiles((prev) =>
|
|
192
|
+
prev.map((f, idx) =>
|
|
193
|
+
idx === i
|
|
194
|
+
? {
|
|
195
|
+
...f,
|
|
196
|
+
status: 'success',
|
|
197
|
+
documentId: result.documentId ?? undefined,
|
|
198
|
+
documentName: result.documentName,
|
|
199
|
+
}
|
|
200
|
+
: f
|
|
201
|
+
)
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
onUploadComplete(result.documentId);
|
|
205
|
+
} else {
|
|
206
|
+
throw new Error(result.errors?.join(', ') || 'Upload failed');
|
|
207
|
+
}
|
|
125
208
|
}
|
|
126
209
|
} catch (err) {
|
|
127
210
|
setFiles((prev) =>
|
|
@@ -130,7 +213,7 @@ export default function UploadModal({
|
|
|
130
213
|
? {
|
|
131
214
|
...f,
|
|
132
215
|
status: 'error',
|
|
133
|
-
error: err instanceof Error ? err.message : 'Upload failed',
|
|
216
|
+
error: err instanceof Error ? err.message : wrapOnly ? 'Wrap failed' : 'Upload failed',
|
|
134
217
|
}
|
|
135
218
|
: f
|
|
136
219
|
)
|
|
@@ -143,8 +226,8 @@ export default function UploadModal({
|
|
|
143
226
|
|
|
144
227
|
const pendingCount = files.filter((f) => f.status === 'pending').length;
|
|
145
228
|
const hasFiles = files.length > 0;
|
|
229
|
+
const isSingleFile = files.length === 1;
|
|
146
230
|
|
|
147
|
-
// Preview slugs
|
|
148
231
|
const thinPackSlugPreview = thinPackName.trim()
|
|
149
232
|
? generateSlug(thinPackName)
|
|
150
233
|
: '(will use document name)';
|
|
@@ -152,11 +235,28 @@ export default function UploadModal({
|
|
|
152
235
|
? generateSlug(sharedLinkName)
|
|
153
236
|
: '(will use document name)';
|
|
154
237
|
|
|
238
|
+
// Class assignment count for summary
|
|
239
|
+
const assignmentCount =
|
|
240
|
+
(classMappings.course ? 1 : 0) +
|
|
241
|
+
Object.keys(classMappings.lessons || {}).length +
|
|
242
|
+
Object.keys(classMappings.blocks || {}).length;
|
|
243
|
+
|
|
244
|
+
// Customize button label
|
|
245
|
+
const customizeLabel = parsingStructure
|
|
246
|
+
? 'Analyzing...'
|
|
247
|
+
: parseFailed
|
|
248
|
+
? 'Not a Rise course'
|
|
249
|
+
: courseStructure
|
|
250
|
+
? `Customize Classes (${courseStructure.lessons.length} lessons)`
|
|
251
|
+
: 'Customize Classes';
|
|
252
|
+
|
|
155
253
|
return (
|
|
156
254
|
<div className="modal-overlay" onClick={onClose}>
|
|
157
255
|
<div className="modal upload-modal" onClick={(e) => e.stopPropagation()}>
|
|
158
256
|
<div className="modal-header">
|
|
159
|
-
<h3 className="modal-title">
|
|
257
|
+
<h3 className="modal-title">
|
|
258
|
+
{wrapOnly ? 'Wrap & Download' : `Upload to: ${folderName}`}
|
|
259
|
+
</h3>
|
|
160
260
|
<button className="modal-close" onClick={onClose}>
|
|
161
261
|
x
|
|
162
262
|
</button>
|
|
@@ -178,24 +278,21 @@ export default function UploadModal({
|
|
|
178
278
|
</div>
|
|
179
279
|
<div className="upload-file-status">
|
|
180
280
|
{item.status === 'pending' && (
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
Remove
|
|
188
|
-
</button>
|
|
189
|
-
</>
|
|
281
|
+
<button
|
|
282
|
+
className="btn btn-sm btn-secondary"
|
|
283
|
+
onClick={() => handleRemoveFile(index)}
|
|
284
|
+
>
|
|
285
|
+
Remove
|
|
286
|
+
</button>
|
|
190
287
|
)}
|
|
191
288
|
{item.status === 'uploading' && (
|
|
192
289
|
<>
|
|
193
290
|
<div className="spinner" />
|
|
194
|
-
<span className="status-uploading">Uploading
|
|
291
|
+
<span className="status-uploading">{wrapOnly ? 'Wrapping...' : 'Uploading...'}</span>
|
|
195
292
|
</>
|
|
196
293
|
)}
|
|
197
294
|
{item.status === 'success' && (
|
|
198
|
-
<span className="status-success">Uploaded</span>
|
|
295
|
+
<span className="status-success">{wrapOnly ? 'Downloaded' : 'Uploaded'}</span>
|
|
199
296
|
)}
|
|
200
297
|
{item.status === 'error' && (
|
|
201
298
|
<span className="status-error" title={item.error}>
|
|
@@ -208,60 +305,146 @@ export default function UploadModal({
|
|
|
208
305
|
</div>
|
|
209
306
|
)}
|
|
210
307
|
|
|
211
|
-
{/*
|
|
212
|
-
|
|
213
|
-
<
|
|
308
|
+
{/* Customize Classes — on-demand toggle */}
|
|
309
|
+
{isSingleFile && !uploading && (
|
|
310
|
+
<div className="structure-section">
|
|
311
|
+
<button
|
|
312
|
+
className={`structure-toggle-btn${structurePanelOpen ? ' open' : ''}${parseFailed ? ' disabled' : ''}`}
|
|
313
|
+
onClick={handleCustomizeClick}
|
|
314
|
+
disabled={parsingStructure || parseFailed}
|
|
315
|
+
>
|
|
316
|
+
{parsingStructure && <div className="spinner" />}
|
|
317
|
+
<span>{customizeLabel}</span>
|
|
318
|
+
{!parsingStructure && !parseFailed && (
|
|
319
|
+
<span className="structure-toggle-arrow">
|
|
320
|
+
{structurePanelOpen ? '\u25B2' : '\u25BC'}
|
|
321
|
+
</span>
|
|
322
|
+
)}
|
|
323
|
+
{assignmentCount > 0 && !structurePanelOpen && (
|
|
324
|
+
<span className="course-structure-badge">{assignmentCount} assigned</span>
|
|
325
|
+
)}
|
|
326
|
+
</button>
|
|
214
327
|
|
|
215
|
-
|
|
328
|
+
{structurePanelOpen && courseStructure && (
|
|
329
|
+
<div className="structure-panel">
|
|
330
|
+
<CourseStructureStep
|
|
331
|
+
structure={courseStructure}
|
|
332
|
+
classMappings={classMappings}
|
|
333
|
+
onChange={setClassMappings}
|
|
334
|
+
/>
|
|
335
|
+
</div>
|
|
336
|
+
)}
|
|
337
|
+
</div>
|
|
338
|
+
)}
|
|
339
|
+
|
|
340
|
+
{/* Options */}
|
|
341
|
+
<div className="upload-link-options">
|
|
342
|
+
{/* Wrap & Download toggle */}
|
|
216
343
|
<div className="upload-link-option">
|
|
217
344
|
<label className="upload-link-toggle">
|
|
218
345
|
<input
|
|
219
346
|
type="checkbox"
|
|
220
|
-
checked={
|
|
221
|
-
onChange={(e) =>
|
|
347
|
+
checked={wrapOnly}
|
|
348
|
+
onChange={(e) => setWrapOnly(e.target.checked)}
|
|
222
349
|
disabled={uploading}
|
|
223
350
|
/>
|
|
224
|
-
<span>
|
|
351
|
+
<span>Wrap & Download only (skip CDS upload)</span>
|
|
225
352
|
</label>
|
|
226
|
-
{
|
|
227
|
-
<
|
|
228
|
-
<input
|
|
229
|
-
type="text"
|
|
230
|
-
placeholder="Link name (uses document name if empty)"
|
|
231
|
-
value={thinPackName}
|
|
232
|
-
onChange={(e) => setThinPackName(e.target.value)}
|
|
233
|
-
disabled={uploading}
|
|
234
|
-
/>
|
|
235
|
-
<span className="slug-preview">Slug: {thinPackSlugPreview}</span>
|
|
236
|
-
</div>
|
|
353
|
+
{wrapOnly && (
|
|
354
|
+
<span className="slug-preview">Wraps with PA-Patcher and downloads the ZIP directly</span>
|
|
237
355
|
)}
|
|
238
356
|
</div>
|
|
239
357
|
|
|
240
|
-
{/*
|
|
241
|
-
|
|
358
|
+
{/* CDS upload options */}
|
|
359
|
+
{!wrapOnly && (
|
|
360
|
+
<>
|
|
361
|
+
<h4>After upload, create:</h4>
|
|
362
|
+
|
|
363
|
+
<div className="upload-link-option">
|
|
364
|
+
<label className="upload-link-toggle">
|
|
365
|
+
<input
|
|
366
|
+
type="checkbox"
|
|
367
|
+
checked={createThinPack}
|
|
368
|
+
onChange={(e) => setCreateThinPack(e.target.checked)}
|
|
369
|
+
disabled={uploading}
|
|
370
|
+
/>
|
|
371
|
+
<span>LMS Thin Pack (public)</span>
|
|
372
|
+
</label>
|
|
373
|
+
{createThinPack && (
|
|
374
|
+
<div className="upload-link-name-input">
|
|
375
|
+
<input
|
|
376
|
+
type="text"
|
|
377
|
+
placeholder="Link name (uses document name if empty)"
|
|
378
|
+
value={thinPackName}
|
|
379
|
+
onChange={(e) => setThinPackName(e.target.value)}
|
|
380
|
+
disabled={uploading}
|
|
381
|
+
/>
|
|
382
|
+
<span className="slug-preview">Slug: {thinPackSlugPreview}</span>
|
|
383
|
+
</div>
|
|
384
|
+
)}
|
|
385
|
+
</div>
|
|
386
|
+
|
|
387
|
+
<div className="upload-link-option">
|
|
388
|
+
<label className="upload-link-toggle">
|
|
389
|
+
<input
|
|
390
|
+
type="checkbox"
|
|
391
|
+
checked={createSharedLink}
|
|
392
|
+
onChange={(e) => setCreateSharedLink(e.target.checked)}
|
|
393
|
+
disabled={uploading}
|
|
394
|
+
/>
|
|
395
|
+
<span>Shared Link (private/SSO)</span>
|
|
396
|
+
</label>
|
|
397
|
+
{createSharedLink && (
|
|
398
|
+
<div className="upload-link-name-input">
|
|
399
|
+
<input
|
|
400
|
+
type="text"
|
|
401
|
+
placeholder="Link name (uses document name if empty)"
|
|
402
|
+
value={sharedLinkName}
|
|
403
|
+
onChange={(e) => setSharedLinkName(e.target.value)}
|
|
404
|
+
disabled={uploading}
|
|
405
|
+
/>
|
|
406
|
+
<span className="slug-preview">Slug: {sharedLinkSlugPreview}</span>
|
|
407
|
+
</div>
|
|
408
|
+
)}
|
|
409
|
+
</div>
|
|
410
|
+
</>
|
|
411
|
+
)}
|
|
412
|
+
|
|
413
|
+
{/* Skin */}
|
|
414
|
+
<div className="upload-link-option" style={{ marginTop: '12px' }}>
|
|
242
415
|
<label className="upload-link-toggle">
|
|
416
|
+
<span>Skin (optional)</span>
|
|
417
|
+
</label>
|
|
418
|
+
<div className="upload-link-name-input">
|
|
243
419
|
<input
|
|
244
|
-
type="
|
|
245
|
-
|
|
246
|
-
|
|
420
|
+
type="text"
|
|
421
|
+
placeholder="e.g. marketing"
|
|
422
|
+
value={skinName}
|
|
423
|
+
onChange={(e) => setSkinName(e.target.value)}
|
|
247
424
|
disabled={uploading}
|
|
248
425
|
/>
|
|
249
|
-
<span>
|
|
250
|
-
</
|
|
251
|
-
{createSharedLink && (
|
|
252
|
-
<div className="upload-link-name-input">
|
|
253
|
-
<input
|
|
254
|
-
type="text"
|
|
255
|
-
placeholder="Link name (uses document name if empty)"
|
|
256
|
-
value={sharedLinkName}
|
|
257
|
-
onChange={(e) => setSharedLinkName(e.target.value)}
|
|
258
|
-
disabled={uploading}
|
|
259
|
-
/>
|
|
260
|
-
<span className="slug-preview">Slug: {sharedLinkSlugPreview}</span>
|
|
261
|
-
</div>
|
|
262
|
-
)}
|
|
426
|
+
<span className="slug-preview">CSS class for custom skin styling</span>
|
|
427
|
+
</div>
|
|
263
428
|
</div>
|
|
264
429
|
</div>
|
|
430
|
+
|
|
431
|
+
{/* Class assignments summary */}
|
|
432
|
+
{assignmentCount > 0 && !structurePanelOpen && (
|
|
433
|
+
<div className="structure-summary-badge">
|
|
434
|
+
Class assignments:{' '}
|
|
435
|
+
{[
|
|
436
|
+
classMappings.course ? '1 course' : '',
|
|
437
|
+
Object.keys(classMappings.lessons || {}).length > 0
|
|
438
|
+
? `${Object.keys(classMappings.lessons || {}).length} lesson(s)`
|
|
439
|
+
: '',
|
|
440
|
+
Object.keys(classMappings.blocks || {}).length > 0
|
|
441
|
+
? `${Object.keys(classMappings.blocks || {}).length} block(s)`
|
|
442
|
+
: '',
|
|
443
|
+
]
|
|
444
|
+
.filter(Boolean)
|
|
445
|
+
.join(', ')}
|
|
446
|
+
</div>
|
|
447
|
+
)}
|
|
265
448
|
</div>
|
|
266
449
|
|
|
267
450
|
<div className="modal-footer">
|
|
@@ -276,8 +459,10 @@ export default function UploadModal({
|
|
|
276
459
|
{uploading ? (
|
|
277
460
|
<>
|
|
278
461
|
<div className="spinner" />
|
|
279
|
-
Uploading...
|
|
462
|
+
{wrapOnly ? 'Wrapping...' : 'Uploading...'}
|
|
280
463
|
</>
|
|
464
|
+
) : wrapOnly ? (
|
|
465
|
+
`Wrap & Download ${pendingCount} file${pendingCount !== 1 ? 's' : ''}`
|
|
281
466
|
) : (
|
|
282
467
|
`Upload ${pendingCount} file${pendingCount !== 1 ? 's' : ''}`
|
|
283
468
|
)}
|