mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-04-21 15:52:02 +00:00
subtitles picker adapted to latest changes in common
This commit is contained in:
parent
ae4bfda554
commit
671873aeb3
2 changed files with 109 additions and 188 deletions
|
|
@ -3,13 +3,16 @@ const PropTypes = require('prop-types');
|
||||||
const classnames = require('classnames');
|
const classnames = require('classnames');
|
||||||
const Icon = require('stremio-icons/dom');
|
const Icon = require('stremio-icons/dom');
|
||||||
const { Modal } = require('stremio-router');
|
const { Modal } = require('stremio-router');
|
||||||
const { ColorPicker } = require('stremio/common');
|
const { ColorInput } = require('stremio/common');
|
||||||
const styles = require('./styles');
|
const styles = require('./styles');
|
||||||
|
|
||||||
const ORIGIN_PRIORITIES = Object.freeze({
|
const ORIGIN_PRIORITIES = Object.freeze({
|
||||||
'LOCAL': 1,
|
'LOCAL': 1,
|
||||||
'EMBEDDED': 2
|
'EMBEDDED': 2
|
||||||
});
|
});
|
||||||
|
const LANGUAGE_PRIORITIES = Object.freeze({
|
||||||
|
'English': 1
|
||||||
|
});
|
||||||
const SUBTITLES_SIZE_LABELS = Object.freeze({
|
const SUBTITLES_SIZE_LABELS = Object.freeze({
|
||||||
1: '75%',
|
1: '75%',
|
||||||
2: '100%',
|
2: '100%',
|
||||||
|
|
@ -27,13 +30,9 @@ const comparatorWithPriorities = (priorities) => {
|
||||||
if (!isNaN(valueB)) return 1;
|
if (!isNaN(valueB)) return 1;
|
||||||
return a - b;
|
return a - b;
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
const NumberInput = ({ value, label, delta, onChange }) => {
|
const NumberInput = ({ value, label, delta, onChange }) => {
|
||||||
if (value === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles['number-input-container']}>
|
<div className={styles['number-input-container']}>
|
||||||
<div className={styles['number-input-button']} data-value={value - delta} onClick={onChange}>
|
<div className={styles['number-input-button']} data-value={value - delta} onClick={onChange}>
|
||||||
|
|
@ -47,100 +46,55 @@ const NumberInput = ({ value, label, delta, onChange }) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const SubtitlesColorPicker = ({ label, value, onChange }) => {
|
const SubtitlesPicker = (props) => {
|
||||||
const [open, setOpen] = React.useState(false);
|
const toggleSubtitleEnabled = React.useCallback(() => {
|
||||||
const onOpen = () => setOpen(true);
|
const selectedSubtitlesTrackId = props.selectedSubtitlesTrackId === null && props.subtitlesTracks.length > 0 ?
|
||||||
const onClose = () => setOpen(false);
|
props.subtitlesTracks[0].id
|
||||||
if (value === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<React.Fragment>
|
|
||||||
<div className={styles['color-picker-button-container']}>
|
|
||||||
<div style={{ backgroundColor: value }} className={styles['color-picker-indicator']} onClick={onOpen} />
|
|
||||||
<div className={styles['color-picker-label']}>{label}</div>
|
|
||||||
</div>
|
|
||||||
{
|
|
||||||
open ?
|
|
||||||
<Modal>
|
|
||||||
<div className={styles['color-picker-modal-container']} onClick={onClose}>
|
|
||||||
<ColorPicker className={styles['color-picker-container']} value={value} onChange={onChange} />
|
|
||||||
</div>
|
|
||||||
</Modal>
|
|
||||||
:
|
|
||||||
null
|
|
||||||
}
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
class SubtitlesPicker extends React.Component {
|
|
||||||
shouldComponentUpdate(nextProps, nextState) {
|
|
||||||
return nextProps.className !== this.props.className ||
|
|
||||||
nextProps.subtitlesTracks !== this.props.subtitlesTracks ||
|
|
||||||
nextProps.selectedSubtitlesTrackId !== this.props.selectedSubtitlesTrackId ||
|
|
||||||
nextProps.subtitlesSize !== this.props.subtitlesSize ||
|
|
||||||
nextProps.subtitlesDelay !== this.props.subtitlesDelay ||
|
|
||||||
nextProps.subtitlesTextColor !== this.props.subtitlesTextColor ||
|
|
||||||
nextProps.subtitlesBackgroundColor !== this.props.subtitlesBackgroundColor ||
|
|
||||||
nextProps.subtitlesOutlineColor !== this.props.subtitlesOutlineColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleSubtitleEnabled = () => {
|
|
||||||
const selectedSubtitlesTrackId = this.props.selectedSubtitlesTrackId === null && this.props.subtitlesTracks.length > 0 ?
|
|
||||||
this.props.subtitlesTracks[0].id
|
|
||||||
:
|
:
|
||||||
null;
|
null;
|
||||||
this.props.dispatch({ propName: 'selectedSubtitlesTrackId', propValue: selectedSubtitlesTrackId });
|
props.dispatch({ propName: 'selectedSubtitlesTrackId', propValue: selectedSubtitlesTrackId });
|
||||||
}
|
}, [props.selectedSubtitlesTrackId, props.subtitlesTracks, props.dispatch]);
|
||||||
|
const labelOnClick = React.useCallback((event) => {
|
||||||
labelOnClick = (event) => {
|
const subtitleTrack = props.subtitlesTracks.find(({ label, origin }) => {
|
||||||
const subtitleTrack = this.props.subtitlesTracks.find(({ label, origin }) => {
|
|
||||||
return label === event.currentTarget.dataset.label &&
|
return label === event.currentTarget.dataset.label &&
|
||||||
origin === event.currentTarget.dataset.origin;
|
origin === event.currentTarget.dataset.origin;
|
||||||
});
|
});
|
||||||
if (subtitleTrack) {
|
if (subtitleTrack) {
|
||||||
this.props.dispatch({ propName: 'selectedSubtitlesTrackId', propValue: subtitleTrack.id });
|
props.dispatch({ propName: 'selectedSubtitlesTrackId', propValue: subtitleTrack.id });
|
||||||
}
|
}
|
||||||
}
|
}, [props.subtitlesTracks, props.dispatch]);
|
||||||
|
const variantOnClick = React.useCallback((event) => {
|
||||||
variantOnClick = (event) => {
|
props.dispatch({ propName: 'selectedSubtitlesTrackId', propValue: event.currentTarget.dataset.trackId });
|
||||||
this.props.dispatch({ propName: 'selectedSubtitlesTrackId', propValue: event.currentTarget.dataset.trackId });
|
}, [props.dispatch]);
|
||||||
}
|
const setsubtitlesSize = React.useCallback((event) => {
|
||||||
|
props.dispatch({ propName: 'subtitlesSize', propValue: event.currentTarget.dataset.value });
|
||||||
setsubtitlesSize = (event) => {
|
}, [props.dispatch]);
|
||||||
this.props.dispatch({ propName: 'subtitlesSize', propValue: event.currentTarget.dataset.value });
|
const setSubtitlesDelay = React.useCallback((event) => {
|
||||||
}
|
props.dispatch({ propName: 'subtitlesDelay', propValue: event.currentTarget.dataset.value });
|
||||||
|
}, [props.dispatch]);
|
||||||
setSubtitlesDelay = (event) => {
|
const setSubtitlesTextColor = React.useCallback((event) => {
|
||||||
this.props.dispatch({ propName: 'subtitlesDelay', propValue: event.currentTarget.dataset.value });
|
props.dispatch({ propName: 'subtitlesTextColor', propValue: event.nativeEvent.value });
|
||||||
}
|
}, [props.dispatch]);
|
||||||
|
const setSubtitlesBackgroundColor = React.useCallback((color) => {
|
||||||
setSubtitlesTextColor = (color) => {
|
props.dispatch({ propName: 'subtitlesBackgroundColor', propValue: color });
|
||||||
this.props.dispatch({ propName: 'subtitlesTextColor', propValue: color });
|
}, [props.dispatch]);
|
||||||
}
|
const setSubtitlesOutlineColor = React.useCallback((color) => {
|
||||||
|
props.dispatch({ propName: 'subtitlesOutlineColor', propValue: color });
|
||||||
setSubtitlesBackgroundColor = (color) => {
|
}, [props.dispatch]);
|
||||||
this.props.dispatch({ propName: 'subtitlesBackgroundColor', propValue: color });
|
const selectedTrack = props.subtitlesTracks.find(({ id }) => id === props.selectedSubtitlesTrackId);
|
||||||
}
|
const groupedTracks = props.subtitlesTracks.reduce((result, track) => {
|
||||||
|
result[track.origin] = result[track.origin] || {};
|
||||||
setSubtitlesOutlineColor = (color) => {
|
result[track.origin][track.label] = result[track.origin][track.label] || [];
|
||||||
this.props.dispatch({ propName: 'subtitlesOutlineColor', propValue: color });
|
result[track.origin][track.label].push(track);
|
||||||
}
|
return result;
|
||||||
|
}, {});
|
||||||
renderToggleButton({ selectedTrack }) {
|
return (
|
||||||
return (
|
<div className={classnames(props.className, styles['subtitles-picker-container'])}>
|
||||||
<div className={styles['toggle-button-container']} onClick={this.toggleSubtitleEnabled}>
|
<div className={styles['toggle-button-container']} onClick={toggleSubtitleEnabled}>
|
||||||
<div className={styles['toggle-label']}>ON</div>
|
<div className={styles['toggle-label']}>ON</div>
|
||||||
<div className={styles['toggle-label']}>OFF</div>
|
<div className={styles['toggle-label']}>OFF</div>
|
||||||
<div className={classnames(styles['toggle-thumb'], { [styles['on']]: !!selectedTrack })} />
|
<div className={classnames(styles['toggle-thumb'], { [styles['on']]: !!selectedTrack })} />
|
||||||
</div>
|
</div>
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderLabelsList({ groupedTracks, selectedTrack }) {
|
|
||||||
return (
|
|
||||||
<div className={styles['labels-list-container']}>
|
<div className={styles['labels-list-container']}>
|
||||||
{
|
{
|
||||||
Object.keys(groupedTracks)
|
Object.keys(groupedTracks)
|
||||||
|
|
@ -150,13 +104,13 @@ class SubtitlesPicker extends React.Component {
|
||||||
<div className={styles['track-origin']}>{origin}</div>
|
<div className={styles['track-origin']}>{origin}</div>
|
||||||
{
|
{
|
||||||
Object.keys(groupedTracks[origin])
|
Object.keys(groupedTracks[origin])
|
||||||
.sort(comparatorWithPriorities(this.props.languagePriorities))
|
.sort(comparatorWithPriorities(LANGUAGE_PRIORITIES))
|
||||||
.map((label) => {
|
.map((label) => {
|
||||||
const selected = selectedTrack && selectedTrack.label === label && selectedTrack.origin === origin;
|
const selected = selectedTrack && selectedTrack.label === label && selectedTrack.origin === origin;
|
||||||
return (
|
return (
|
||||||
<div key={label}
|
<div key={label}
|
||||||
className={classnames(styles['language-label'], { [styles['selected']]: selected })}
|
className={classnames(styles['language-label'], { [styles['selected']]: selected })}
|
||||||
onClick={this.labelOnClick}
|
onClick={labelOnClick}
|
||||||
data-label={label}
|
data-label={label}
|
||||||
data-origin={origin}
|
data-origin={origin}
|
||||||
children={label}
|
children={label}
|
||||||
|
|
@ -168,115 +122,83 @@ class SubtitlesPicker extends React.Component {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
);
|
{
|
||||||
}
|
!selectedTrack ?
|
||||||
|
<div className={styles['preferences-container']}>
|
||||||
renderVariantsList({ groupedTracks, selectedTrack }) {
|
<div className={styles['subtitles-disabled-label']}>Subtitles are disabled</div>
|
||||||
if (groupedTracks[selectedTrack.origin][selectedTrack.label].length <= 1) {
|
</div>
|
||||||
return null;
|
:
|
||||||
}
|
<div className={styles['preferences-container']}>
|
||||||
|
<div className={styles['preferences-title']}>Preferences</div>
|
||||||
return (
|
{
|
||||||
<div className={styles['variants-container']}>
|
groupedTracks[selectedTrack.origin][selectedTrack.label].length > 1 ?
|
||||||
{
|
<div className={styles['variants-container']}>
|
||||||
groupedTracks[selectedTrack.origin][selectedTrack.label].map((track, index) => (
|
{
|
||||||
<div key={track.id}
|
groupedTracks[selectedTrack.origin][selectedTrack.label].map((track, index) => (
|
||||||
className={classnames(styles['variant-button'], { [styles['selected']]: track.id === selectedTrack.id })}
|
<div key={track.id}
|
||||||
title={track.id}
|
className={classnames(styles['variant-button'], { [styles['selected']]: track.id === selectedTrack.id })}
|
||||||
onClick={this.variantOnClick}
|
title={track.id}
|
||||||
data-track-id={track.id}
|
onClick={variantOnClick}
|
||||||
children={index + 1}
|
data-track-id={track.id}
|
||||||
|
children={index + 1}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
null
|
||||||
|
}
|
||||||
|
<div className={styles['color-picker-button-container']}>
|
||||||
|
<ColorInput
|
||||||
|
className={styles['color-picker-indicator']}
|
||||||
|
value={props.subtitlesTextColor}
|
||||||
|
onChange={setSubtitlesTextColor}
|
||||||
|
/>
|
||||||
|
<div className={styles['color-picker-label']}>Text color</div>
|
||||||
|
</div>
|
||||||
|
{/* <SubtitlesColorPicker
|
||||||
|
label={'Background color'}
|
||||||
|
value={props.subtitlesBackgroundColor}
|
||||||
|
onChange={setSubtitlesBackgroundColor}
|
||||||
/>
|
/>
|
||||||
))
|
<SubtitlesColorPicker
|
||||||
}
|
label={'Outline color'}
|
||||||
</div>
|
value={props.subtitlesOutlineColor}
|
||||||
);
|
onChange={setSubtitlesOutlineColor}
|
||||||
}
|
/> */}
|
||||||
|
<NumberInput
|
||||||
renderPreferences({ groupedTracks, selectedTrack }) {
|
label={SUBTITLES_SIZE_LABELS[props.subtitlesSize]}
|
||||||
if (!selectedTrack) {
|
value={props.subtitlesSize}
|
||||||
return (
|
delta={1}
|
||||||
<div className={styles['preferences-container']}>
|
onChange={setsubtitlesSize}
|
||||||
<div className={styles['subtitles-disabled-label']}>Subtitles are disabled</div>
|
/>
|
||||||
</div>
|
<NumberInput
|
||||||
);
|
label={`${(props.subtitlesDelay / 1000).toFixed(2)}s`}
|
||||||
}
|
value={props.subtitlesDelay}
|
||||||
|
delta={100}
|
||||||
return (
|
onChange={setSubtitlesDelay}
|
||||||
<div className={styles['preferences-container']}>
|
/>
|
||||||
<div className={styles['preferences-title']}>Preferences</div>
|
</div>
|
||||||
{this.renderVariantsList({ groupedTracks, selectedTrack })}
|
}
|
||||||
<SubtitlesColorPicker
|
</div>
|
||||||
label={'Text color'}
|
);
|
||||||
value={this.props.subtitlesTextColor}
|
};
|
||||||
onChange={this.setSubtitlesTextColor}
|
|
||||||
/>
|
|
||||||
<SubtitlesColorPicker
|
|
||||||
label={'Background color'}
|
|
||||||
value={this.props.subtitlesBackgroundColor}
|
|
||||||
onChange={this.setSubtitlesBackgroundColor}
|
|
||||||
/>
|
|
||||||
<SubtitlesColorPicker
|
|
||||||
label={'Outline color'}
|
|
||||||
value={this.props.subtitlesOutlineColor}
|
|
||||||
onChange={this.setSubtitlesOutlineColor}
|
|
||||||
/>
|
|
||||||
<NumberInput
|
|
||||||
label={SUBTITLES_SIZE_LABELS[this.props.subtitlesSize]}
|
|
||||||
value={this.props.subtitlesSize}
|
|
||||||
delta={1}
|
|
||||||
onChange={this.setsubtitlesSize}
|
|
||||||
/>
|
|
||||||
<NumberInput
|
|
||||||
label={`${(this.props.subtitlesDelay / 1000).toFixed(2)}s`}
|
|
||||||
value={this.props.subtitlesDelay}
|
|
||||||
delta={100}
|
|
||||||
onChange={this.setSubtitlesDelay}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const selectedTrack = this.props.subtitlesTracks.find(({ id }) => id === this.props.selectedSubtitlesTrackId);
|
|
||||||
const groupedTracks = this.props.subtitlesTracks.reduce((result, track) => {
|
|
||||||
result[track.origin] = result[track.origin] || {};
|
|
||||||
result[track.origin][track.label] = result[track.origin][track.label] || [];
|
|
||||||
result[track.origin][track.label].push(track);
|
|
||||||
return result;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={classnames(this.props.className, styles['subtitles-picker-container'])}>
|
|
||||||
{this.renderToggleButton({ selectedTrack })}
|
|
||||||
{this.renderLabelsList({ groupedTracks, selectedTrack })}
|
|
||||||
{this.renderPreferences({ groupedTracks, selectedTrack })}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SubtitlesPicker.propTypes = {
|
SubtitlesPicker.propTypes = {
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
languagePriorities: PropTypes.objectOf(PropTypes.number).isRequired,
|
languagePriorities: PropTypes.objectOf(PropTypes.number),
|
||||||
subtitlesTracks: PropTypes.arrayOf(PropTypes.shape({
|
subtitlesTracks: PropTypes.arrayOf(PropTypes.shape({
|
||||||
id: PropTypes.string.isRequired,
|
id: PropTypes.string.isRequired,
|
||||||
label: PropTypes.string.isRequired,
|
label: PropTypes.string.isRequired,
|
||||||
origin: PropTypes.string.isRequired
|
origin: PropTypes.string.isRequired
|
||||||
})).isRequired,
|
})),
|
||||||
selectedSubtitlesTrackId: PropTypes.string,
|
selectedSubtitlesTrackId: PropTypes.string,
|
||||||
subtitlesSize: PropTypes.number,
|
subtitlesSize: PropTypes.number,
|
||||||
subtitlesDelay: PropTypes.number,
|
subtitlesDelay: PropTypes.number,
|
||||||
subtitlesTextColor: PropTypes.string,
|
subtitlesTextColor: PropTypes.string,
|
||||||
subtitlesBackgroundColor: PropTypes.string,
|
subtitlesBackgroundColor: PropTypes.string,
|
||||||
subtitlesOutlineColor: PropTypes.string,
|
subtitlesOutlineColor: PropTypes.string,
|
||||||
dispatch: PropTypes.func.isRequired
|
dispatch: PropTypes.func
|
||||||
};
|
|
||||||
SubtitlesPicker.defaultProps = {
|
|
||||||
subtitlesTracks: Object.freeze([]),
|
|
||||||
languagePriorities: Object.freeze({
|
|
||||||
English: 1
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = SubtitlesPicker;
|
module.exports = SubtitlesPicker;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
.subtitles-picker-container {
|
.subtitles-picker-container {
|
||||||
width: calc(var(--subtitles-picker-button-size) * 14);
|
--subtitles-picker-button-size: 2.5rem;
|
||||||
height: calc(var(--subtitles-picker-button-size) * 9);
|
|
||||||
font-size: calc(var(--subtitles-picker-button-size) * 0.45);
|
font-size: calc(var(--subtitles-picker-button-size) * 0.45);
|
||||||
padding: calc(var(--subtitles-picker-button-size) * 0.3);
|
padding: calc(var(--subtitles-picker-button-size) * 0.3);
|
||||||
gap: calc(var(--subtitles-picker-button-size) * 0.3);
|
gap: calc(var(--subtitles-picker-button-size) * 0.3);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue