Skip to content

Commit af2a1c4

Browse files
feat: Add UI for adding a new block (#33)
1 parent 8055323 commit af2a1c4

29 files changed

+2292
-714
lines changed

index.html

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@
1111

1212
<link rel="preconnect" href="https://fonts.googleapis.com">
1313
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
14-
<link href="https://fonts.googleapis.com/css2?family=Inter:[email protected]&display=swap" rel="stylesheet">
14+
<link rel="preconnect" href="https://plausible.io">
15+
16+
<link rel="preload" href="https://fonts.googleapis.com/css2?family=Inter:[email protected]&display=swap" as="style">
17+
<link href="https://fonts.googleapis.com/css2?family=Inter:[email protected]&display=swap" rel="stylesheet" media="print">
1518

1619
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
1720
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">

src/LolApp.tsx

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { useTranscriber } from "./transcriber/useTranscriber";
66
import Constants from "./transcriber/Constants";
77
import { Spinner } from "./ui/Spinner";
88
import { LandingDropzone } from "./screens/LandingDropzone";
9-
import { Progress } from "./ui/Progress";
9+
import { Progress } from "./ui/Progress.gen";
1010
import { makeEditorContextComponent } from "./screens/editor/EditorContext.gen";
1111
import { Transition } from "@headlessui/react";
1212
import type {
@@ -23,7 +23,10 @@ import { ShowErrorContext, UserFacingError } from "./ErrorBoundary";
2323
import { log } from "./hooks/useAnalytics";
2424
import HeartIcon from "@heroicons/react/20/solid/HeartIcon";
2525
import { ProductHuntIcon } from "./ui/Icons.res.mjs";
26-
import { Editor } from "./screens/editor/Editor.gen";
26+
27+
const Editor = React.lazy(() =>
28+
import("./screens/editor/Editor.gen").then((m) => ({ default: m.Editor })),
29+
);
2730

2831
type VideoFile = {
2932
name: string;
@@ -583,16 +586,24 @@ export default function LolApp() {
583586

584587
{EditorContext && (
585588
<EditorContext.make>
586-
<Editor
587-
render={render}
588-
subtitlesManager={subtitlesManager}
589-
rendererPreviewCanvasRef={rendererPreviewCanvasRef}
590-
renderCanvasKey={renderCanvasKey}
591-
videoFileName={file.name}
592-
onResetPlayerState={(fn: () => void) => {
593-
resetPlayerStateRef.current = fn;
594-
}}
595-
/>
589+
<React.Suspense
590+
fallback={
591+
<div className="flex items-center justify-center h-dvh md:h-screen">
592+
<Spinner sizeRem={3} />
593+
</div>
594+
}
595+
>
596+
<Editor
597+
render={render}
598+
subtitlesManager={subtitlesManager}
599+
rendererPreviewCanvasRef={rendererPreviewCanvasRef}
600+
renderCanvasKey={renderCanvasKey}
601+
videoFileName={file.name}
602+
onResetPlayerState={(fn: () => void) => {
603+
resetPlayerStateRef.current = fn;
604+
}}
605+
/>
606+
</React.Suspense>
596607
</EditorContext.make>
597608
)}
598609

src/Main.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,13 @@ Sentry.init({
2828
Sentry.replayIntegration(),
2929
],
3030
tracesSampleRate: 1.0,
31-
// Set 'tracePropagationTargets' to control for which URLs distributed tracing should be enabled
3231
tracePropagationTargets: [
3332
"localhost",
3433
/^https:\/\/subtitles\.fframes\.studio/
3534
],
3635
enabled: process.env.NODE_ENV !== 'development',
37-
replaysSessionSampleRate: 0.05, // This sets the sample rate at 10%. You may want to change it to 100% while in development and then sample at a lower rate in production.
38-
replaysOnErrorSampleRate: 1.0, // If you're not already sampling the entire session, change the sample rate to 100% when sampling sessions where errors occur.
36+
replaysSessionSampleRate: 0.05,
37+
replaysOnErrorSampleRate: 1.0,
3938
});
4039

4140
const rootElement = document.querySelector("#root");

src/bindings/Web.res

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,13 @@ let isFocusable = el =>
2424
| Some("input")
2525
| Some("button")
2626
| Some("checkbox")
27-
| Some("link") => true
28-
| _ => false
27+
| Some("link")
28+
| Some("menuitem")
29+
| Some("menu") => true
30+
| _ =>
31+
// Also check if element is inside a dropdown/popover by checking data attributes
32+
el->Webapi.Dom.Element.getAttribute("data-radix-collection-item")->Option.isSome ||
33+
el->Webapi.Dom.Element.getAttribute("data-state")->Option.isSome
2934
}
3035
}
3136

src/bindings/Web.res.mjs

Lines changed: 18 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
// AddCueMenu.res - FAB button with dropdown menu for adding/splitting cues
2+
3+
@react.component
4+
let make = (
5+
~hasPauseBefore: bool,
6+
~hasPauseAfter: bool,
7+
~wordChunks: array<WordTimestamps.wordChunk>,
8+
~onAddBefore: unit => unit,
9+
~onAddAfter: unit => unit,
10+
~onSplit: int => unit,
11+
~onSplitPreviewChange: option<int> => unit,
12+
) => {
13+
let (isOpen, setIsOpen) = React.useState(_ => false)
14+
let (splitPreview, setSplitPreview) = React.useState(_ => None)
15+
16+
let wordCount = Array.length(wordChunks)
17+
let canSplit = wordCount >= 2
18+
19+
// Reset preview when dropdown closes
20+
React.useEffect1(() => {
21+
if !isOpen {
22+
setSplitPreview(_ => None)
23+
onSplitPreviewChange(None)
24+
}
25+
None
26+
}, [isOpen])
27+
28+
// Update parent with preview changes
29+
React.useEffect1(() => {
30+
onSplitPreviewChange(splitPreview)
31+
None
32+
}, [splitPreview])
33+
34+
<DropdownMenu.Root open_=isOpen onOpenChange={v => setIsOpen(_ => v)}>
35+
<DropdownMenu.Trigger asChild=true>
36+
<button
37+
type_="button"
38+
className="absolute -bottom-4 right-1 rounded-full bg-zinc-900 border-2 border-orange-500 p-2 shadow-lg transition-all opacity-0 scale-90 group-focus-within:opacity-100 group-focus-within:scale-100 hover:scale-115 hover:bg-zinc-800 focus:outline-none focus-visible:ring-2 focus-visible:ring-orange-400 focus-visible:ring-offset-2 focus-visible:ring-offset-zinc-900 z-50">
39+
<Icons.AdjustmentsVerticalIcon className="size-5 text-orange-500" />
40+
</button>
41+
</DropdownMenu.Trigger>
42+
<DropdownMenu.Content sideOffset={8} align=#end>
43+
<DropdownMenu.Label> {React.string("Add or split cue")} </DropdownMenu.Label>
44+
// Add cue BEFORE
45+
<DropdownMenu.Item
46+
onClick={_ => {
47+
onAddBefore()
48+
setIsOpen(_ => false)
49+
}}
50+
disabled={!hasPauseBefore}>
51+
<span className="flex items-center gap-2">
52+
<Icons.ArrowUpLeftIcon className="size-4" />
53+
{React.string("Add cue before")}
54+
</span>
55+
</DropdownMenu.Item>
56+
{!hasPauseBefore
57+
? <div className="px-3 py-1 text-xs text-zinc-500">
58+
{React.string("No pause gap available")}
59+
</div>
60+
: React.null}
61+
// Add cue AFTER
62+
<DropdownMenu.Item
63+
onClick={_ => {
64+
onAddAfter()
65+
setIsOpen(_ => false)
66+
}}
67+
disabled={!hasPauseAfter}>
68+
<span className="flex items-center gap-2">
69+
<Icons.ArrowDownLeftIcon className="size-4" />
70+
{React.string("Add cue after")}
71+
</span>
72+
</DropdownMenu.Item>
73+
{!hasPauseAfter
74+
? <div className="px-3 py-1 text-xs text-zinc-500">
75+
{React.string("No pause gap available")}
76+
</div>
77+
: React.null}
78+
<DropdownMenu.Separator />
79+
// Split current cue by words
80+
{canSplit
81+
? <>
82+
<DropdownMenu.Label>
83+
<span className="flex items-center gap-2">
84+
<Icons.ScissorsIcon className="size-4" />
85+
{React.string("Split current cue")}
86+
</span>
87+
</DropdownMenu.Label>
88+
<div className="flex flex-col gap-2 p-3 min-w-48">
89+
<Slider
90+
value={splitPreview->Option.getOr(wordCount / 2)}
91+
min={1}
92+
max={wordCount}
93+
step={1}
94+
onValueChange={v => setSplitPreview(_ => Some(v))}
95+
/>
96+
<button
97+
type_="button"
98+
onClick={_ => {
99+
onSplit(splitPreview->Option.getOr(wordCount / 2))
100+
setIsOpen(_ => false)
101+
}}
102+
className="w-full py-1.5 px-3 rounded-lg bg-orange-500 hover:bg-orange-400 text-white text-sm font-medium transition-colors">
103+
{React.string("Split")}
104+
</button>
105+
</div>
106+
</>
107+
: <div className="px-3 py-2 text-xs text-zinc-500">
108+
{React.string("Need at least 2 words to split")}
109+
</div>}
110+
</DropdownMenu.Content>
111+
</DropdownMenu.Root>
112+
}

0 commit comments

Comments
 (0)