eprec 1.12.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.
@@ -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
  }
@@ -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 () => <EditingWorkspace />
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
- void requestQueue(`/task/${encodeURIComponent(taskId)}`, {
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
- 'status-pill--info',
948
- task.status === 'running' &&
949
- 'status-pill--warning',
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 === 'queued' ||
987
- task.status === 'error' ? (
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"