Skip to content

Commit

Permalink
Merge branch 'master' into pdf
Browse files Browse the repository at this point in the history
  • Loading branch information
dulli authored Sep 9, 2023
2 parents f4a1d33 + 0dd130c commit be24b69
Show file tree
Hide file tree
Showing 30 changed files with 696 additions and 226 deletions.
185 changes: 185 additions & 0 deletions src/apps/experimental/components/library/SortButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
import React, { FC, useCallback } from 'react';
import IconButton from '@mui/material/IconButton';
import MenuItem from '@mui/material/MenuItem';
import Popover from '@mui/material/Popover';
import Typography from '@mui/material/Typography';
import Divider from '@mui/material/Divider';
import Box from '@mui/material/Box';
import InputLabel from '@mui/material/InputLabel';
import FormControl from '@mui/material/FormControl';
import Select, { SelectChangeEvent } from '@mui/material/Select';
import SortByAlphaIcon from '@mui/icons-material/SortByAlpha';

import globalize from 'scripts/globalize';
import { LibraryViewSettings } from 'types/library';
import { LibraryTab } from 'types/libraryTab';
import { ItemSortBy } from '@jellyfin/sdk/lib/models/api/item-sort-by';
import { SortOrder } from '@jellyfin/sdk/lib/generated-client';

const sortMenuOptions = [
{ label: 'Name', value: ItemSortBy.SortName },
{ label: 'OptionRandom', value: ItemSortBy.Random },
{ label: 'OptionImdbRating', value: ItemSortBy.CommunityRating },
{ label: 'OptionCriticRating', value: ItemSortBy.CriticRating },
{ label: 'OptionDateAdded', value: ItemSortBy.DateCreated },
{ label: 'OptionDatePlayed', value: ItemSortBy.DatePlayed },
{ label: 'OptionParentalRating', value: ItemSortBy.OfficialRating },
{ label: 'OptionPlayCount', value: ItemSortBy.PlayCount },
{ label: 'OptionReleaseDate', value: ItemSortBy.PremiereDate },
{ label: 'Runtime', value: ItemSortBy.Runtime }
];

const sortOrderMenuOptions = [
{ label: 'Ascending', value: SortOrder.Ascending },
{ label: 'Descending', value: SortOrder.Descending }
];

interface SortButtonProps {
viewType: LibraryTab;
libraryViewSettings: LibraryViewSettings;
setLibraryViewSettings: React.Dispatch<
React.SetStateAction<LibraryViewSettings>
>;
}

const SortButton: FC<SortButtonProps> = ({
viewType,
libraryViewSettings,
setLibraryViewSettings
}) => {
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const open = Boolean(anchorEl);
const id = open ? 'sort-popover' : undefined;

const handleClick = useCallback((event: React.MouseEvent<HTMLElement>) => {
setAnchorEl(event.currentTarget);
}, []);

const handleClose = useCallback(() => {
setAnchorEl(null);
}, []);

const onSelectChange = useCallback(
(event: SelectChangeEvent) => {
const name = event.target.name;

setLibraryViewSettings((prevState) => ({
...prevState,
StartIndex: 0,
[name]: event.target.value
}));
},
[setLibraryViewSettings]
);

const getVisibleSortMenu = () => {
const visibleSortMenu: ItemSortBy[] = [ItemSortBy.SortName, ItemSortBy.Random, ItemSortBy.DateCreated];

if (
viewType !== LibraryTab.Photos
&& viewType !== LibraryTab.Videos
&& viewType !== LibraryTab.Books
) {
visibleSortMenu.push(ItemSortBy.CommunityRating);
visibleSortMenu.push(ItemSortBy.CriticRating);
visibleSortMenu.push(ItemSortBy.DatePlayed);
visibleSortMenu.push(ItemSortBy.OfficialRating);
visibleSortMenu.push(ItemSortBy.PlayCount);
visibleSortMenu.push(ItemSortBy.PremiereDate);
visibleSortMenu.push(ItemSortBy.Runtime);
}

return visibleSortMenu;
};

return (
<Box>
<IconButton
title={globalize.translate('Sort')}
sx={{ ml: 2 }}
aria-describedby={id}
className='paper-icon-button-light btnShuffle autoSize'
onClick={handleClick}
>
<SortByAlphaIcon />
</IconButton>
<Popover
id={id}
open={open}
anchorEl={anchorEl}
onClose={handleClose}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'center'
}}
transformOrigin={{
vertical: 'top',
horizontal: 'center'
}}
sx={{
'& .MuiFormControl-root': { m: 1, width: 200 }
}}
>

<FormControl fullWidth>
<InputLabel id='select-sort-label'>
<Typography component='span'>
{globalize.translate('LabelSortBy')}
</Typography>
</InputLabel>
<Select
labelId='select-sort-label'
id='selectSortBy'
value={libraryViewSettings.SortBy}
label={globalize.translate('LabelSortBy')}
name='SortBy'
onChange={onSelectChange}
>
{sortMenuOptions
.filter((option) => getVisibleSortMenu().includes(option.value))
.map((option) => (
<MenuItem
key={option.value}
value={option.value}
>
<Typography component='span'>
{globalize.translate(option.label)}
</Typography>
</MenuItem>
))}
</Select>
</FormControl>

<Divider />
<FormControl fullWidth>
<InputLabel id='select-sortorder-label'>
<Typography component='span'>
{globalize.translate('LabelSortOrder')}
</Typography>
</InputLabel>
<Select
labelId='select-sortorder-label'
id='selectSortOrder'
value={libraryViewSettings.SortOrder}
label={globalize.translate('LabelSortOrder')}
name='SortOrder'
onChange={onSelectChange}
>
{sortOrderMenuOptions.map((option) => (
<MenuItem
key={option.value}
value={option.value}
>
<Typography component='span'>
{option.label}
</Typography>
</MenuItem>
))}
</Select>
</FormControl>
</Popover>
</Box>
);
};

export default SortButton;
13 changes: 13 additions & 0 deletions src/components/nowPlayingBar/nowPlayingBar.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ let volumeSlider;
let volumeSliderContainer;
let playPauseButtons;
let positionSlider;
let toggleAirPlayButton;
let toggleRepeatButton;
let toggleRepeatButtonIcon;

Expand Down Expand Up @@ -78,6 +79,8 @@ function getNowPlayingBarHtml() {
html += '<input type="range" is="emby-slider" pin step="1" min="0" max="100" value="0" class="slider-medium-thumb nowPlayingBarVolumeSlider"/>';
html += '</div>';

html += '<button is="paper-icon-button-light" class="btnAirPlay mediaButton"><span class="material-icons airplay" aria-hidden="true"></span></button>';

html += '<button is="paper-icon-button-light" class="toggleRepeatButton mediaButton"><span class="material-icons repeat" aria-hidden="true"></span></button>';
html += '<button is="paper-icon-button-light" class="btnShuffleQueue mediaButton"><span class="material-icons shuffle" aria-hidden="true"></span></button>';

Expand Down Expand Up @@ -192,6 +195,13 @@ function bindEvents(elem) {
}
});

toggleAirPlayButton = elem.querySelector('.btnAirPlay');
toggleAirPlayButton.addEventListener('click', function () {
if (currentPlayer) {
playbackManager.toggleAirPlay(currentPlayer);
}
});

elem.querySelector('.btnShuffleQueue').addEventListener('click', function () {
if (currentPlayer) {
playbackManager.toggleQueueShuffleMode();
Expand Down Expand Up @@ -328,6 +338,9 @@ function updatePlayerStateInternal(event, state, player) {
toggleRepeatButton.classList.remove('hide');
}

const hideAirPlayButton = supportedCommands.indexOf('AirPlay') === -1;
toggleAirPlayButton.classList.toggle('hide', hideAirPlayButton);

updateRepeatModeDisplay(playbackManager.getRepeatMode());
onQueueShuffleModeChange();

Expand Down
34 changes: 18 additions & 16 deletions src/components/subtitlesync/subtitlesync.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ function init(instance) {
playbackManager.setSubtitleOffset(inputOffset, player);
// synchronize with slider value
subtitleSyncSlider.updateOffset(
getPercentageFromOffset(inputOffset));
getSliderValueFromOffset(inputOffset));
} else {
this.textContent = (playbackManager.getPlayerSubtitleOffset(player) || 0) + 's';
}
Expand All @@ -79,17 +79,17 @@ function init(instance) {
}
};

subtitleSyncSlider.updateOffset = function (percent) {
// default value is 0s = 50%
this.value = percent === undefined ? 50 : percent;
subtitleSyncSlider.updateOffset = function (sliderValue) {
// default value is 0s = 0ms
this.value = sliderValue === undefined ? 0 : sliderValue;
};

subtitleSyncSlider.addEventListener('change', function () {
// set new offset
playbackManager.setSubtitleOffset(getOffsetFromPercentage(this.value), player);
playbackManager.setSubtitleOffset(getOffsetFromSliderValue(this.value), player);
// synchronize with textField value
subtitleSyncTextField.updateOffset(
getOffsetFromPercentage(this.value));
getOffsetFromSliderValue(this.value));
});

subtitleSyncSlider.getBubbleHtml = function (value) {
Expand All @@ -108,20 +108,22 @@ function init(instance) {
}

function getOffsetFromPercentage(value) {
// convert percent to fraction
// convert percentage to fraction
let offset = (value - 50) / 50;
// multiply by offset min/max range value (-x to +x) :
offset *= 30;
return offset.toFixed(1);
}

function getPercentageFromOffset(value) {
// divide by offset min/max range value (-x to +x) :
let percentValue = value / 30;
// convert fraction to percent
percentValue *= 50;
percentValue += 50;
return Math.min(100, Math.max(0, percentValue.toFixed(1)));
function getOffsetFromSliderValue(value) {
// convert slider value to offset
const offset = value / 10;
return offset.toFixed(1);
}

function getSliderValueFromOffset(value) {
const sliderValue = value * 10;
return Math.min(300, Math.max(-300, sliderValue.toFixed(1)));
}

class SubtitleSync {
Expand Down Expand Up @@ -155,8 +157,8 @@ class SubtitleSync {
if (playbackManager.isShowingSubtitleOffsetEnabled(player) && playbackManager.canHandleOffsetOnCurrentSubtitle(player)) {
// if no subtitle offset is defined or element has focus (offset being defined)
if (!(playbackManager.getPlayerSubtitleOffset(player) || subtitleSyncTextField.hasFocus)) {
// set default offset to '0' = 50%
subtitleSyncSlider.value = '50';
// set default offset to '0' = 0ms
subtitleSyncSlider.value = '0';
subtitleSyncTextField.textContent = '0s';
playbackManager.setSubtitleOffset(0, player);
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/subtitlesync/subtitlesync.template.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<button type="button" is="paper-icon-button-light" class="subtitleSync-closeButton"><span class="material-icons close" aria-hidden="true"></span></button>
<div class="subtitleSyncTextField" contenteditable="true" spellcheck="false">0s</div>
<div class="sliderContainer subtitleSyncSliderContainer">
<input is="emby-slider" type="range" step=".1" min="0" max="100" value="50" class="subtitleSyncSlider" data-slider-keep-progress="true" />
<input is="emby-slider" type="range" step="1" min="-300" max="300" value="0" class="subtitleSyncSlider" data-slider-keep-progress="true" />
</div>
</div>
</div>
8 changes: 4 additions & 4 deletions src/controllers/movies/moviecollections.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,15 @@ export default function (view, params, tabContent) {
EnableImageTypes: 'Primary,Backdrop,Banner,Thumb',
StartIndex: 0
},
view: libraryBrowser.getSavedView(key) || 'Poster'
view: userSettings.getSavedView(key) || 'Poster'
};

if (userSettings.libraryPageSize() > 0) {
pageData.query['Limit'] = userSettings.libraryPageSize();
}

pageData.query.ParentId = params.topParentId;
libraryBrowser.loadSavedQueryValues(key, pageData.query);
userSettings.loadQuerySettings(key, pageData.query);
}

return pageData;
Expand Down Expand Up @@ -183,7 +183,7 @@ export default function (view, params, tabContent) {
const itemsContainer = tabContent.querySelector('.itemsContainer');
itemsContainer.innerHTML = html;
imageLoader.lazyChildren(itemsContainer);
libraryBrowser.saveQueryValues(getSavedQueryKey(), query);
userSettings.saveQuerySettings(getSavedQueryKey(), query);
loading.hide();
isLoading = false;

Expand Down Expand Up @@ -234,7 +234,7 @@ export default function (view, params, tabContent) {
btnSelectView.addEventListener('layoutchange', function (e) {
const viewStyle = e.detail.viewStyle;
getPageData().view = viewStyle;
libraryBrowser.saveViewSetting(getSavedQueryKey(), viewStyle);
userSettings.saveViewSetting(getSavedQueryKey(), viewStyle);
getQuery().StartIndex = 0;
onViewStyleChange();
reloadItems(tabElement);
Expand Down
10 changes: 5 additions & 5 deletions src/controllers/movies/moviegenres.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import escapeHtml from 'escape-html';
import layoutManager from '../../components/layoutManager';
import loading from '../../components/loading/loading';
import libraryBrowser from '../../scripts/libraryBrowser';
import * as userSettings from '../../scripts/settings/userSettings';
import cardBuilder from '../../components/cardbuilder/cardBuilder';
import lazyLoader from '../../components/lazyLoader/lazyLoaderIntersectionObserver';
import globalize from '../../scripts/globalize';
Expand All @@ -22,10 +22,10 @@ export default function (view, params, tabContent) {
Recursive: true,
EnableTotalRecordCount: false
},
view: 'Poster'
view: userSettings.getSavedView(key) || 'Poster'
};
pageData.query.ParentId = params.topParentId;
libraryBrowser.loadSavedQueryValues(key, pageData.query);
userSettings.loadQuerySettings(key, pageData.query);
}

return pageData;
Expand Down Expand Up @@ -181,7 +181,7 @@ export default function (view, params, tabContent) {

elem.innerHTML = html;
lazyLoader.lazyChildren(elem, fillItemsContainer);
libraryBrowser.saveQueryValues(getSavedQueryKey(), query);
userSettings.saveQuerySettings(getSavedQueryKey(), query);
loading.hide();
});
}
Expand All @@ -203,7 +203,7 @@ export default function (view, params, tabContent) {

this.setCurrentViewStyle = function (viewStyle) {
getPageData().view = viewStyle;
libraryBrowser.saveViewSetting(getSavedQueryKey(), viewStyle);
userSettings.saveViewSetting(getSavedQueryKey(), viewStyle);
fullyReload();
};

Expand Down
Loading

0 comments on commit be24b69

Please sign in to comment.