eprec 1.11.0 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/assets/styles.css +287 -0
- package/app/client/app.tsx +11 -2
- package/app/client/editing-workspace.tsx +283 -78
- package/app/client/trim-points.tsx +1287 -0
- package/app/config/routes.ts +1 -0
- package/app/router.tsx +6 -0
- package/app/routes/index.tsx +3 -0
- package/app/routes/trim-points.tsx +51 -0
- package/app/trim-api.ts +261 -0
- package/app/trim-commands.ts +154 -0
- package/package.json +1 -1
- package/server/processing-queue.ts +441 -0
- package/src/app-server.ts +8 -0
- package/src/cli.ts +8 -11
|
@@ -23,6 +23,8 @@ const MIN_CUT_LENGTH = 0.2
|
|
|
23
23
|
const DEFAULT_CUT_LENGTH = 2.4
|
|
24
24
|
const PLAYHEAD_STEP = 0.1
|
|
25
25
|
const DEFAULT_PREVIEW_URL = '/e2e-test.mp4'
|
|
26
|
+
const QUEUE_API_BASE = '/api/processing-queue'
|
|
27
|
+
const QUEUE_STREAM_URL = `${QUEUE_API_BASE}/stream`
|
|
26
28
|
|
|
27
29
|
function readInitialVideoPath() {
|
|
28
30
|
if (typeof window === 'undefined') return ''
|
|
@@ -42,14 +44,36 @@ function extractVideoName(value: string) {
|
|
|
42
44
|
return last && last.length > 0 ? last : value
|
|
43
45
|
}
|
|
44
46
|
|
|
45
|
-
type ProcessingStatus = 'queued' | 'running' | 'done'
|
|
47
|
+
type ProcessingStatus = 'queued' | 'running' | 'done' | 'error'
|
|
46
48
|
type ProcessingCategory = 'chapter' | 'transcript' | 'export'
|
|
49
|
+
type ProcessingAction =
|
|
50
|
+
| 'edit-chapter'
|
|
51
|
+
| 'combine-chapters'
|
|
52
|
+
| 'regenerate-transcript'
|
|
53
|
+
| 'detect-command-windows'
|
|
54
|
+
| 'render-preview'
|
|
55
|
+
| 'export-final'
|
|
56
|
+
type ProcessingProgress = {
|
|
57
|
+
step: number
|
|
58
|
+
totalSteps: number
|
|
59
|
+
label: string
|
|
60
|
+
percent: number
|
|
61
|
+
}
|
|
47
62
|
type ProcessingTask = {
|
|
48
63
|
id: string
|
|
49
64
|
title: string
|
|
50
65
|
detail: string
|
|
51
66
|
status: ProcessingStatus
|
|
52
67
|
category: ProcessingCategory
|
|
68
|
+
action: ProcessingAction
|
|
69
|
+
progress?: ProcessingProgress
|
|
70
|
+
errorMessage?: string
|
|
71
|
+
updatedAt: number
|
|
72
|
+
createdAt: number
|
|
73
|
+
}
|
|
74
|
+
type ProcessingQueueSnapshot = {
|
|
75
|
+
tasks: ProcessingTask[]
|
|
76
|
+
activeTaskId: string | null
|
|
53
77
|
}
|
|
54
78
|
|
|
55
79
|
export function EditingWorkspace(handle: Handle) {
|
|
@@ -79,8 +103,9 @@ export function EditingWorkspace(handle: Handle) {
|
|
|
79
103
|
let primaryChapterId = chapters[0]?.id ?? ''
|
|
80
104
|
let secondaryChapterId = chapters[1]?.id ?? chapters[0]?.id ?? ''
|
|
81
105
|
let processingQueue: ProcessingTask[] = []
|
|
82
|
-
let
|
|
83
|
-
let
|
|
106
|
+
let queueLoading = true
|
|
107
|
+
let queueError = ''
|
|
108
|
+
let queueStreamStatus: 'connecting' | 'open' | 'error' = 'connecting'
|
|
84
109
|
let manualCutId = 1
|
|
85
110
|
let previewDuration = 0
|
|
86
111
|
let previewReady = false
|
|
@@ -173,6 +198,39 @@ export function EditingWorkspace(handle: Handle) {
|
|
|
173
198
|
})
|
|
174
199
|
}
|
|
175
200
|
|
|
201
|
+
const loadQueueSnapshot = async () => {
|
|
202
|
+
await requestQueue('', { method: 'GET' })
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const connectQueueStream = () => {
|
|
206
|
+
if (typeof window === 'undefined') return
|
|
207
|
+
const stream = new EventSource(QUEUE_STREAM_URL)
|
|
208
|
+
const updateStatus = () => {
|
|
209
|
+
queueStreamStatus =
|
|
210
|
+
stream.readyState === EventSource.OPEN
|
|
211
|
+
? 'open'
|
|
212
|
+
: stream.readyState === EventSource.CONNECTING
|
|
213
|
+
? 'connecting'
|
|
214
|
+
: 'error'
|
|
215
|
+
handle.update()
|
|
216
|
+
}
|
|
217
|
+
stream.addEventListener('open', updateStatus)
|
|
218
|
+
stream.addEventListener('error', updateStatus)
|
|
219
|
+
stream.addEventListener('snapshot', (event) => {
|
|
220
|
+
try {
|
|
221
|
+
const snapshot = JSON.parse(
|
|
222
|
+
(event as MessageEvent<string>).data,
|
|
223
|
+
) as ProcessingQueueSnapshot
|
|
224
|
+
applyQueueSnapshot(snapshot)
|
|
225
|
+
} catch (error) {
|
|
226
|
+
setQueueError('Unable to parse queue updates.')
|
|
227
|
+
}
|
|
228
|
+
})
|
|
229
|
+
handle.signal.addEventListener('abort', () => {
|
|
230
|
+
stream.close()
|
|
231
|
+
})
|
|
232
|
+
}
|
|
233
|
+
|
|
176
234
|
if (initialVideoPath) {
|
|
177
235
|
void loadVideoFromPath(initialVideoPath)
|
|
178
236
|
}
|
|
@@ -309,26 +367,67 @@ export function EditingWorkspace(handle: Handle) {
|
|
|
309
367
|
handle.update()
|
|
310
368
|
}
|
|
311
369
|
|
|
370
|
+
const applyQueueSnapshot = (snapshot: ProcessingQueueSnapshot) => {
|
|
371
|
+
processingQueue = Array.isArray(snapshot.tasks) ? snapshot.tasks : []
|
|
372
|
+
queueLoading = false
|
|
373
|
+
queueError = ''
|
|
374
|
+
handle.update()
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const setQueueError = (message: string) => {
|
|
378
|
+
queueError = message
|
|
379
|
+
queueLoading = false
|
|
380
|
+
handle.update()
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const requestQueue = async (
|
|
384
|
+
path: string,
|
|
385
|
+
options: RequestInit & { body?: string } = {},
|
|
386
|
+
) => {
|
|
387
|
+
try {
|
|
388
|
+
const response = await fetch(`${QUEUE_API_BASE}${path}`, {
|
|
389
|
+
...options,
|
|
390
|
+
headers: {
|
|
391
|
+
Accept: 'application/json',
|
|
392
|
+
...(options.body ? { 'Content-Type': 'application/json' } : {}),
|
|
393
|
+
},
|
|
394
|
+
signal: handle.signal,
|
|
395
|
+
})
|
|
396
|
+
if (!response.ok) {
|
|
397
|
+
throw new Error(`Queue request failed (${response.status}).`)
|
|
398
|
+
}
|
|
399
|
+
const snapshot = (await response.json()) as ProcessingQueueSnapshot
|
|
400
|
+
applyQueueSnapshot(snapshot)
|
|
401
|
+
} catch (error) {
|
|
402
|
+
if (handle.signal.aborted) return
|
|
403
|
+
setQueueError(
|
|
404
|
+
error instanceof Error
|
|
405
|
+
? error.message
|
|
406
|
+
: 'Unable to reach the processing queue.',
|
|
407
|
+
)
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
void loadQueueSnapshot()
|
|
412
|
+
connectQueueStream()
|
|
413
|
+
|
|
312
414
|
const queueTask = (
|
|
415
|
+
action: ProcessingAction,
|
|
313
416
|
title: string,
|
|
314
417
|
detail: string,
|
|
315
418
|
category: ProcessingCategory,
|
|
316
419
|
) => {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
title,
|
|
320
|
-
|
|
321
|
-
status: 'queued',
|
|
322
|
-
category,
|
|
323
|
-
}
|
|
324
|
-
processingQueue = [...processingQueue, task]
|
|
325
|
-
handle.update()
|
|
420
|
+
void requestQueue('/enqueue', {
|
|
421
|
+
method: 'POST',
|
|
422
|
+
body: JSON.stringify({ action, title, detail, category }),
|
|
423
|
+
})
|
|
326
424
|
}
|
|
327
425
|
|
|
328
426
|
const queueChapterEdit = () => {
|
|
329
427
|
const chapter = findChapter(primaryChapterId)
|
|
330
428
|
if (!chapter) return
|
|
331
429
|
queueTask(
|
|
430
|
+
'edit-chapter',
|
|
332
431
|
`Edit ${chapter.title}`,
|
|
333
432
|
`Review trims for ${formatTimestamp(chapter.start)} - ${formatTimestamp(
|
|
334
433
|
chapter.end,
|
|
@@ -342,6 +441,7 @@ export function EditingWorkspace(handle: Handle) {
|
|
|
342
441
|
const secondary = findChapter(secondaryChapterId)
|
|
343
442
|
if (!primary || !secondary || primary.id === secondary.id) return
|
|
344
443
|
queueTask(
|
|
444
|
+
'combine-chapters',
|
|
345
445
|
`Combine ${primary.title} + ${secondary.title}`,
|
|
346
446
|
'Merge both chapters into a single preview export.',
|
|
347
447
|
'chapter',
|
|
@@ -350,6 +450,7 @@ export function EditingWorkspace(handle: Handle) {
|
|
|
350
450
|
|
|
351
451
|
const queueTranscriptRegeneration = () => {
|
|
352
452
|
queueTask(
|
|
453
|
+
'regenerate-transcript',
|
|
353
454
|
'Regenerate transcript',
|
|
354
455
|
'Run Whisper alignment and refresh search cues.',
|
|
355
456
|
'transcript',
|
|
@@ -358,6 +459,7 @@ export function EditingWorkspace(handle: Handle) {
|
|
|
358
459
|
|
|
359
460
|
const queueCommandScan = () => {
|
|
360
461
|
queueTask(
|
|
462
|
+
'detect-command-windows',
|
|
361
463
|
'Detect command windows',
|
|
362
464
|
'Scan for Jarvis commands and update cut ranges.',
|
|
363
465
|
'transcript',
|
|
@@ -366,6 +468,7 @@ export function EditingWorkspace(handle: Handle) {
|
|
|
366
468
|
|
|
367
469
|
const queuePreviewRender = () => {
|
|
368
470
|
queueTask(
|
|
471
|
+
'render-preview',
|
|
369
472
|
'Render preview clip',
|
|
370
473
|
'Bake a short MP4 with current edits applied.',
|
|
371
474
|
'export',
|
|
@@ -374,6 +477,7 @@ export function EditingWorkspace(handle: Handle) {
|
|
|
374
477
|
|
|
375
478
|
const queueFinalExport = () => {
|
|
376
479
|
queueTask(
|
|
480
|
+
'export-final',
|
|
377
481
|
'Export edited chapters',
|
|
378
482
|
'Render final chapters and write the export package.',
|
|
379
483
|
'export',
|
|
@@ -381,36 +485,25 @@ export function EditingWorkspace(handle: Handle) {
|
|
|
381
485
|
}
|
|
382
486
|
|
|
383
487
|
const startNextTask = () => {
|
|
384
|
-
|
|
385
|
-
const next = processingQueue.find((task) => task.status === 'queued')
|
|
386
|
-
if (!next) return
|
|
387
|
-
activeTaskId = next.id
|
|
388
|
-
processingQueue = processingQueue.map((task) =>
|
|
389
|
-
task.id === next.id ? { ...task, status: 'running' } : task,
|
|
390
|
-
)
|
|
391
|
-
handle.update()
|
|
488
|
+
void requestQueue('/run-next', { method: 'POST' })
|
|
392
489
|
}
|
|
393
490
|
|
|
394
491
|
const markActiveDone = () => {
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
492
|
+
void requestQueue('/mark-done', { method: 'POST' })
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
const cancelActiveTask = (taskId: string) => {
|
|
496
|
+
void requestQueue(`/task/${encodeURIComponent(taskId)}`, {
|
|
497
|
+
method: 'DELETE',
|
|
498
|
+
})
|
|
401
499
|
}
|
|
402
500
|
|
|
403
501
|
const clearCompletedTasks = () => {
|
|
404
|
-
|
|
405
|
-
handle.update()
|
|
502
|
+
void requestQueue('/clear-completed', { method: 'POST' })
|
|
406
503
|
}
|
|
407
504
|
|
|
408
505
|
const removeTask = (taskId: string) => {
|
|
409
|
-
|
|
410
|
-
if (activeTaskId === taskId) {
|
|
411
|
-
activeTaskId = null
|
|
412
|
-
}
|
|
413
|
-
handle.update()
|
|
506
|
+
cancelActiveTask(taskId)
|
|
414
507
|
}
|
|
415
508
|
|
|
416
509
|
const syncVideoToPlayhead = (value: number) => {
|
|
@@ -460,8 +553,28 @@ export function EditingWorkspace(handle: Handle) {
|
|
|
460
553
|
const completedCount = processingQueue.filter(
|
|
461
554
|
(task) => task.status === 'done',
|
|
462
555
|
).length
|
|
556
|
+
const errorCount = processingQueue.filter(
|
|
557
|
+
(task) => task.status === 'error',
|
|
558
|
+
).length
|
|
463
559
|
const runningTask =
|
|
464
560
|
processingQueue.find((task) => task.status === 'running') ?? null
|
|
561
|
+
const queueStreamMeta =
|
|
562
|
+
queueStreamStatus === 'open'
|
|
563
|
+
? { label: 'Live', className: 'status-pill--success' }
|
|
564
|
+
: queueStreamStatus === 'connecting'
|
|
565
|
+
? { label: 'Connecting', className: 'status-pill--warning' }
|
|
566
|
+
: { label: 'Offline', className: 'status-pill--danger' }
|
|
567
|
+
const runningSummary = runningTask
|
|
568
|
+
? runningTask.progress?.label
|
|
569
|
+
? `Running: ${runningTask.title} · ${runningTask.progress.label}`
|
|
570
|
+
: `Running: ${runningTask.title}`
|
|
571
|
+
: 'Idle'
|
|
572
|
+
const queueStreamSummary =
|
|
573
|
+
queueStreamStatus === 'open'
|
|
574
|
+
? 'Queue updates are live.'
|
|
575
|
+
: queueStreamStatus === 'connecting'
|
|
576
|
+
? 'Connecting to queue updates.'
|
|
577
|
+
: 'Queue updates are offline.'
|
|
465
578
|
const canCombineChapters =
|
|
466
579
|
primaryChapterId.length > 0 &&
|
|
467
580
|
secondaryChapterId.length > 0 &&
|
|
@@ -492,6 +605,11 @@ export function EditingWorkspace(handle: Handle) {
|
|
|
492
605
|
Review transcript-based edits, refine command windows, and prepare
|
|
493
606
|
the final CLI export in one place.
|
|
494
607
|
</p>
|
|
608
|
+
<nav class="app-nav">
|
|
609
|
+
<a class="app-link" href="/trim-points">
|
|
610
|
+
Trim points
|
|
611
|
+
</a>
|
|
612
|
+
</nav>
|
|
495
613
|
</header>
|
|
496
614
|
|
|
497
615
|
<section class="app-card app-card--full source-card">
|
|
@@ -612,16 +730,34 @@ export function EditingWorkspace(handle: Handle) {
|
|
|
612
730
|
<div class="summary-item">
|
|
613
731
|
<span class="summary-label">Queue</span>
|
|
614
732
|
<span class="summary-value">{queuedCount} queued</span>
|
|
733
|
+
<span class="summary-subtext">{runningSummary}</span>
|
|
734
|
+
</div>
|
|
735
|
+
<div class="summary-item">
|
|
736
|
+
<span class="summary-label">Errors</span>
|
|
737
|
+
<span class="summary-value">{errorCount}</span>
|
|
615
738
|
<span class="summary-subtext">
|
|
616
|
-
{
|
|
739
|
+
{errorCount > 0
|
|
740
|
+
? 'Review failed tasks below.'
|
|
741
|
+
: 'No failures yet.'}
|
|
742
|
+
</span>
|
|
743
|
+
</div>
|
|
744
|
+
<div class="summary-item">
|
|
745
|
+
<span class="summary-label">Stream</span>
|
|
746
|
+
<span
|
|
747
|
+
class={classNames('status-pill', queueStreamMeta.className)}
|
|
748
|
+
>
|
|
749
|
+
{queueStreamMeta.label}
|
|
617
750
|
</span>
|
|
751
|
+
<span class="summary-subtext">{queueStreamSummary}</span>
|
|
618
752
|
</div>
|
|
619
753
|
</div>
|
|
620
754
|
<div class="actions-buttons">
|
|
621
755
|
<button
|
|
622
756
|
class="button button--primary"
|
|
623
757
|
type="button"
|
|
624
|
-
disabled={
|
|
758
|
+
disabled={
|
|
759
|
+
queueLoading || queuedCount === 0 || Boolean(runningTask)
|
|
760
|
+
}
|
|
625
761
|
on={{ click: startNextTask }}
|
|
626
762
|
>
|
|
627
763
|
Run next
|
|
@@ -629,15 +765,29 @@ export function EditingWorkspace(handle: Handle) {
|
|
|
629
765
|
<button
|
|
630
766
|
class="button button--ghost"
|
|
631
767
|
type="button"
|
|
632
|
-
disabled={!runningTask}
|
|
768
|
+
disabled={queueLoading || !runningTask}
|
|
633
769
|
on={{ click: markActiveDone }}
|
|
634
770
|
>
|
|
635
771
|
Mark running done
|
|
636
772
|
</button>
|
|
773
|
+
<button
|
|
774
|
+
class="button button--danger"
|
|
775
|
+
type="button"
|
|
776
|
+
disabled={queueLoading || !runningTask}
|
|
777
|
+
on={{
|
|
778
|
+
click: () => {
|
|
779
|
+
if (runningTask) {
|
|
780
|
+
cancelActiveTask(runningTask.id)
|
|
781
|
+
}
|
|
782
|
+
},
|
|
783
|
+
}}
|
|
784
|
+
>
|
|
785
|
+
Cancel running
|
|
786
|
+
</button>
|
|
637
787
|
<button
|
|
638
788
|
class="button button--ghost"
|
|
639
789
|
type="button"
|
|
640
|
-
disabled={completedCount === 0}
|
|
790
|
+
disabled={queueLoading || completedCount === 0}
|
|
641
791
|
on={{ click: clearCompletedTasks }}
|
|
642
792
|
>
|
|
643
793
|
Clear completed
|
|
@@ -765,56 +915,111 @@ export function EditingWorkspace(handle: Handle) {
|
|
|
765
915
|
<div class="panel-header">
|
|
766
916
|
<h3>Processing queue</h3>
|
|
767
917
|
<span class="summary-subtext">
|
|
768
|
-
{
|
|
918
|
+
{queueLoading
|
|
919
|
+
? 'Loading...'
|
|
920
|
+
: `${processingQueue.length} total`}
|
|
769
921
|
</span>
|
|
770
922
|
</div>
|
|
771
|
-
{
|
|
923
|
+
{queueError ? (
|
|
924
|
+
<p class="status-note status-note--danger">{queueError}</p>
|
|
925
|
+
) : null}
|
|
926
|
+
{queueLoading ? (
|
|
927
|
+
<p class="app-muted">Loading queue updates...</p>
|
|
928
|
+
) : processingQueue.length === 0 ? (
|
|
772
929
|
<p class="app-muted">
|
|
773
930
|
No actions queued yet. Use the buttons above to stage work.
|
|
774
931
|
</p>
|
|
775
932
|
) : (
|
|
776
933
|
<ul class="stacked-list processing-list">
|
|
777
|
-
{processingQueue.map((task) =>
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
934
|
+
{processingQueue.map((task) => {
|
|
935
|
+
const showProgress =
|
|
936
|
+
task.status === 'running' || task.status === 'error'
|
|
937
|
+
const progress =
|
|
938
|
+
task.progress ??
|
|
939
|
+
(task.status === 'running'
|
|
940
|
+
? {
|
|
941
|
+
step: 0,
|
|
942
|
+
totalSteps: 0,
|
|
943
|
+
label: 'Starting',
|
|
944
|
+
percent: 0,
|
|
945
|
+
}
|
|
946
|
+
: null)
|
|
947
|
+
return (
|
|
948
|
+
<li
|
|
949
|
+
class={classNames(
|
|
950
|
+
'stacked-item',
|
|
951
|
+
'processing-row',
|
|
952
|
+
task.status === 'running' && 'is-running',
|
|
953
|
+
task.status === 'done' && 'is-complete',
|
|
954
|
+
task.status === 'error' && 'is-error',
|
|
955
|
+
)}
|
|
956
|
+
>
|
|
957
|
+
<div class="processing-row-header">
|
|
958
|
+
<div>
|
|
959
|
+
<h4>{task.title}</h4>
|
|
960
|
+
<p class="app-muted">{task.detail}</p>
|
|
961
|
+
</div>
|
|
962
|
+
<span
|
|
963
|
+
class={classNames(
|
|
964
|
+
'status-pill',
|
|
965
|
+
task.status === 'queued' && 'status-pill--info',
|
|
966
|
+
task.status === 'running' && 'status-pill--warning',
|
|
967
|
+
task.status === 'done' && 'status-pill--success',
|
|
968
|
+
task.status === 'error' && 'status-pill--danger',
|
|
969
|
+
)}
|
|
811
970
|
>
|
|
812
|
-
|
|
813
|
-
</
|
|
971
|
+
{task.status}
|
|
972
|
+
</span>
|
|
973
|
+
</div>
|
|
974
|
+
{showProgress && progress ? (
|
|
975
|
+
<div class="processing-progress">
|
|
976
|
+
<div class="processing-progress-meta">
|
|
977
|
+
<span>{progress.label}</span>
|
|
978
|
+
<span>
|
|
979
|
+
{progress.totalSteps > 0
|
|
980
|
+
? `${progress.step}/${progress.totalSteps}`
|
|
981
|
+
: '...'}
|
|
982
|
+
</span>
|
|
983
|
+
</div>
|
|
984
|
+
<div class="processing-progress-bar">
|
|
985
|
+
<span
|
|
986
|
+
class="processing-progress-fill"
|
|
987
|
+
style={`--progress:${progress.percent}%`}
|
|
988
|
+
/>
|
|
989
|
+
</div>
|
|
990
|
+
</div>
|
|
814
991
|
) : null}
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
992
|
+
{task.status === 'error' && task.errorMessage ? (
|
|
993
|
+
<p class="status-note status-note--danger processing-error">
|
|
994
|
+
{task.errorMessage}
|
|
995
|
+
</p>
|
|
996
|
+
) : null}
|
|
997
|
+
<div class="processing-row-meta">
|
|
998
|
+
<span class="summary-subtext">
|
|
999
|
+
{formatProcessingCategory(task.category)}
|
|
1000
|
+
</span>
|
|
1001
|
+
{task.status === 'running' ? (
|
|
1002
|
+
<button
|
|
1003
|
+
class="button button--danger"
|
|
1004
|
+
type="button"
|
|
1005
|
+
on={{ click: () => cancelActiveTask(task.id) }}
|
|
1006
|
+
>
|
|
1007
|
+
Cancel
|
|
1008
|
+
</button>
|
|
1009
|
+
) : null}
|
|
1010
|
+
{task.status === 'queued' || task.status === 'error' ? (
|
|
1011
|
+
<button
|
|
1012
|
+
class="button button--ghost"
|
|
1013
|
+
type="button"
|
|
1014
|
+
on={{ click: () => removeTask(task.id) }}
|
|
1015
|
+
>
|
|
1016
|
+
Remove
|
|
1017
|
+
</button>
|
|
1018
|
+
) : null}
|
|
1019
|
+
</div>
|
|
1020
|
+
</li>
|
|
1021
|
+
)
|
|
1022
|
+
})}
|
|
818
1023
|
</ul>
|
|
819
1024
|
)}
|
|
820
1025
|
</div>
|