eprec 1.12.0 → 1.13.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/app/assets/styles.css +244 -0
- package/app/client/app.tsx +11 -2
- package/app/client/editing-workspace.tsx +42 -19
- package/app/client/trim-points.tsx +1276 -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 +262 -0
- package/app/trim-commands.ts +154 -0
- package/package.json +1 -1
- package/process-course/edits/cli.ts +3 -0
- package/server/processing-queue.ts +8 -8
- package/src/app-server.ts +4 -0
- package/src/cli.ts +4 -1
package/app/assets/styles.css
CHANGED
|
@@ -182,6 +182,23 @@ p {
|
|
|
182
182
|
max-width: 640px;
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
+
.app-nav {
|
|
186
|
+
display: flex;
|
|
187
|
+
flex-wrap: wrap;
|
|
188
|
+
gap: var(--spacing-md);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.app-link {
|
|
192
|
+
color: var(--color-primary);
|
|
193
|
+
font-weight: var(--font-weight-semibold);
|
|
194
|
+
font-size: var(--font-size-sm);
|
|
195
|
+
text-decoration: none;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.app-link:hover {
|
|
199
|
+
text-decoration: underline;
|
|
200
|
+
}
|
|
201
|
+
|
|
185
202
|
.app-grid {
|
|
186
203
|
display: grid;
|
|
187
204
|
gap: var(--spacing-lg);
|
|
@@ -515,6 +532,229 @@ p {
|
|
|
515
532
|
gap: var(--spacing-xl);
|
|
516
533
|
}
|
|
517
534
|
|
|
535
|
+
.trim-grid {
|
|
536
|
+
align-items: start;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
.trim-preview {
|
|
540
|
+
display: flex;
|
|
541
|
+
flex-direction: column;
|
|
542
|
+
gap: var(--spacing-sm);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
.trim-hint {
|
|
546
|
+
margin: 0;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
.trim-track {
|
|
550
|
+
position: relative;
|
|
551
|
+
height: 96px;
|
|
552
|
+
border-radius: var(--radius-xl);
|
|
553
|
+
border: 1px solid var(--color-border);
|
|
554
|
+
background: linear-gradient(
|
|
555
|
+
90deg,
|
|
556
|
+
var(--color-border-muted) 0%,
|
|
557
|
+
var(--color-border-muted) 2%,
|
|
558
|
+
transparent 2%,
|
|
559
|
+
transparent 20%
|
|
560
|
+
);
|
|
561
|
+
background-size: 20% 100%;
|
|
562
|
+
overflow: hidden;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
.trim-waveform {
|
|
566
|
+
position: absolute;
|
|
567
|
+
inset: 0;
|
|
568
|
+
width: 100%;
|
|
569
|
+
height: 100%;
|
|
570
|
+
pointer-events: none;
|
|
571
|
+
color: var(--color-text-faint);
|
|
572
|
+
opacity: 0.7;
|
|
573
|
+
z-index: 1;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
.trim-track--disabled {
|
|
577
|
+
opacity: 0.6;
|
|
578
|
+
pointer-events: none;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
.trim-track--skeleton {
|
|
582
|
+
min-height: 96px;
|
|
583
|
+
background: linear-gradient(
|
|
584
|
+
90deg,
|
|
585
|
+
var(--color-background) 0%,
|
|
586
|
+
var(--color-border) 45%,
|
|
587
|
+
var(--color-background) 90%
|
|
588
|
+
);
|
|
589
|
+
background-size: 200% 100%;
|
|
590
|
+
animation: shimmer 1.8s infinite;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
.trim-range {
|
|
594
|
+
position: absolute;
|
|
595
|
+
top: 18px;
|
|
596
|
+
height: 60px;
|
|
597
|
+
border-radius: var(--radius-md);
|
|
598
|
+
border: 1px solid var(--color-warning-border);
|
|
599
|
+
background: var(--color-warning-surface);
|
|
600
|
+
left: var(--range-left);
|
|
601
|
+
width: var(--range-width);
|
|
602
|
+
display: flex;
|
|
603
|
+
align-items: center;
|
|
604
|
+
justify-content: center;
|
|
605
|
+
gap: var(--spacing-sm);
|
|
606
|
+
padding: 0 var(--spacing-lg);
|
|
607
|
+
box-shadow: inset 0 0 0 1px
|
|
608
|
+
color-mix(in srgb, var(--color-warning-border) 40%, transparent);
|
|
609
|
+
z-index: 2;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
.trim-range.is-selected {
|
|
613
|
+
box-shadow: 0 0 0 2px
|
|
614
|
+
color-mix(in srgb, var(--color-primary) 55%, transparent);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
.trim-range-label {
|
|
618
|
+
font-size: var(--font-size-xs);
|
|
619
|
+
font-weight: var(--font-weight-semibold);
|
|
620
|
+
color: var(--color-text-secondary);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
.trim-handle {
|
|
624
|
+
position: absolute;
|
|
625
|
+
top: 50%;
|
|
626
|
+
width: 14px;
|
|
627
|
+
height: 52px;
|
|
628
|
+
border-radius: var(--radius-sm);
|
|
629
|
+
background: var(--color-primary);
|
|
630
|
+
border: 1px solid var(--color-primary-active);
|
|
631
|
+
box-shadow: var(--shadow-sm);
|
|
632
|
+
cursor: ew-resize;
|
|
633
|
+
transform: translate(-50%, -50%);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
.trim-handle--start {
|
|
637
|
+
left: 0;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
.trim-handle--end {
|
|
641
|
+
right: 0;
|
|
642
|
+
transform: translate(50%, -50%);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
.trim-handle:focus-visible {
|
|
646
|
+
outline: 2px solid var(--color-border-accent);
|
|
647
|
+
outline-offset: 2px;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
.trim-handle-label {
|
|
651
|
+
position: absolute;
|
|
652
|
+
top: -18px;
|
|
653
|
+
font-size: var(--font-size-xs);
|
|
654
|
+
font-weight: var(--font-weight-semibold);
|
|
655
|
+
color: var(--color-text-inverse);
|
|
656
|
+
background: var(--color-surface-inverse);
|
|
657
|
+
padding: 2px 6px;
|
|
658
|
+
border-radius: var(--radius-pill);
|
|
659
|
+
white-space: nowrap;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
.trim-handle-label--start {
|
|
663
|
+
left: 0;
|
|
664
|
+
transform: translateX(-50%);
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
.trim-handle-label--end {
|
|
668
|
+
right: 0;
|
|
669
|
+
transform: translateX(50%);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
.trim-playhead {
|
|
673
|
+
position: absolute;
|
|
674
|
+
top: 0;
|
|
675
|
+
bottom: 0;
|
|
676
|
+
width: 2px;
|
|
677
|
+
left: var(--playhead);
|
|
678
|
+
background: var(--color-primary);
|
|
679
|
+
box-shadow: 0 0 0 1px
|
|
680
|
+
color-mix(in srgb, var(--color-primary) 40%, transparent);
|
|
681
|
+
z-index: 3;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
.trim-range-list {
|
|
685
|
+
gap: var(--spacing-sm);
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
.trim-range-row {
|
|
689
|
+
border: 1px solid var(--color-border);
|
|
690
|
+
padding: var(--spacing-md);
|
|
691
|
+
display: flex;
|
|
692
|
+
flex-direction: column;
|
|
693
|
+
gap: var(--spacing-md);
|
|
694
|
+
background: var(--color-surface);
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
.trim-range-summary {
|
|
698
|
+
border: none;
|
|
699
|
+
padding: 0;
|
|
700
|
+
background: transparent;
|
|
701
|
+
display: flex;
|
|
702
|
+
flex-direction: column;
|
|
703
|
+
gap: var(--spacing-xs);
|
|
704
|
+
text-align: left;
|
|
705
|
+
cursor: pointer;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
.trim-range-time {
|
|
709
|
+
font-weight: var(--font-weight-semibold);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
.trim-range-fields {
|
|
713
|
+
display: grid;
|
|
714
|
+
grid-template-columns: repeat(2, minmax(0, 1fr)) auto;
|
|
715
|
+
gap: var(--spacing-sm);
|
|
716
|
+
align-items: end;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
.trim-waveform-meta {
|
|
720
|
+
display: flex;
|
|
721
|
+
justify-content: flex-end;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
.trim-time-row {
|
|
725
|
+
display: flex;
|
|
726
|
+
align-items: center;
|
|
727
|
+
justify-content: space-between;
|
|
728
|
+
gap: var(--spacing-md);
|
|
729
|
+
flex-wrap: wrap;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
.trim-progress {
|
|
733
|
+
display: flex;
|
|
734
|
+
align-items: center;
|
|
735
|
+
gap: var(--spacing-md);
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
.trim-progress progress {
|
|
739
|
+
flex: 1;
|
|
740
|
+
height: 10px;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
.trim-command-card {
|
|
744
|
+
gap: var(--spacing-lg);
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
.trim-command-actions {
|
|
748
|
+
display: flex;
|
|
749
|
+
gap: var(--spacing-sm);
|
|
750
|
+
flex-wrap: wrap;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
.trim-output {
|
|
754
|
+
max-height: 240px;
|
|
755
|
+
overflow-y: auto;
|
|
756
|
+
}
|
|
757
|
+
|
|
518
758
|
.timeline-header {
|
|
519
759
|
display: flex;
|
|
520
760
|
align-items: flex-start;
|
|
@@ -955,4 +1195,8 @@ p {
|
|
|
955
1195
|
.timeline-controls {
|
|
956
1196
|
grid-template-columns: 1fr;
|
|
957
1197
|
}
|
|
1198
|
+
|
|
1199
|
+
.trim-range-fields {
|
|
1200
|
+
grid-template-columns: 1fr;
|
|
1201
|
+
}
|
|
958
1202
|
}
|
package/app/client/app.tsx
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
|
+
import type { Handle } from 'remix/component'
|
|
1
2
|
import { EditingWorkspace } from './editing-workspace.tsx'
|
|
3
|
+
import { TrimPoints } from './trim-points.tsx'
|
|
2
4
|
|
|
3
|
-
export function App() {
|
|
4
|
-
return () =>
|
|
5
|
+
export function App(handle: Handle) {
|
|
6
|
+
return () => {
|
|
7
|
+
const pathname =
|
|
8
|
+
typeof window === 'undefined' ? '/' : window.location.pathname
|
|
9
|
+
if (pathname.startsWith('/trim-points')) {
|
|
10
|
+
return <TrimPoints />
|
|
11
|
+
}
|
|
12
|
+
return <EditingWorkspace />
|
|
13
|
+
}
|
|
5
14
|
}
|
|
@@ -396,8 +396,7 @@ export function EditingWorkspace(handle: Handle) {
|
|
|
396
396
|
if (!response.ok) {
|
|
397
397
|
throw new Error(`Queue request failed (${response.status}).`)
|
|
398
398
|
}
|
|
399
|
-
const snapshot =
|
|
400
|
-
(await response.json()) as ProcessingQueueSnapshot
|
|
399
|
+
const snapshot = (await response.json()) as ProcessingQueueSnapshot
|
|
401
400
|
applyQueueSnapshot(snapshot)
|
|
402
401
|
} catch (error) {
|
|
403
402
|
if (handle.signal.aborted) return
|
|
@@ -493,14 +492,18 @@ export function EditingWorkspace(handle: Handle) {
|
|
|
493
492
|
void requestQueue('/mark-done', { method: 'POST' })
|
|
494
493
|
}
|
|
495
494
|
|
|
495
|
+
const cancelActiveTask = (taskId: string) => {
|
|
496
|
+
void requestQueue(`/task/${encodeURIComponent(taskId)}`, {
|
|
497
|
+
method: 'DELETE',
|
|
498
|
+
})
|
|
499
|
+
}
|
|
500
|
+
|
|
496
501
|
const clearCompletedTasks = () => {
|
|
497
502
|
void requestQueue('/clear-completed', { method: 'POST' })
|
|
498
503
|
}
|
|
499
504
|
|
|
500
505
|
const removeTask = (taskId: string) => {
|
|
501
|
-
|
|
502
|
-
method: 'DELETE',
|
|
503
|
-
})
|
|
506
|
+
cancelActiveTask(taskId)
|
|
504
507
|
}
|
|
505
508
|
|
|
506
509
|
const syncVideoToPlayhead = (value: number) => {
|
|
@@ -602,6 +605,11 @@ export function EditingWorkspace(handle: Handle) {
|
|
|
602
605
|
Review transcript-based edits, refine command windows, and prepare
|
|
603
606
|
the final CLI export in one place.
|
|
604
607
|
</p>
|
|
608
|
+
<nav class="app-nav">
|
|
609
|
+
<a class="app-link" href="/trim-points">
|
|
610
|
+
Trim points
|
|
611
|
+
</a>
|
|
612
|
+
</nav>
|
|
605
613
|
</header>
|
|
606
614
|
|
|
607
615
|
<section class="app-card app-card--full source-card">
|
|
@@ -736,10 +744,7 @@ export function EditingWorkspace(handle: Handle) {
|
|
|
736
744
|
<div class="summary-item">
|
|
737
745
|
<span class="summary-label">Stream</span>
|
|
738
746
|
<span
|
|
739
|
-
class={classNames(
|
|
740
|
-
'status-pill',
|
|
741
|
-
queueStreamMeta.className,
|
|
742
|
-
)}
|
|
747
|
+
class={classNames('status-pill', queueStreamMeta.className)}
|
|
743
748
|
>
|
|
744
749
|
{queueStreamMeta.label}
|
|
745
750
|
</span>
|
|
@@ -765,6 +770,20 @@ export function EditingWorkspace(handle: Handle) {
|
|
|
765
770
|
>
|
|
766
771
|
Mark running done
|
|
767
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>
|
|
768
787
|
<button
|
|
769
788
|
class="button button--ghost"
|
|
770
789
|
type="button"
|
|
@@ -943,14 +962,10 @@ export function EditingWorkspace(handle: Handle) {
|
|
|
943
962
|
<span
|
|
944
963
|
class={classNames(
|
|
945
964
|
'status-pill',
|
|
946
|
-
task.status === 'queued' &&
|
|
947
|
-
|
|
948
|
-
task.status === '
|
|
949
|
-
|
|
950
|
-
task.status === 'done' &&
|
|
951
|
-
'status-pill--success',
|
|
952
|
-
task.status === 'error' &&
|
|
953
|
-
'status-pill--danger',
|
|
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',
|
|
954
969
|
)}
|
|
955
970
|
>
|
|
956
971
|
{task.status}
|
|
@@ -983,8 +998,16 @@ export function EditingWorkspace(handle: Handle) {
|
|
|
983
998
|
<span class="summary-subtext">
|
|
984
999
|
{formatProcessingCategory(task.category)}
|
|
985
1000
|
</span>
|
|
986
|
-
{task.status === '
|
|
987
|
-
|
|
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' ? (
|
|
988
1011
|
<button
|
|
989
1012
|
class="button button--ghost"
|
|
990
1013
|
type="button"
|