pstream is dead; long live pstream taciturnaxolotl.github.io/pstream-ng/
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

custom color captions

+98 -26
+48 -18
src/components/player/atoms/settings/CaptionSettingsView.tsx
··· 7 7 import { Menu } from "@/components/player/internals/ContextMenu"; 8 8 import { useOverlayRouter } from "@/hooks/useOverlayRouter"; 9 9 import { useProgressBar } from "@/hooks/useProgressBar"; 10 - import { useSubtitleStore } from "@/stores/subtitles"; 10 + import { SubtitleStyling, useSubtitleStore } from "@/stores/subtitles"; 11 11 12 12 export function ColorOption(props: { 13 13 color: string; ··· 214 214 ); 215 215 } 216 216 217 - export const colors = ["#ffffff", "#b0b0b0", "#80b1fa", "#e2e535"]; 217 + export const colors = ["#ffffff", "#80b1fa", "#e2e535", "#10B239FF"]; 218 218 219 219 export function CaptionSettingsView({ 220 220 id, ··· 225 225 }) { 226 226 const { t } = useTranslation(); 227 227 const router = useOverlayRouter(id); 228 - const styling = useSubtitleStore((s) => s.styling); 229 - const overrideCasing = useSubtitleStore((s) => s.overrideCasing); 230 - const delay = useSubtitleStore((s) => s.delay); 231 - const setOverrideCasing = useSubtitleStore((s) => s.setOverrideCasing); 232 - const setDelay = useSubtitleStore((s) => s.setDelay); 233 - const updateStyling = useSubtitleStore((s) => s.updateStyling); 228 + const subtitleStore = useSubtitleStore(); 229 + const styling = subtitleStore.styling; 230 + const overrideCasing = subtitleStore.overrideCasing; 231 + const delay = subtitleStore.delay; 232 + const setOverrideCasing = subtitleStore.setOverrideCasing; 233 + const setDelay = subtitleStore.setDelay; 234 + const updateStyling = subtitleStore.updateStyling; 235 + 236 + useEffect(() => { 237 + subtitleStore.updateStyling(styling); 238 + }, [styling, subtitleStore]); 239 + 240 + const handleStylingChange = (newStyling: SubtitleStyling) => { 241 + updateStyling(newStyling); 242 + }; 234 243 235 244 return ( 236 245 <> ··· 270 279 <div className="flex justify-center items-center"> 271 280 <Toggle 272 281 enabled={styling.bold} 273 - onClick={() => updateStyling({ bold: !styling.bold })} 282 + onClick={() => 283 + handleStylingChange({ ...styling, bold: !styling.bold }) 284 + } 274 285 /> 275 286 </div> 276 287 </div> ··· 279 290 label={t("settings.subtitles.backgroundLabel")} 280 291 max={100} 281 292 min={0} 282 - onChange={(v) => updateStyling({ backgroundOpacity: v / 100 })} 293 + onChange={(v) => 294 + handleStylingChange({ ...styling, backgroundOpacity: v / 100 }) 295 + } 283 296 value={styling.backgroundOpacity * 100} 284 297 textTransformer={(s) => `${s}%`} 285 298 /> ··· 287 300 label={t("settings.subtitles.backgroundBlurLabel")} 288 301 max={100} 289 302 min={0} 290 - onChange={(v) => updateStyling({ backgroundBlur: v / 100 })} 303 + onChange={(v) => 304 + handleStylingChange({ ...styling, backgroundBlur: v / 100 }) 305 + } 291 306 value={styling.backgroundBlur * 100} 292 307 textTransformer={(s) => `${s}%`} 293 308 /> ··· 296 311 max={200} 297 312 min={1} 298 313 textTransformer={(s) => `${s}%`} 299 - onChange={(v) => updateStyling({ size: v / 100 })} 314 + onChange={(v) => handleStylingChange({ ...styling, size: v / 100 })} 300 315 value={styling.size * 100} 301 316 /> 302 317 <div className="flex justify-between items-center"> 303 318 <Menu.FieldTitle> 304 319 {t("settings.subtitles.colorLabel")} 305 320 </Menu.FieldTitle> 306 - <div className="flex justify-center items-center"> 307 - {colors.map((v) => ( 321 + <div className="flex justify-center items-center space-x-2"> 322 + {colors.map((color) => ( 308 323 <ColorOption 309 - onClick={() => updateStyling({ color: v })} 310 - color={v} 311 - active={styling.color === v} 312 - key={v} 324 + key={color} 325 + color={color} 326 + active={styling.color === color} 327 + onClick={() => handleStylingChange({ ...styling, color })} 313 328 /> 314 329 ))} 330 + {/* Add Color Picker */} 331 + <div className="relative inline-block"> 332 + <input 333 + type="color" 334 + value={styling.color} 335 + onChange={(e) => { 336 + const color = e.target.value; 337 + handleStylingChange({ ...styling, color }); 338 + }} 339 + className="absolute opacity-0 cursor-pointer w-10 h-10" 340 + /> 341 + <div style={{ color: styling.color }}> 342 + <Icon icon={Icons.BRUSH} className="text-2xl" /> 343 + </div> 344 + </div> 315 345 </div> 316 346 </div> 317 347 </Menu.Section>
+50 -8
src/pages/parts/settings/CaptionsPart.tsx
··· 1 1 import classNames from "classnames"; 2 - import { useState } from "react"; 2 + import { useEffect, useState } from "react"; 3 3 import { Helmet } from "react-helmet-async"; 4 4 import { useTranslation } from "react-i18next"; 5 5 ··· 14 14 import { CaptionCue } from "@/components/player/Player"; 15 15 import { Heading1 } from "@/components/utils/Text"; 16 16 import { Transition } from "@/components/utils/Transition"; 17 - import { SubtitleStyling } from "@/stores/subtitles"; 17 + import { SubtitleStyling, useSubtitleStore } from "@/stores/subtitles"; 18 18 19 19 export function CaptionPreview(props: { 20 20 fullscreen?: boolean; ··· 77 77 }) { 78 78 const { t } = useTranslation(); 79 79 const [fullscreenPreview, setFullscreenPreview] = useState(false); 80 + 81 + const subtitleStore = useSubtitleStore(); 82 + 83 + useEffect(() => { 84 + subtitleStore.updateStyling(props.styling); 85 + }, [props.styling, subtitleStore, subtitleStore.updateStyling]); 86 + 87 + const handleStylingChange = (newStyling: SubtitleStyling) => { 88 + props.setStyling(newStyling); 89 + subtitleStore.updateStyling(newStyling); 90 + }; 80 91 81 92 return ( 82 93 <div> ··· 88 99 max={100} 89 100 min={0} 90 101 onChange={(v) => 91 - props.setStyling({ ...props.styling, backgroundOpacity: v / 100 }) 102 + handleStylingChange({ 103 + ...props.styling, 104 + backgroundOpacity: v / 100, 105 + }) 92 106 } 93 107 value={props.styling.backgroundOpacity * 100} 94 108 textTransformer={(s) => `${s}%`} ··· 98 112 max={100} 99 113 min={0} 100 114 onChange={(v) => 101 - props.setStyling({ ...props.styling, backgroundBlur: v / 100 }) 115 + handleStylingChange({ 116 + ...props.styling, 117 + backgroundBlur: v / 100, 118 + }) 102 119 } 103 120 value={props.styling.backgroundBlur * 1} 104 121 textTransformer={(s) => `${s}%`} ··· 109 126 min={1} 110 127 textTransformer={(s) => `${s}%`} 111 128 onChange={(v) => 112 - props.setStyling({ ...props.styling, size: v / 100 }) 129 + handleStylingChange({ 130 + ...props.styling, 131 + size: v / 100, 132 + }) 113 133 } 114 134 value={props.styling.size * 100} 115 135 /> ··· 121 141 <Toggle 122 142 enabled={props.styling.bold} 123 143 onClick={() => 124 - props.setStyling({ 144 + handleStylingChange({ 125 145 ...props.styling, 126 146 bold: !props.styling.bold, 127 147 }) ··· 133 153 <Menu.FieldTitle> 134 154 {t("settings.subtitles.colorLabel")} 135 155 </Menu.FieldTitle> 136 - <div className="flex justify-center items-center"> 156 + <div className="flex justify-center items-center space-x-2"> 137 157 {colors.map((v) => ( 138 158 <ColorOption 139 159 onClick={() => 140 - props.setStyling({ ...props.styling, color: v }) 160 + handleStylingChange({ 161 + ...props.styling, 162 + color: v, 163 + }) 141 164 } 142 165 color={v} 143 166 active={props.styling.color === v} 144 167 key={v} 145 168 /> 146 169 ))} 170 + {/* Add Color Picker */} 171 + <div className="relative"> 172 + <input 173 + type="color" 174 + value={props.styling.color} 175 + onChange={(e) => { 176 + const color = e.target.value; 177 + handleStylingChange({ ...props.styling, color }); 178 + subtitleStore.updateStyling({ 179 + ...props.styling, 180 + color, 181 + }); 182 + }} 183 + className="absolute opacity-0 cursor-pointer w-8 h-8" 184 + /> 185 + <div style={{ color: props.styling.color }}> 186 + <Icon icon={Icons.BRUSH} className="text-2xl" /> 187 + </div> 188 + </div> 147 189 </div> 148 190 </div> 149 191 </div>