diff --git a/src/routes/Settings/Settings.js b/src/routes/Settings/Settings.js index 3e4ee5a11..265e389c8 100644 --- a/src/routes/Settings/Settings.js +++ b/src/routes/Settings/Settings.js @@ -5,100 +5,156 @@ import { Input, Popup, Checkbox } from 'stremio-common'; import classnames from 'classnames'; import styles from './styles'; +const SECTIONS_ORDER = { + 'General': 1, + 'Player': 2, + 'Streaming': 3 +}; + class Settings extends Component { constructor(props) { super(props); - this.state = {} + this.state = { + selectedSectionId: null, + sections: [], + inputs: [] + }; } - static getDerivedStateFromProps(nextProps, prevState) { - const sections = nextProps.settingsConfiguration.reduce((sections, setting) => { - if (!sections[setting.section]) { - sections[setting.section] = { + componentDidMount() { + this.settingsOnUpdate([ + { section: 'General', label: 'LOG OUT', type: 'button' }, + { section: 'General', label: 'Change password', type: 'link', href: '' }, + { section: 'General', label: 'Import options', type: 'text' }, + { section: 'General', label: 'Import from Facebook', type: 'link', href: '' }, + { section: 'General', label: 'Export user data', type: 'link', href: '' }, + { section: 'General', label: 'Subscribe to calendar', type: 'link', href: '' }, + { section: 'General', label: 'Contact support', type: 'link', href: 'https://stremio.zendesk.com/' }, + { section: 'General', label: 'Request account deletion', type: 'link', href: 'https://docs.google.com/forms/d/e/1FAIpQLScubrlTpDMIPUUBlhZ5lwcXl3HxzKfunIMCX5Jnp-cDyglWjQ/viewform?usp=sf_link' }, + { section: 'General', header: 'Trakt Scrobbling', label: 'AUTHENTICATE', type: 'button', icon: 'ic_trackt' }, + { section: 'General', header: 'UI Language', label: 'UI Language', type: 'select', options: ['Български език', 'English', 'Deutsch', 'Español', 'Italiano'], value: 'English' }, + { section: 'Player', label: 'ADD-ONS', type: 'button', icon: 'ic_addons' }, + { section: 'Player', header: 'Default Subtitles Language', label: 'Default Subtitles Language', type: 'select', options: ['English', 'Nederlands', 'Avesta', 'Български език', 'Deutsch', 'Español', 'Italiano'], value: 'English' }, + { section: 'Player', header: 'Default Subtitles Size', label: 'Default Subtitles Size', type: 'select', options: ['72%', '80%', '100%', '120%', '140%', '160%', '180%'], value: '100%' }, + { section: 'Player', header: 'Subtitles Background', label: 'Subtitles background', type: 'select', options: ['None', 'Solid', 'Transparent'], value: 'None' }, + { section: 'Player', header: 'Subtitles color', label: 'Subtitles color', type: 'color', color: '#FFFFFF' }, + { section: 'Player', header: 'Subtitles outline color', label: 'Subtitles outline color', type: 'color', color: '#000000' }, + { section: 'Player', label: 'Auto-play next episode', type: 'checkbox', value: true }, + { section: 'Player', label: 'Pause playback when minimized', type: 'checkbox', value: false }, + { section: 'Player', label: 'Hardware-accelerated decoding', type: 'checkbox', value: true }, + { section: 'Player', label: 'Launch player in a separate window (advanced)', type: 'checkbox', value: true }, + { section: 'Streaming', header: 'Caching', label: 'Caching', type: 'select', options: ['No Caching', '2GB', '5GB', '10GB'], value: '2GB' }, + { section: 'Streaming', header: 'Torrent Profile', label: 'Torrent Profile', type: 'select', options: ['Default', 'Soft', 'Fast'], value: 'Default' }, + { section: 'Streaming', header: 'Streaming server URL: http://127.0.0.1:11470', label: 'Streaming server is available.', type: 'text', icon: 'ic_check' } + ]); + } + + settingsOnUpdate = (settings) => { + this.setState(({ selectedSectionId, inputs }) => { + const sections = settings.map(({ section }) => section) + .filter((section, index, sections) => sections.indexOf(section) === index) + .sort(function(a, b) { + const valueA = SECTIONS_ORDER[a]; + const valueB = SECTIONS_ORDER[b]; + if (!isNaN(valueA) && !isNaN(valueB)) return valueA - valueB; + if (!isNaN(valueA)) return -1; + if (!isNaN(valueB)) return 1; + return a - b; + }) + .map((section) => ({ + id: section, + ref: React.createRef() + })); + + return { + selectedSectionId: selectedSectionId !== null && sections.map(({ id }) => id === selectedSectionId) ? selectedSectionId : (sections.length > 0 ? sections[0].id : null), + sections: sections, + inputs: settings.map((setting) => ({ + ...setting, + id: setting.label, ref: React.createRef(), - order: Object.keys(sections).length, - inputs: [] - }; + active: inputs.find(({ id }) => id === setting.id) + })) } - - sections[setting.section].inputs.push({ - ref: React.createRef(), - active: prevState.sections && prevState.sections[setting.section].inputs.find(({ label }) => label === setting.label).active, - ...setting - }); - return sections; - }, {}); - - const selectedSection = sections[prevState.selectedSection] ? - prevState.selectedSection - : - Object.keys(sections).find((sectionName) => sections[sectionName].order === 0); - - return { selectedSection, sections }; + }); } changeSection = (event) => { - this.setState({ selectedSection: event.currentTarget.dataset.section }); + this.setState({ selectedSectionId: event.currentTarget.dataset.section }); } shouldComponentUpdate(nextProps, nextState) { - return nextState.selectedSection !== this.state.selectedSection || - nextState.sections !== this.state.sections; + return nextState.selectedSectionId !== this.state.selectedSectionId || + nextState.sections !== this.state.sections || + nextState.inputs !== this.state.inputs; } componentDidUpdate(prevProps, prevState) { - if (prevState.selectedSection !== this.state.selectedSection) { - const sectionName = Object.keys(this.state.sections).find((sectionName) => sectionName === this.state.selectedSection); - this.state.sections[sectionName].ref.current.scrollIntoView(); + if (prevState.selectedSectionId !== this.state.selectedSectionId) { + const sectionName = this.state.sections.find((sectionName) => sectionName.id === this.state.selectedSectionId); + sectionName.ref.current.scrollIntoView(); } - Object.values(this.state.sections).forEach(({ inputs }) => { - inputs.forEach((input) => { - input.ref.current && !input.active && input.ref.current.close && input.ref.current.close(); - }); + this.state.inputs.forEach((input) => { + input.ref.current && !input.active && input.ref.current.close && input.ref.current.close(); }); } activate = (label) => { - this.setState(({ sections }) => ({ - sections: Object.keys(sections).reduce((nextSections, sectionName) => { - nextSections[sectionName] = nextSections[sectionName] || sections[sectionName]; - nextSections[sectionName].inputs = nextSections[sectionName].inputs.map((input) => ({ - ...input, - active: label === input.label - })); - return nextSections; - }, {}) + this.setState(({ inputs }) => ({ + inputs: inputs.map((input) => ({ + ...input, + active: label === input.label + })) })); } - deactivate = () => { - this.setState(({ sections }) => ({ - sections: Object.keys(sections).reduce((nextSections, sectionName) => { - nextSections[sectionName] = nextSections[sectionName] || sections[sectionName]; - nextSections[sectionName].inputs = nextSections[sectionName].inputs.map((input) => ({ + deactivate = (label) => { + this.setState(({ inputs }) => ({ + inputs: inputs.map((input) => ({ + ...input, + active: label === input.label ? false : input.active + })) + })); + } + + toggleCheckbox = (label) => { + this.setState(({ inputs }) => ({ + inputs: inputs.map((input) => ({ + ...input, + value: label === input.label ? !input.value : input.value + })) + })); + } + + onChange = (event) => { + var data = event.currentTarget.dataset; + + this.setState(({ inputs }) => { + return { + inputs: inputs.map((input) => ({ ...input, + value: data.label === input.id ? data.option : input.value, active: false - })); - return nextSections; - }, {}) - })); + })) + } + }) } - renderPopup({ ref, activate, deactivate, active, label, inputLabel, array, onClick }) { + renderPopup({ ref, activate, deactivate, active, value, inputLabel, options, onClick }) { return ( - +
-
{label}
+
{value}
- {array.map((element) => -
{element}
+ {options.map((option) => +
{option}
)}
@@ -122,16 +178,18 @@ class Settings extends Component { return (
- {Object.keys(this.state.sections).map((section) => -
{section}
+ {this.state.sections.map((section) => +
+ {section.id} +
)}
- {Object.keys(this.state.sections).map((section) => -
-
{section}
+ {this.state.sections.map((section) => +
+
{section.id}
{ - section === 'General' + section.id === 'General' ?
{this.renderAvatar()} @@ -140,69 +198,71 @@ class Settings extends Component { : null } - {this.state.sections[section].inputs.map((input) => { - if (input.type === 'select') { - return ( -
- {input.header ?
{input.header}
: null} - {this.renderPopup({ - ref: input.ref, - activate: this.activate, - deactivate: this.deactivate, - active: input.active, - inputLabel: input.label, - label: input.options[0], - array: input.options, - onClick: input.onChange - })} -
- ); - } else if (input.type === 'link') { - return ( -
- {input.header ?
{input.header}
: null} - {input.label} -
- ); - } else if (input.type === 'button') { - return ( -
- {input.header ?
{input.header}
: null} - - {input.icon ? : null} -
{input.label}
- -
- ); - } else if (input.type === 'checkbox') { - return ( -
- {input.header ?
{input.header}
: null} - -
{input.label}
-
-
- ); - } else if (input.type === 'text') { - return ( -
- {input.header ?
{input.header}
: null} -
- {input.icon ? : null} - {input.label} + {this.state.inputs + .filter((input) => input.section === section.id) + .map((input) => { + if (input.type === 'select') { + return ( +
+ {input.header ?
{input.header}
: null} + {this.renderPopup({ + ref: input.ref, + activate: this.activate, + deactivate: this.deactivate, + active: input.active, + inputLabel: input.label, + value: input.value, + options: input.options, + onClick: this.onChange + })}
-
- ); - } else if (input.type === 'color') { - return ( -
- {input.header ?
{input.header}
: null} - -
- ); - } - })} -
+ ); + } else if (input.type === 'link') { + return ( +
+ {input.header ?
{input.header}
: null} + {input.label} +
+ ); + } else if (input.type === 'button') { + return ( +
+ {input.header ?
{input.header}
: null} + + {input.icon ? : null} +
{input.label}
+ +
+ ); + } else if (input.type === 'checkbox') { + return ( +
+ {input.header ?
{input.header}
: null} + +
{input.label}
+
+
+ ); + } else if (input.type === 'text') { + return ( +
+ {input.header ?
{input.header}
: null} +
+ {input.icon ? : null} + {input.label} +
+
+ ); + } else if (input.type === 'color') { + return ( +
+ {input.header ?
{input.header}
: null} + +
+ ); + } + })} +
)})}
@@ -231,31 +291,7 @@ Settings.propTypes = { Settings.defaultProps = { avatar: '', email: '', - settingsConfiguration: [ - { section: 'General', label: 'LOG OUT', type: 'button' }, - { section: 'General', label: 'Change password', type: 'link', href: '' }, - { section: 'General', label: 'Import options', type: 'text' }, - { section: 'General', label: 'Import from Facebook', type: 'link', href: '' }, - { section: 'General', label: 'Export user data', type: 'link', href: '' }, - { section: 'General', label: 'Subscribe to calendar', type: 'link', href: '' }, - { section: 'General', label: 'Contact support', type: 'link', href: 'https://stremio.zendesk.com/' }, - { section: 'General', label: 'Request account deletion', type: 'link', href: 'https://docs.google.com/forms/d/e/1FAIpQLScubrlTpDMIPUUBlhZ5lwcXl3HxzKfunIMCX5Jnp-cDyglWjQ/viewform?usp=sf_link' }, - { section: 'General', header: 'Trakt Scrobbling', label: 'AUTHENTICATE', type: 'button', icon: 'ic_trackt' }, - { section: 'General', header: 'UI Language', label: 'UI Language', type: 'select', options: ['Български език', 'English', 'Deutsch', 'Español', 'Italiano'], value: 'English', onChange: (function() { alert('32423') }) }, - { section: 'Player', label: 'ADD-ONS', type: 'button', icon: 'ic_addons' }, - { section: 'Player', header: 'Default Subtitles Language', label: 'Default Subtitles Language', type: 'select', options: ['English', 'Nederlands', 'Avesta', 'Български език', 'Deutsch', 'Español', 'Italiano'], value: 'English' }, - { section: 'Player', header: 'Default Subtitles Size', label: 'Default Subtitles Size', type: 'select', options: ['72%', '80%', '100%', '120%', '140%', '160%', '180%'], value: '100%' }, - { section: 'Player', header: 'Subtitles Background', label: 'Subtitles background', type: 'select', options: ['None', 'Solid', 'Transparent'], value: 'None' }, - { section: 'Player', header: 'Subtitles color', label: 'Subtitles color', type: 'color', color: '#FFFFFF' }, - { section: 'Player', header: 'Subtitles outline color', label: 'Subtitles outline color', type: 'color', color: '#000000' }, - { section: 'Player', label: 'Auto-play next episode', type: 'checkbox', value: true }, - { section: 'Player', label: 'Pause playback when minimized', type: 'checkbox', value: false }, - { section: 'Player', label: 'Hardware-accelerated decoding', type: 'checkbox', value: true }, - { section: 'Player', label: 'Launch player in a separate window (advanced)', type: 'checkbox', value: true }, - { section: 'Streaming', header: 'Caching', label: 'Caching', type: 'select', options: ['No Caching', '2GB', '5GB', '10GB'] }, - { section: 'Streaming', header: 'Torrent Profile', label: 'Torrent Profile', type: 'select', options: ['Default', 'Soft', 'Fast'] }, - { section: 'Streaming', header: 'Streaming server URL: http://127.0.0.1:11470', label: 'Streaming server is available.', type: 'text', icon: 'ic_check' } - ] + settingsConfiguration: [] } export default Settings; diff --git a/src/routes/Settings/styles.less b/src/routes/Settings/styles.less index aebac5f9f..d8943ad5c 100644 --- a/src/routes/Settings/styles.less +++ b/src/routes/Settings/styles.less @@ -23,6 +23,7 @@ cursor: pointer; &.selected { + color: var(--color-surfacelighter); background-color: var(--color-background); &:hover { @@ -95,7 +96,7 @@ border: 1px solid var(--color-primary); cursor: pointer; - .label { + .value { font-size: 14px; color: var(--color-surface); } @@ -109,7 +110,7 @@ &:hover { background-color: var(--color-backgroundlight); - .label { + .value { color: var(--color-surfacelighter); } @@ -121,7 +122,7 @@ &:global(.active) { background-color: var(--color-surfacelighter); - .label { + .value { color: var(--color-backgrounddarker); } @@ -258,6 +259,13 @@ height: var(--spacing); fill: var(--color-signal5); } + + .x-icon { + margin-right: calc(var(--spacing) * 0.5); + width: var(--spacing); + height: var(--spacing); + fill: var(--color-signal2); + } } } @@ -275,6 +283,7 @@ .color-picker { width: 100%; height: calc(var(--spacing) * 2.5); + cursor: pointer; } } } @@ -292,7 +301,7 @@ border: 1px solid var(--color-primary); background-color: var(--color-surfacelighter); - .label { + .option { padding: calc(var(--spacing) * 0.5); width: 100%; display: flex;