1
0
Fork 0
mirror of https://github.com/jellyfin/jellyfin-web synced 2025-03-30 19:56:21 +00:00

WIP: set theme music volume level via user setting

This commit is contained in:
NathanGrenier 2025-03-28 00:13:10 -04:00
parent ccd1070417
commit 56f8ec5947
10 changed files with 99 additions and 1 deletions

4
.gitignore vendored
View file

@ -21,3 +21,7 @@ config.json
# environment related # environment related
.envrc .envrc
# other
repomix.config.json
repomix-output.txt

View file

@ -1,4 +1,5 @@
import Checkbox from '@mui/material/Checkbox'; import Checkbox from '@mui/material/Checkbox';
import Slider from '@mui/material/Slider';
import FormControl from '@mui/material/FormControl'; import FormControl from '@mui/material/FormControl';
import FormControlLabel from '@mui/material/FormControlLabel'; import FormControlLabel from '@mui/material/FormControlLabel';
import FormHelperText from '@mui/material/FormHelperText'; import FormHelperText from '@mui/material/FormHelperText';
@ -13,10 +14,11 @@ import type { DisplaySettingsValues } from '../types/displaySettingsValues';
interface LibraryPreferencesProps { interface LibraryPreferencesProps {
onChange: (event: React.SyntheticEvent) => void; onChange: (event: React.SyntheticEvent) => void;
onSliderChange: () => (event: Event, value: number | number[]) => void;
values: DisplaySettingsValues; values: DisplaySettingsValues;
} }
export function LibraryPreferences({ onChange, values }: Readonly<LibraryPreferencesProps>) { export function LibraryPreferences({ onChange, onSliderChange, values }: Readonly<LibraryPreferencesProps>) {
return ( return (
<Stack spacing={3}> <Stack spacing={3}>
<Typography variant='h2'>{globalize.translate('HeaderLibraries')}</Typography> <Typography variant='h2'>{globalize.translate('HeaderLibraries')}</Typography>
@ -79,6 +81,30 @@ export function LibraryPreferences({ onChange, values }: Readonly<LibraryPrefere
</FormHelperText> </FormHelperText>
</FormControl> </FormControl>
<FormControl fullWidth>
<FormControlLabel
aria-describedby='display-settings-lib-theme-song-volume-level-description'
control={
<Slider
disabled={!values.enableLibraryThemeSongs}
valueLabelDisplay='auto'
value={values.libraryThemeSongsVolumeLevel}
marks
defaultValue={50}
min={0}
max={100}
step={5}
onChange={onSliderChange}
/>
}
label={globalize.translate('LabelThemeSongVolume')}
name='libraryThemeSongsVolumeLevel'
/>
<FormHelperText id='display-settings-lib-theme-song-volume-level-description'>
{globalize.translate('ThemeSongVolumeHelp')}
</FormHelperText>
</FormControl>
<FormControl fullWidth> <FormControl fullWidth>
<FormControlLabel <FormControlLabel
aria-describedby='display-settings-lib-theme-videos-description' aria-describedby='display-settings-lib-theme-videos-description'

View file

@ -87,6 +87,7 @@ async function loadDisplaySettings({
enableItemDetailsBanner: Boolean(settings.detailsBanner()), enableItemDetailsBanner: Boolean(settings.detailsBanner()),
enableLibraryBackdrops: Boolean(settings.enableBackdrops()), enableLibraryBackdrops: Boolean(settings.enableBackdrops()),
enableLibraryThemeSongs: Boolean(settings.enableThemeSongs()), enableLibraryThemeSongs: Boolean(settings.enableThemeSongs()),
libraryThemeSongsVolumeLevel: settings.themeSongsVolumeLevel(),
enableLibraryThemeVideos: Boolean(settings.enableThemeVideos()), enableLibraryThemeVideos: Boolean(settings.enableThemeVideos()),
enableRewatchingInNextUp: Boolean(settings.enableRewatchingInNextUp()), enableRewatchingInNextUp: Boolean(settings.enableRewatchingInNextUp()),
episodeImagesInNextUp: Boolean(settings.useEpisodeImagesInNextUpAndResume()), episodeImagesInNextUp: Boolean(settings.useEpisodeImagesInNextUpAndResume()),
@ -132,6 +133,7 @@ async function saveDisplaySettings({
userSettings.detailsBanner(newDisplaySettings.enableItemDetailsBanner); userSettings.detailsBanner(newDisplaySettings.enableItemDetailsBanner);
userSettings.enableBackdrops(newDisplaySettings.enableLibraryBackdrops); userSettings.enableBackdrops(newDisplaySettings.enableLibraryBackdrops);
userSettings.enableThemeSongs(newDisplaySettings.enableLibraryThemeSongs); userSettings.enableThemeSongs(newDisplaySettings.enableLibraryThemeSongs);
userSettings.themeSongsVolumeLevel(newDisplaySettings.libraryThemeSongsVolumeLevel);
userSettings.enableThemeVideos(newDisplaySettings.enableLibraryThemeVideos); userSettings.enableThemeVideos(newDisplaySettings.enableLibraryThemeVideos);
userSettings.enableRewatchingInNextUp(newDisplaySettings.enableRewatchingInNextUp); userSettings.enableRewatchingInNextUp(newDisplaySettings.enableRewatchingInNextUp);
userSettings.useEpisodeImagesInNextUpAndResume(newDisplaySettings.episodeImagesInNextUp); userSettings.useEpisodeImagesInNextUpAndResume(newDisplaySettings.episodeImagesInNextUp);

View file

@ -9,6 +9,7 @@ export interface DisplaySettingsValues {
enableItemDetailsBanner: boolean; enableItemDetailsBanner: boolean;
enableLibraryBackdrops: boolean; enableLibraryBackdrops: boolean;
enableLibraryThemeSongs: boolean; enableLibraryThemeSongs: boolean;
libraryThemeSongsVolumeLevel: number;
enableLibraryThemeVideos: boolean; enableLibraryThemeVideos: boolean;
enableRewatchingInNextUp: boolean; enableRewatchingInNextUp: boolean;
episodeImagesInNextUp: boolean; episodeImagesInNextUp: boolean;

View file

@ -40,6 +40,19 @@ export default function UserDisplayPreferences() {
} }
}, [updateField, values]); }, [updateField, values]);
const handleSliderChange = useCallback(() => (e: Event, newValue: number | number[]) => {
const target = e.target as HTMLInputElement;
const fieldName = target.name as keyof DisplaySettingsValues;
const value = Array.isArray(newValue) ? newValue[0] : newValue;
if (values?.[fieldName] !== value) {
updateField({
name: fieldName,
value: value.toString()
});
}
}, [updateField, values]);
if (loading || !values) { if (loading || !values) {
return <LoadingComponent />; return <LoadingComponent />;
} }
@ -66,6 +79,7 @@ export default function UserDisplayPreferences() {
/> />
<LibraryPreferences <LibraryPreferences
onChange={handleFieldChange} onChange={handleFieldChange}
onSliderChange={handleSliderChange}
values={values} values={values}
/> />
<NextUpPreferences <NextUpPreferences

View file

@ -1,4 +1,5 @@
import escapeHtml from 'escape-html'; import escapeHtml from 'escape-html';
import dom from '../../scripts/dom';
import browser from '../../scripts/browser'; import browser from '../../scripts/browser';
import layoutManager from '../layoutManager'; import layoutManager from '../layoutManager';
import { pluginManager } from '../pluginManager'; import { pluginManager } from '../pluginManager';
@ -67,6 +68,19 @@ function showOrHideMissingEpisodesField(context) {
context.querySelector('.fldDisplayMissingEpisodes').classList.remove('hide'); context.querySelector('.fldDisplayMissingEpisodes').classList.remove('hide');
} }
function updateThemeSongVolumeDisabledState(context) {
const isEnabled = context.querySelector('#chkThemeSong').checked;
const sliderElement = context.querySelector('#sliderThemeSongVolume');
sliderElement.disabled = !isEnabled;
sliderElement.classList.toggle('disabled', !isEnabled);
// Find the slider container and toggle the disabled class on it
const sliderContainer = dom.parentWithClass(sliderElement, 'sliderContainer-settings');
if (sliderContainer) {
sliderContainer.classList.toggle('disabled', !isEnabled);
}
}
function loadForm(context, user, userSettings) { function loadForm(context, user, userSettings) {
if (appHost.supports('displaylanguage')) { if (appHost.supports('displaylanguage')) {
context.querySelector('.languageSection').classList.remove('hide'); context.querySelector('.languageSection').classList.remove('hide');
@ -115,6 +129,11 @@ function loadForm(context, user, userSettings) {
context.querySelector('.chkDisplayMissingEpisodes').checked = user.Configuration.DisplayMissingEpisodes || false; context.querySelector('.chkDisplayMissingEpisodes').checked = user.Configuration.DisplayMissingEpisodes || false;
context.querySelector('#chkThemeSong').checked = userSettings.enableThemeSongs(); context.querySelector('#chkThemeSong').checked = userSettings.enableThemeSongs();
context.querySelector('#chkThemeSong').addEventListener('change', function() {
updateThemeSongVolumeDisabledState(context);
});
context.querySelector('#sliderThemeSongVolume').value = userSettings.themeSongsVolumeLevel();
updateThemeSongVolumeDisabledState(context);
context.querySelector('#chkThemeVideo').checked = userSettings.enableThemeVideos(); context.querySelector('#chkThemeVideo').checked = userSettings.enableThemeVideos();
context.querySelector('#chkFadein').checked = userSettings.enableFastFadein(); context.querySelector('#chkFadein').checked = userSettings.enableFastFadein();
context.querySelector('#chkBlurhash').checked = userSettings.enableBlurhash(); context.querySelector('#chkBlurhash').checked = userSettings.enableBlurhash();
@ -150,6 +169,7 @@ function saveUser(context, user, userSettingsInstance, apiClient) {
userSettingsInstance.dateTimeLocale(context.querySelector('.selectDateTimeLocale').value); userSettingsInstance.dateTimeLocale(context.querySelector('.selectDateTimeLocale').value);
userSettingsInstance.enableThemeSongs(context.querySelector('#chkThemeSong').checked); userSettingsInstance.enableThemeSongs(context.querySelector('#chkThemeSong').checked);
userSettingsInstance.themeSongsVolumeLevel(context.querySelector('#sliderThemeSongVolume').value);
userSettingsInstance.enableThemeVideos(context.querySelector('#chkThemeVideo').checked); userSettingsInstance.enableThemeVideos(context.querySelector('#chkThemeVideo').checked);
userSettingsInstance.theme(context.querySelector('#selectTheme').value); userSettingsInstance.theme(context.querySelector('#selectTheme').value);
userSettingsInstance.dashboardTheme(context.querySelector('#selectDashboardTheme').value); userSettingsInstance.dashboardTheme(context.querySelector('#selectDashboardTheme').value);

View file

@ -255,6 +255,13 @@
<div class="fieldDescription checkboxFieldDescription">${EnableThemeSongsHelp}</div> <div class="fieldDescription checkboxFieldDescription">${EnableThemeSongsHelp}</div>
</div> </div>
<div class="sliderContainer-settings">
<div class="sliderContainer">
<input is="emby-slider" id="sliderThemeSongVolume" label="${LabelThemeSongVolume}" type="range" min="0" max="100" step="1" value="50" />
</div>
<div class="fieldDescription">${ThemeSongVolumeHelp}</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription fldThemeVideo"> <div class="checkboxContainer checkboxContainer-withDescription fldThemeVideo">
<label> <label>
<input type="checkbox" is="emby-checkbox" id="chkThemeVideo" /> <input type="checkbox" is="emby-checkbox" id="chkThemeVideo" />

View file

@ -248,6 +248,14 @@
height: 2.83em; /* similar to emby-input with its 110% font-size */ height: 2.83em; /* similar to emby-input with its 110% font-size */
} }
.sliderContainer-settings.disabled {
opacity: 0.5;
}
.sliderContainer-settings.disabled .sliderBubble {
display: none;
}
.sliderLabel { .sliderLabel {
display: block; display: block;
margin-bottom: 0.25em; margin-bottom: 0.25em;

View file

@ -230,6 +230,19 @@ export class UserSettings {
return toBoolean(this.get('enableThemeSongs', false), false); return toBoolean(this.get('enableThemeSongs', false), false);
} }
/**
* Get or set 'Theme Songs' volume level.
* @param {number|undefined} [val] - Volume level to set.
* @return {number} Current volume level.
*/
themeSongsVolumeLevel(val) {
if (val !== undefined) {
return this.set('themeSongsVolumeLevel', val.toString());
}
return parseInt(this.get('themeSongsVolumeLevel') || '100', 10);
}
/** /**
* Get or set 'Theme Videos' state. * Get or set 'Theme Videos' state.
* @param {boolean|undefined} [val] - Flag to enable 'Theme Videos' or undefined. * @param {boolean|undefined} [val] - Flag to enable 'Theme Videos' or undefined.
@ -676,6 +689,7 @@ export const selectAudioNormalization = currentSettings.selectAudioNormalization
export const enableNextVideoInfoOverlay = currentSettings.enableNextVideoInfoOverlay.bind(currentSettings); export const enableNextVideoInfoOverlay = currentSettings.enableNextVideoInfoOverlay.bind(currentSettings);
export const enableVideoRemainingTime = currentSettings.enableVideoRemainingTime.bind(currentSettings); export const enableVideoRemainingTime = currentSettings.enableVideoRemainingTime.bind(currentSettings);
export const enableThemeSongs = currentSettings.enableThemeSongs.bind(currentSettings); export const enableThemeSongs = currentSettings.enableThemeSongs.bind(currentSettings);
export const themeSongsVolumeLevel = currentSettings.themeSongsVolumeLevel.bind(currentSettings);
export const enableThemeVideos = currentSettings.enableThemeVideos.bind(currentSettings); export const enableThemeVideos = currentSettings.enableThemeVideos.bind(currentSettings);
export const enableFastFadein = currentSettings.enableFastFadein.bind(currentSettings); export const enableFastFadein = currentSettings.enableFastFadein.bind(currentSettings);
export const enableBlurhash = currentSettings.enableBlurhash.bind(currentSettings); export const enableBlurhash = currentSettings.enableBlurhash.bind(currentSettings);

View file

@ -960,6 +960,7 @@
"LabelTextWeight": "Text weight", "LabelTextWeight": "Text weight",
"Bold": "Bold", "Bold": "Bold",
"LabelTheme": "Theme", "LabelTheme": "Theme",
"LabelThemeSongVolume": "Theme song volume",
"LabelTime": "Time", "LabelTime": "Time",
"LabelTimeLimitHours": "Time limit (hours)", "LabelTimeLimitHours": "Time limit (hours)",
"LabelTitle": "Title", "LabelTitle": "Title",
@ -1578,6 +1579,7 @@
"TellUsAboutYourself": "Tell us about yourself", "TellUsAboutYourself": "Tell us about yourself",
"TextSent": "Text sent.", "TextSent": "Text sent.",
"ThemeSongs": "Theme songs", "ThemeSongs": "Theme songs",
"ThemeSongVolumeHelp": "Adjust the volume of theme songs played while browsing the library.",
"ThemeVideos": "Theme videos", "ThemeVideos": "Theme videos",
"TheseSettingsAffectSubtitlesOnThisDevice": "These settings affect subtitles on this device", "TheseSettingsAffectSubtitlesOnThisDevice": "These settings affect subtitles on this device",
"ThisWizardWillGuideYou": "This wizard will help guide you through the setup process. To begin, please select your preferred language.", "ThisWizardWillGuideYou": "This wizard will help guide you through the setup process. To begin, please select your preferred language.",