feat: make subtitles draggable and customizable#26
Conversation
📝 WalkthroughWalkthroughThis PR adds configurable subtitle styling (font size, color, background, font family) with localStorage persistence, integrates subtitle configuration controls into the settings UI, updates the Subtitle component to render with dynamic styles, and wires draggable behavior for the subtitle element using a ChangesSubtitle Configuration and Draggable Support
Sequence DiagramsequenceDiagram
participant User
participant SettingsUI as Settings UI
participant SubtitleContext
participant Subtitle as Subtitle Component
participant SubtitleText as SubtitleText
User->>SettingsUI: Change font size/color/background
SettingsUI->>SubtitleContext: Call setSubtitleConfig(partialConfig)
SubtitleContext->>SubtitleContext: Merge and persist to localStorage
SubtitleContext->>Subtitle: Update context value
Subtitle->>Subtitle: Read subtitleConfig
Subtitle->>SubtitleText: Pass fontSize, color, fontFamily
SubtitleText->>SubtitleText: Apply dynamic styles
SubtitleText->>User: Render styled subtitle
User->>Subtitle: Mouse down on subtitle
Subtitle->>Subtitle: Call onMouseDown from useDraggable
Subtitle->>User: Display draggable cursor
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~22 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
src/renderer/src/App.tsxESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox. src/renderer/src/components/canvas/subtitle.tsxESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox. src/renderer/src/components/sidebar/setting/general.tsxESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox.
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/renderer/src/App.tsx (1)
135-150:⚠️ Potential issue | 🟠 Major | ⚡ Quick winDragging will double-apply the centering transform.
The wrapper is already centered with
left="50%"andtranslateX(-50%), anduseDraggablethen writes anothertranslateX(-50%) translate(...)onto the subtitle element itself. On the first drag, that stacks two centering offsets and shifts the subtitle left by half its own width.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/renderer/src/App.tsx` around lines 135 - 150, The subtitle gets double-centered because the wrapper Box sets left="50%" + transform="translateX(-50%)" while useDraggable writes a new transform onto the subtitleRef, overwriting/stacking transforms; fix by changing the drag transform application so it composes with any existing translateX(-50%) instead of replacing it: in the code path where useDraggable (or handleSubtitleMouseDown/drag handlers) writes subtitleRef.current.style.transform, detect an existing translateX(-50%) (or any translateX(...) prefix) and append the drag translate(...) after it (or preserve and reapply the existing translateX(-50%) before the drag translate), ensuring the final transform becomes "translateX(-50%) translate(tx, ty)" rather than duplicating/stacking offsets.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/renderer/src/App.tsx`:
- Around line 41-46: The useDraggable hook (use-draggable.ts) currently only
keeps position in refs so positions reset after reload; update useDraggable to
load the saved position for the given componentId on init and apply it to
elementRef, and persist updates whenever the user finishes dragging (e.g., on
handleMouseDown/drag end) by saving the new coordinates keyed by componentId
(use localStorage or the project's electron storage utility). Ensure the hook
reads saved state when created, applies it to the element's style/transform, and
writes back the updated position whenever the handlers (handleMouseDown → drag
end) change the position so
elementRef/handleSubtitleMouseDown/handleSubtitleMouseEnter/handleSubtitleMouseLeave
reflect persisted coordinates across reloads.
In `@src/renderer/src/components/sidebar/setting/general.tsx`:
- Around line 104-129: The new subtitle controls (rendered when showSubtitle is
true) use hardcoded English strings for labels and placeholders in the
InputField props (e.g., "Subtitle Font Size", "e.g. 1.5rem, 24px") — update each
label and placeholder to use the localization function t(...) instead,
creating/using appropriate i18n keys for "subtitle.fontSize.label",
"subtitle.fontSize.placeholder", "subtitle.color.label",
"subtitle.color.placeholder", "subtitle.backgroundColor.label",
"subtitle.backgroundColor.placeholder", and "subtitle.fontFamily.label" /
"subtitle.fontFamily.placeholder" and pass t('...') to the InputField label and
placeholder props while leaving value and onChange (subtitleConfig and
setSubtitleConfig) unchanged.
---
Outside diff comments:
In `@src/renderer/src/App.tsx`:
- Around line 135-150: The subtitle gets double-centered because the wrapper Box
sets left="50%" + transform="translateX(-50%)" while useDraggable writes a new
transform onto the subtitleRef, overwriting/stacking transforms; fix by changing
the drag transform application so it composes with any existing translateX(-50%)
instead of replacing it: in the code path where useDraggable (or
handleSubtitleMouseDown/drag handlers) writes
subtitleRef.current.style.transform, detect an existing translateX(-50%) (or any
translateX(...) prefix) and append the drag translate(...) after it (or preserve
and reapply the existing translateX(-50%) before the drag translate), ensuring
the final transform becomes "translateX(-50%) translate(tx, ty)" rather than
duplicating/stacking offsets.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 3a903c54-8b6e-40f7-a966-608ed3533b1d
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (5)
src/renderer/src/App.tsxsrc/renderer/src/components/canvas/subtitle.tsxsrc/renderer/src/components/sidebar/setting/general.tsxsrc/renderer/src/context/subtitle-context.tsxsrc/renderer/src/hooks/sidebar/setting/use-general-settings.ts
| const { | ||
| elementRef: subtitleRef, | ||
| handleMouseDown: handleSubtitleMouseDown, | ||
| handleMouseEnter: handleSubtitleMouseEnter, | ||
| handleMouseLeave: handleSubtitleMouseLeave, | ||
| } = useDraggable({ componentId: 'subtitle' }); |
There was a problem hiding this comment.
Drag position still resets after refresh.
This wires in useDraggable, but the supplied src/renderer/src/hooks/electron/use-draggable.ts implementation only keeps position in refs and never loads/saves anything by componentId. After a reload the subtitle goes back to its default location, so the persistence part of the feature is still missing.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/renderer/src/App.tsx` around lines 41 - 46, The useDraggable hook
(use-draggable.ts) currently only keeps position in refs so positions reset
after reload; update useDraggable to load the saved position for the given
componentId on init and apply it to elementRef, and persist updates whenever the
user finishes dragging (e.g., on handleMouseDown/drag end) by saving the new
coordinates keyed by componentId (use localStorage or the project's electron
storage utility). Ensure the hook reads saved state when created, applies it to
the element's style/transform, and writes back the updated position whenever the
handlers (handleMouseDown → drag end) change the position so
elementRef/handleSubtitleMouseDown/handleSubtitleMouseEnter/handleSubtitleMouseLeave
reflect persisted coordinates across reloads.
| {showSubtitle && ( | ||
| <> | ||
| <InputField | ||
| label="Subtitle Font Size" | ||
| value={subtitleConfig.fontSize} | ||
| onChange={(value) => setSubtitleConfig({ fontSize: value as string })} | ||
| placeholder="e.g. 1.5rem, 24px" | ||
| /> | ||
| <InputField | ||
| label="Subtitle Color" | ||
| value={subtitleConfig.color} | ||
| onChange={(value) => setSubtitleConfig({ color: value as string })} | ||
| placeholder="e.g. white, #ffffff" | ||
| /> | ||
| <InputField | ||
| label="Subtitle Background Color" | ||
| value={subtitleConfig.backgroundColor} | ||
| onChange={(value) => setSubtitleConfig({ backgroundColor: value as string })} | ||
| placeholder="e.g. rgba(0, 0, 0, 0.7)" | ||
| /> | ||
| <InputField | ||
| label="Subtitle Font Family" | ||
| value={subtitleConfig.fontFamily} | ||
| onChange={(value) => setSubtitleConfig({ fontFamily: value as string })} | ||
| placeholder="e.g. Arial, sans-serif" | ||
| /> |
There was a problem hiding this comment.
Localize the new subtitle control labels and placeholders.
This block hardcodes English copy while the rest of the settings panel uses t(...). The new subtitle options will stay untranslated for non-English users.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/renderer/src/components/sidebar/setting/general.tsx` around lines 104 -
129, The new subtitle controls (rendered when showSubtitle is true) use
hardcoded English strings for labels and placeholders in the InputField props
(e.g., "Subtitle Font Size", "e.g. 1.5rem, 24px") — update each label and
placeholder to use the localization function t(...) instead, creating/using
appropriate i18n keys for "subtitle.fontSize.label",
"subtitle.fontSize.placeholder", "subtitle.color.label",
"subtitle.color.placeholder", "subtitle.backgroundColor.label",
"subtitle.backgroundColor.placeholder", and "subtitle.fontFamily.label" /
"subtitle.fontFamily.placeholder" and pass t('...') to the InputField label and
placeholder props while leaving value and onChange (subtitleConfig and
setSubtitleConfig) unchanged.
| const { | ||
| showSubtitle, | ||
| setShowSubtitle, | ||
| subtitleConfig, | ||
| setSubtitleConfig, | ||
| } = useSubtitle(); |
There was a problem hiding this comment.
Keep subtitle style edits in the save/cancel draft flow.
These values now bypass settings/originalSettings and go straight through setSubtitleConfig, which immediately persists in src/renderer/src/context/subtitle-context.tsx Lines 85-90. Pressing Cancel will restore other settings, but the new subtitle font/color/background changes remain applied and saved.
Also applies to: 264-265
This PR addresses the feature request for movable and styleable subtitles.
Key Enhancements:
This improves visibility and readability for streamers, as they can now move subtitles to a non-obstructive area of their screen.
Related Issue: Issue #369
Summary by CodeRabbit
New Features