diff --git a/src/common/ColorInput/ColorInput.js b/src/common/ColorInput/ColorInput.js new file mode 100644 index 000000000..ee5a37a10 --- /dev/null +++ b/src/common/ColorInput/ColorInput.js @@ -0,0 +1,62 @@ +const React = require('react'); +const PropTypes = require('prop-types'); +const { Modal } = require('stremio-router'); +const Button = require('stremio/common/Button'); +const ColorPicker = require('stremio/common/ColorPicker'); +const useBinaryState = require('stremio/common/useBinaryState'); +const Icon = require('stremio-icons/dom'); +const styles = require('./styles'); + +const ColorInput = ({ className, id, value, onChange }) => { + const [colorInputVisible, showColorInput, closeColorInput] = useBinaryState(false); + const [selectedColor, setSelectedColor] = React.useState(value); + + const confirmColorInput = React.useCallback((event) => { + if(typeof onChange === "function") { + event.nativeEvent.value = selectedColor; + onChange(event); + } + closeColorInput(); + }, [selectedColor, onChange]); + + React.useEffect(() => { + setSelectedColor(value); + }, [value, colorInputVisible]); + + const modalBackgroundOnClick = React.useCallback((event) => { + if(event.target === event.currentTarget) { + closeColorInput(); + } + }, []); + + return ( + + + { + colorInputVisible + ? + +
+ +

Choose a color:

+ + +
+
+ : + null + } +
+ ); +}; + +ColorInput.propTypes = { + className: PropTypes.string, + id: PropTypes.string.isRequired, + value: PropTypes.string, + onChange: PropTypes.func +}; + +module.exports = ColorInput; diff --git a/src/common/ColorInput/index.js b/src/common/ColorInput/index.js new file mode 100644 index 000000000..ce4fa913a --- /dev/null +++ b/src/common/ColorInput/index.js @@ -0,0 +1,4 @@ +const ColorInput = require('./ColorInput'); + +module.exports = ColorInput; + diff --git a/src/common/ColorInput/styles.less b/src/common/ColorInput/styles.less new file mode 100644 index 000000000..c52eaaba4 --- /dev/null +++ b/src/common/ColorInput/styles.less @@ -0,0 +1,49 @@ +.color-input-modal { + background-color: var(--color-backgrounddarker40); + display: flex; + flex-direction: column; + + + .color-input-container { + position: relative; + padding: 1rem; + background-color: var(--color-surfacelighter); + + margin: auto; + + * { + overflow: visible; + } + + .x-icon { + position: absolute; + top: 1rem; + right: 1rem; + width: 1rem; + height: 1rem; + fill: var(--color-surfacedark); + } + + h1 { + font-size: 1.2rem; + } + + .color-input { + margin: 1rem auto 0 auto; + :global(.a-color-picker-stack):not(:global(.a-color-picker-row-top)) canvas, :global(.a-color-picker-circle) { + border: solid 1px var(--color-surfacedark); + } + :global(.a-color-picker-circle) { + box-shadow: 0 0 .2rem var(--color-surfacedark); + } + } + + .button { + text-align: center; + color: var(--color-surfacelighter); + background-color: var(--color-signal5); + padding: 1rem; + margin-top: 1rem; + } + } +} \ No newline at end of file diff --git a/src/common/index.js b/src/common/index.js index 580b6db4a..5771413e0 100644 --- a/src/common/index.js +++ b/src/common/index.js @@ -1,6 +1,7 @@ const Button = require('./Button'); const Checkbox = require('./Checkbox'); const ColorPicker = require('./ColorPicker'); +const ColorInput = require('./ColorInput'); const Dropdown = require('./Dropdown'); const MainNavBar = require('./MainNavBar'); const MetaItem = require('./MetaItem'); @@ -25,6 +26,7 @@ module.exports = { Button, Checkbox, ColorPicker, + ColorInput, Dropdown, MainNavBar, MetaItem, diff --git a/src/routes/Settings/SectionsList/SectionsList.js b/src/routes/Settings/SectionsList/SectionsList.js new file mode 100644 index 000000000..c835738b2 --- /dev/null +++ b/src/routes/Settings/SectionsList/SectionsList.js @@ -0,0 +1,196 @@ +const React = require('react'); +const PropTypes = require('prop-types'); +const { Button, Dropdown, Checkbox, ColorInput } = require('stremio/common'); +const Icon = require('stremio-icons/dom'); +const classnames = require('classnames'); +const styles = require('./styles'); + +const SectionsList = React.forwardRef(({ className, sections, preferences, onPreferenceChanged, onScroll }, ref) => { + const toggleCheckbox = (id) => { + onPreferenceChanged(id, !preferences[id]); + }; + + const colorChanged = React.useCallback((event) => { + const id = event.currentTarget.dataset.id; + const color = event.nativeEvent.value; + onPreferenceChanged(id, color); + }, [onPreferenceChanged]); + + const updateDropdown = React.useCallback((event) => { + var data = event.currentTarget.dataset; + onPreferenceChanged(data.name, data.value); + }, [onPreferenceChanged]); + + const checkUser = React.useCallback((event) => { + if(! preferences.user) { + // Here in Stremio 4 we show a toast with a message, asking the anonymous user to log in/register + console.log('No user found'); + event.preventDefault(); + } + }, []); + + // Determines whether the link should be opened in new window or in the current one. + const getTargetFor = url => ['//', 'http://', 'https://', 'file://', 'ftp://', 'mailto:', 'magnet:'] + .some(scheme => url.startsWith(scheme)) ? '_blank' : '_self' + + // TODO: If we get the user data after initialization, these should be wrapped in React.useState and set by React.useEffect + const changePasswordUrl = preferences.user && 'https://www.strem.io/reset-password/' + preferences.user.email; + const webCalUrl = preferences.user && 'webcal://www.strem.io/calendar/' + preferences.user._id + '.ics'; + + const sectionsElements = sections.map((section) => +
+
{section.id}
+ {(section.inputs || []) + .map((input) => { + if (input.type === 'user') { + return ( + +
+ { + !preferences.user + ? +
+ : +
+ } +
{!preferences.user ? 'Anonymous user' : preferences.user.email}
+
+
+ +
+
+ +
+
+
+
{'Import options'}
+
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
{'Trakt Scrobbling'}
+ +
+ + ); + } else if (input.type === 'select') { + return ( +
+ {input.header ?
{input.header}
: null} + +
+ ); + } else if (input.type === 'link') { + return ( +
+ {input.header ?
{input.header}
: null} + +
+ ); + } else if (input.type === 'button') { + return ( +
+ {input.header ?
{input.header}
: null} + +
+ ); + } else if (input.type === 'checkbox') { + return ( +
+ {input.header ?
{input.header}
: null} + +
{input.label}
+
+
+ ); + } else if (input.type === 'static-text') { + return ( +
+ {input.header ?
{input.header}
: null} +
+ {input.icon ? : null} +
{input.label}
+
+
+ ); + } else if (input.type === 'color') { + return ( +
+ {input.header ?
{input.header}
: null} + +
+ ); + } + })} +
+ ); + + return ( +
+ {sectionsElements} +
+ ); +}); + +SectionsList.propTypes = { + className: PropTypes.string, + sections: PropTypes.arrayOf(PropTypes.shape({ + id: PropTypes.string.isRequired, + ref: PropTypes.shape({ + current: PropTypes.object, + }).isRequired, + inputs: PropTypes.arrayOf(PropTypes.shape({ + type: PropTypes.string.isRequired, + id: PropTypes.string, + header: PropTypes.string, + label: PropTypes.string, + icon: PropTypes.string, + href: PropTypes.string, + options: PropTypes.arrayOf(PropTypes.shape({ + label: PropTypes.string.isRequired, + value: PropTypes.string.isRequired, + })), + })), + })), + preferences: PropTypes.object, + onPreferenceChanged: PropTypes.func.isRequired, + onScroll: PropTypes.func.isRequired, +}; + +module.exports = SectionsList; \ No newline at end of file diff --git a/src/routes/Settings/SectionsList/index.js b/src/routes/Settings/SectionsList/index.js new file mode 100644 index 000000000..ff7a9b4a0 --- /dev/null +++ b/src/routes/Settings/SectionsList/index.js @@ -0,0 +1,3 @@ +const SectionsList = require('./SectionsList'); + +module.exports = SectionsList; \ No newline at end of file diff --git a/src/routes/Settings/SectionsList/styles.less b/src/routes/Settings/SectionsList/styles.less new file mode 100644 index 000000000..d7b647535 --- /dev/null +++ b/src/routes/Settings/SectionsList/styles.less @@ -0,0 +1,181 @@ +:import('~stremio/common/Checkbox/styles.less') { + checkbox-icon: icon; +} + +.section { + padding: 4rem 2rem; + + .section-header { + margin: 0 1.5rem 1.5rem 1.5rem; + font-size: 2rem; + color: var(--color-surfacelighter); + } + + .input-container { + margin: 1.5rem; + display: flex; + flex-direction: column; + + .input-header { + margin-bottom: 0.5rem; + color: var(--color-surfacelighter); + } + + .checkbox-icon { + flex: none; + width: 1.2rem; + height: 1.2rem; + fill: var(--color-surfacelight); + } + + &.user-container { + flex-direction: row; + align-items: center; + + .avatar { + margin: 0 1rem; + width: 4.2rem; + height: 4.2rem; + border-radius: 50%; + border: var(--focusable-border-size) solid var(--color-primary); + background-size: cover; + background-position: center; + background-repeat: no-repeat; + } + + .email { + color: var(--color-surfacelighter); + } + } + + &.select-container { + .dropdown { + height: 3rem; + width: var(--input-width); + } + } + + &.link-container { + margin: 1rem 1.5rem; + + .link { + display: block; + color: var(--color-secondarylight); + + &:focus { + color: var(--color-surface); + } + + &:hover { + color: var(--color-surfacelighter); + } + } + } + + &.button-container { + .button { + padding: 0.7rem; + width: var(--input-width); + min-height: calc(var(--input-width) * 0.09); + display: flex; + align-items: center; + justify-content: center; + background-color: var(--color-primary); + cursor: pointer; + + .icon { + width: 1.4rem; + height: 100%; + margin-right: 0.5rem; + fill: var(--color-surfacelighter); + } + + .label { + color: var(--color-surfacelighter); + } + + &:focus { + border-color: var(--color-surfacelighter); + } + + &:hover { + border-color: transparent; + background-color: var(--color-primarylight); + } + } + } + + &.checkbox-container { + .checkbox { + display: flex; + flex-direction: row; + align-items: center; + cursor: pointer; + + .label { + width: 100%; + margin-left: 0.5rem; + color: var(--color-surfacelight); + } + + &:focus, + &:hover { + .checkbox-icon { + fill: var(--color-surfacelighter); + } + + .label { + color: var(--color-surfacelighter); + } + } + } + } + + &.text-container { + .text { + display: flex; + flex-direction: row; + align-items: center; + + .icon { + margin-right: 0.5rem; + width: 1rem; + height: 1rem; + fill: var(--color-signal5); + } + + .x-icon { + margin-right: 0.5rem; + width: 1rem; + height: 1rem; + fill: var(--color-signal2); + } + + .label { + width: 100%; + color: var(--color-surfacelighter); + } + } + } + + &.color-container { + .color-picker { + width: var(--input-width); + height: calc(var(--input-width) * 0.08); + cursor: pointer; + + &:focus { + border-color: var(--color-surfacelighter); + } + + &:hover { + border-color: transparent; + } + } + } + } + + >:last-child { + margin-bottom: 0; + } +} \ No newline at end of file diff --git a/src/routes/Settings/SectionsSelector/SectionsSelector.js b/src/routes/Settings/SectionsSelector/SectionsSelector.js new file mode 100644 index 000000000..a1f08dd12 --- /dev/null +++ b/src/routes/Settings/SectionsSelector/SectionsSelector.js @@ -0,0 +1,28 @@ +const React = require('react'); +const PropTypes = require('prop-types'); +const { Button } = require('stremio/common'); +const classnames = require('classnames'); +const styles = require('./styles'); + +const SectionsSelector = ({ className, sections, selectedSectionId, onSelectedSection }) => { + return ( +
+ {sections.map((section) => + + )} +
+ ); +}; + +SectionsSelector.propTypes = { + className: PropTypes.string, + sections: PropTypes.arrayOf(PropTypes.shape({ + id: PropTypes.string.isRequired, + })), + selectedSectionId: PropTypes.string.isRequired, + onSelectedSection: PropTypes.func.isRequired, +}; + +module.exports = SectionsSelector; diff --git a/src/routes/Settings/SectionsSelector/index.js b/src/routes/Settings/SectionsSelector/index.js new file mode 100644 index 000000000..d873092a7 --- /dev/null +++ b/src/routes/Settings/SectionsSelector/index.js @@ -0,0 +1,3 @@ +const SectionsSelector = require('./SectionsSelector'); + +module.exports = SectionsSelector; diff --git a/src/routes/Settings/SectionsSelector/styles.less b/src/routes/Settings/SectionsSelector/styles.less new file mode 100644 index 000000000..f4a15c2a8 --- /dev/null +++ b/src/routes/Settings/SectionsSelector/styles.less @@ -0,0 +1,26 @@ +.section-label { + padding: 1rem; + font-size: 1.1rem; + border: calc(var(--focusable-border-size) * 0.5) solid transparent; + color: var(--color-surfacelight); + cursor: pointer; + + &.selected { + color: var(--color-surfacelighter); + background-color: var(--color-background); + + &:hover { + background-color: var(--color-background); + } + } + + &:focus { + border-color: var(--color-surfacelighter); + } + + &:hover { + color: var(--color-surfacelighter); + background-color: var(--color-surface20); + border-color: transparent; + } +} \ No newline at end of file diff --git a/src/routes/Settings/Settings.js b/src/routes/Settings/Settings.js index abcfcf59e..4884722dd 100644 --- a/src/routes/Settings/Settings.js +++ b/src/routes/Settings/Settings.js @@ -1,312 +1,68 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import Icon from 'stremio-icons/dom'; -import { Input, Popup, Checkbox } from 'stremio/common'; -import classnames from 'classnames'; -import styles from './styles'; +const React = require('react'); +const { NavBar } = require('stremio/common'); +const styles = require('./styles'); +const SectionsSelector = require('./SectionsSelector'); +const SectionsList = require('./SectionsList'); +const { settingsSections } = require('./constants'); +const useSettings = require('./useSettings'); -const SECTIONS_ORDER = { - 'General': 1, - 'Player': 2, - 'Streaming': 3 -}; +const devTestWithUser = true; -class Settings extends Component { - constructor(props) { - super(props); +const Settings = () => { + const [preferences, setPreferences] = useSettings(devTestWithUser); + const sections = React.useMemo(()=>Object.keys(settingsSections) + .map((section) => ({ + id: section, + inputs: settingsSections[section], + ref: React.createRef() + })), []); + const [selectedSectionId, setSelectedSectionId] = React.useState(sections[0].id); + const scrollContainerRef = React.useRef(null); - this.scrollContainerRef = React.createRef(); + ///////////////// - this.state = { - selectedSectionId: null, - sections: [], - inputs: [] - }; + const updatePreference = (option, value) => { + setPreferences({ ...preferences, [option]: value }); } - componentDidMount() { - this.settingsOnUpdate([ - { section: 'General', label: 'Username', type: 'user', avatar: '', email: '' }, - { section: 'General', label: 'LOG OUT', type: 'button' }, - { section: 'General', label: 'Change password', type: 'link', href: '' }, - { section: 'General', label: 'Import options', type: 'static-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: 'static-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() - })); - - var sectionId = null; - if (selectedSectionId !== null && sections.find(({ id }) => id === selectedSectionId)) { - sectionId = selectedSectionId - } else if (sections.length > 0) { - sectionId = sections[0].id - } - - return { - selectedSectionId: sectionId, - sections: sections, - inputs: settings.map((setting) => ({ - ...setting, - id: setting.label, - ref: React.createRef(), - active: !!(inputs.find(({ id }) => id === setting.label) || {}).active - })) - } + const changeSection = React.useCallback((event) => { + const currentSectionId = event.currentTarget.dataset.section; + const section = sections.find((section) => section.id === currentSectionId); + //setSelectedSectionId(currentSectionId); + scrollContainerRef.current.scrollTo({ + top: section.ref.current.offsetTop, + behavior: 'smooth' }); - } + }, [sections]); - changeSection = (event) => { - this.setState({ selectedSectionId: event.currentTarget.dataset.section }, () => { - const section = this.state.sections.find((section) => section.id === this.state.selectedSectionId); - this.scrollContainerRef.current.scrollTo({ - top: section.ref.current.offsetTop - }); - }); - } - - onScroll = () => { - if (this.state.sections.length <= 0) { - return; - } - - if (this.scrollContainerRef.current.scrollTop + this.scrollContainerRef.current.clientHeight === this.scrollContainerRef.current.scrollHeight) { - this.setState({ selectedSectionId: this.state.sections[this.state.sections.length - 1].id }); + const sectionListOnScorll = React.useCallback((event) => { + const scrollContainer = event.currentTarget; + if (scrollContainer.scrollTop + scrollContainer.clientHeight === scrollContainer.scrollHeight) { + setSelectedSectionId(sections[sections.length - 1].id); } else { - for (let i = this.state.sections.length - 1; i >= 0; i--) { - if (this.state.sections[i].ref.current.offsetTop <= this.scrollContainerRef.current.scrollTop) { - this.setState({ selectedSectionId: this.state.sections[i].id }); + for (let i = sections.length - 1;i >= 0;i--) { + if (sections[i].ref.current.offsetTop <= scrollContainer.scrollTop) { + setSelectedSectionId(sections[i].id); break; } } } - } + }, [sections]); - shouldComponentUpdate(nextProps, nextState) { - return nextState.selectedSectionId !== this.state.selectedSectionId || - nextState.sections !== this.state.sections || - nextState.inputs !== this.state.inputs; - } - - componentDidUpdate(prevProps, prevState) { - this.state.inputs.forEach((input) => { - input.ref.current && !input.active && input.ref.current.close && input.ref.current.close(); - }); - } - - activate = (id) => { - this.setState(({ inputs }) => ({ - inputs: inputs.map((input) => ({ - ...input, - active: id === input.id - })) - })); - } - - deactivate = (id) => { - this.setState(({ inputs }) => ({ - inputs: inputs.map((input) => ({ - ...input, - active: id === input.id ? false : input.active - })) - })); - } - - toggleCheckbox = (id) => { - this.setState(({ inputs }) => ({ - inputs: inputs.map((input) => ({ - ...input, - value: id === input.id ? !input.value : input.value - })) - })); - } - - onChange = (event) => { - var data = event.currentTarget.dataset; - - this.setState(({ inputs }) => { - return { - inputs: inputs.map((input) => ({ - ...input, - value: data.id === input.id ? data.option : input.value, - active: false - })) - } - }) - } - - renderPopup({ ref, activate, deactivate, active, value, id, options, onClick }) { - return ( - - - -
{value}
- - -
- -
- {options.map((option) => -
{option}
- )} -
-
-
- ); - } - - render() { - return ( + return ( +
+
-
- {this.state.sections.map((section) => - - {section.id} - - )} -
-
- {this.state.sections.map((section) => -
-
{section.id}
- {this.state.inputs - .filter((input) => input.section === section.id) - .map((input) => { - if (input.type === 'user') { - return ( -
- { - input.email.length === 0 - ? -
- : -
- } -
{input.email.length === 0 ? 'Anonymous user' : input.email}
-
- ); - } else if (input.type === 'select') { - return ( -
- {input.header ?
{input.header}
: null} - {this.renderPopup({ - ref: input.ref, - activate: this.activate, - deactivate: this.deactivate, - active: input.active, - id: input.id, - value: input.value, - options: input.options, - onClick: this.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 === 'static-text') { - return ( -
- {input.header ?
{input.header}
: null} -
- {input.icon ? : null} -
{input.label}
-
-
- ); - } else if (input.type === 'color') { - return ( -
- {input.header ?
{input.header}
: null} - -
- ); - } - })} -
- )} -
+ +
- ); - } -} +
+ ); +}; -Settings.propTypes = { - settingsConfiguration: PropTypes.arrayOf(PropTypes.shape({ - section: PropTypes.string.isRequired, - header: PropTypes.string, - label: PropTypes.string.isRequired, - type: PropTypes.string.isRequired, - avatar: PropTypes.string, - email: PropTypes.string, - href: PropTypes.string, - icon: PropTypes.string, - options: PropTypes.arrayOf(PropTypes.string), - value: PropTypes.oneOfType([ - PropTypes.bool, - PropTypes.string - ]) - })).isRequired -} -Settings.defaultProps = { - settingsConfiguration: [] -} - -export default Settings; +module.exports = Settings; diff --git a/src/routes/Settings/constants.js b/src/routes/Settings/constants.js new file mode 100644 index 000000000..563bbd8c2 --- /dev/null +++ b/src/routes/Settings/constants.js @@ -0,0 +1,27 @@ +const settingsSections = { + 'General': [ + { 'type': 'user' }, + { 'header': 'UI Language', 'label': 'UI Language', 'type': 'select', 'options': [{ 'label': 'Български език', 'value': 'bul' }, { 'label': 'English', 'value': 'eng' }, { 'label': 'Deutsch', 'value': 'ger' }, { 'label': 'Español', 'value': 'esp' }, { 'label': 'Italiano', 'value': 'ita' }], 'id': 'ui_language' }, + ], + 'Player': [ + { 'label': 'ADD-ONS', 'type': 'button', 'icon': 'ic_addons', 'id': 'add-ons', 'href': '#/addons' }, + { 'header': 'Default Subtitles Language', 'label': 'Default Subtitles Language', 'type': 'select', 'options': [{ 'label': 'Български език', 'value': 'bul' }, { 'label': 'English', 'value': 'eng' }, { 'label': 'Deutsch', 'value': 'ger' }, { 'label': 'Español', 'value': 'esp' }, { 'label': 'Italiano', 'value': 'ita' }], 'id': 'default_subtitles_language' }, + { 'header': 'Default Subtitles Size', 'label': 'Default Subtitles Size', 'type': 'select', 'options': [{ 'label': '72%', 'value': '72%' }, { 'label': '80%', 'value': '80%' }, { 'label': '100%', 'value': '100%' }, { 'label': '120%', 'value': '120%' }, { 'label': '140%', 'value': '140%' }, { 'label': '160%', 'value': '160%' }, { 'label': '180%', 'value': '180%' }], 'id': 'default_subtitles_size' }, + { 'header': 'Subtitles Background', 'label': 'Subtitles background', 'type': 'select', 'options': [{ 'label': 'None', 'value': '' }, { 'label': 'Solid', 'value': 'solid' }, { 'label': 'Transparent', 'value': 'transparent' }], 'id': 'subtitles_background' }, + { 'header': 'Subtitles color', 'label': 'Subtitles color', 'type': 'color', 'id': 'subtitles_color' }, + { 'header': 'Subtitles outline color', 'label': 'Subtitles outline color', 'type': 'color', 'id': 'subtitles_outline_color' }, + { 'label': 'Auto-play next episode', 'type': 'checkbox', 'id': 'auto-play_next_episode' }, + { 'label': 'Pause playback when minimized', 'type': 'checkbox', 'id': 'pause_playback_when_minimized' }, + { 'label': 'Hardware-accelerated decoding', 'type': 'checkbox', 'id': 'hardware-accelerated_decoding' }, + { 'label': 'Launch player in a separate window (advanced)', 'type': 'checkbox', 'id': 'launch_player_in_a_separate_window_(advanced)' }, + ], + 'Streaming': [ + { 'header': 'Caching', 'label': 'Caching', 'type': 'select', 'options': [{ 'label': 'No Caching', 'value': '' }, { 'label': '2GB', 'value': '2048' }, { 'label': '5GB', 'value': '5120' }, { 'label': '10GB', 'value': '10240' }], 'id': 'caching' }, + { 'header': 'Torrent Profile', 'label': 'Torrent Profile', 'type': 'select', 'options': [{ 'label': 'Default', 'value': 'profile-default' }, { 'label': 'Soft', 'value': 'profile-soft' }, { 'label': 'Fast', 'value': 'profile-fast' }], 'id': 'torrent_profile' }, + { 'header': 'Streaming server URL: http://127.0.0.1:11470', 'label': 'Streaming server is available.', 'type': 'static-text', 'icon': 'ic_check', 'id': 'streaming_server_is_available.' } + ] +}; + +module.exports = { + settingsSections, +}; diff --git a/src/routes/Settings/index.js b/src/routes/Settings/index.js index 6a02e4e24..0e6819989 100644 --- a/src/routes/Settings/index.js +++ b/src/routes/Settings/index.js @@ -1,3 +1,3 @@ -import Settings from './Settings'; +const Settings = require('./Settings'); -export default Settings; +module.exports = Settings; diff --git a/src/routes/Settings/styles.less b/src/routes/Settings/styles.less index 9b7f6e72b..df5281628 100644 --- a/src/routes/Settings/styles.less +++ b/src/routes/Settings/styles.less @@ -1,307 +1,41 @@ -.settings-container, :global(.popup-container) { - --spacing: 16px; - --input-width: 500px; - font-size: 14px; -} - -.settings-container { +.settings-parent-container { + display: flex; + flex-direction: column; width: 100%; height: 100%; - display: flex; - flex-direction: row; + background-color: var(--color-background); - .side-menu { - padding: var(--spacing); - width: 17em; - display: flex; - flex-direction: column; - background-color: var(--color-backgroundlighter); - - .section-label { - padding: var(--spacing); - font-size: 1.1em; - border: calc(var(--focusable-border-size) * 0.5) solid transparent; - color: var(--color-surfacelight); - cursor: pointer; - - &.selected { - color: var(--color-surfacelighter); - background-color: var(--color-background); - - &:hover { - background-color: var(--color-background); - } - } - - &:focus { - border-color: var(--color-surfacelighter); - } - - &:hover { - color: var(--color-surfacelighter); - background-color: var(--color-surface20); - border-color: transparent; - } - } + .nav-bar { + flex: none; + align-self: stretch; } - .scroll-container { - padding: 0 calc(var(--spacing) * 2); - flex: 1; - overflow-y: auto; + .settings-container { + width: 100%; + height: 100%; + display: flex; + flex-direction: row; + background-color: var(--color-backgroundlight); + --input-width: 35rem; + + .side-menu { + padding: 1rem; + width: 17rem; + display: flex; + flex-direction: column; + background-color: var(--color-backgroundlighter); - .section { - padding: calc(var(--spacing) * 4) calc(var(--spacing) * 2); - .section-header { - margin: 0 calc(var(--spacing) * 1.5) calc(var(--spacing) * 1.5) calc(var(--spacing) * 1.5); - font-size: 2em; - color: var(--color-surfacelighter); - } - - .input-container { - margin: calc(var(--spacing) * 1.5); - display: flex; - flex-direction: column; - - .input-header { - margin-bottom: calc(var(--spacing) * 0.5); - color: var(--color-surfacelighter); - } - - &.user-container { - flex-direction: row; - align-items: center; - - .avatar { - margin: 0 var(--spacing); - width: 4.2em; - height: 4.2em; - border-radius: 50%; - border: var(--focusable-border-size) solid var(--color-primary); - background-size: cover; - background-position: center; - background-repeat: no-repeat; - } - - .email { - color: var(--color-surfacelighter); - } - } - - &.select-container { - .bar-button { - padding: calc(var(--spacing) * 0.5); - width: var(--input-width); - display: flex; - flex-direction: row; - align-items: center; - justify-content: space-between; - border: calc(var(--focusable-border-size) * 0.5) solid var(--color-primary); - cursor: pointer; - - .value { - width: 100%; - color: var(--color-surface); - } - - .icon { - width: var(--spacing); - height: var(--spacing); - fill: var(--color-surface); - } - - &:focus { - border-color: var(--color-surfacelighter); - } - - &:hover { - background-color: var(--color-backgroundlight); - - .value { - color: var(--color-surfacelighter); - } - - .icon { - fill: var(--color-surfacelighter); - } - } - - &:global(.active) { - background-color: var(--color-surfacelighter); - - .value { - color: var(--color-backgrounddarker); - } - - .icon { - fill: var(--color-backgrounddarker); - } - } - } - } - - &.link-container { - margin: var(--spacing) calc(var(--spacing) * 1.5); - - .link { - display: block; - color: var(--color-secondarylight); - - &:focus { - color: var(--color-surface); - } - - &:hover { - color: var(--color-surfacelighter); - } - } - } - - &.button-container { - .button { - padding: calc(var(--spacing) * 0.7); - width: var(--input-width); - min-height: calc(var(--input-width) * 0.09); - display: flex; - align-items: center; - justify-content: center; - background-color: var(--color-primary); - border: calc(var(--focusable-border-size) * 0.5) solid transparent; - cursor: pointer; - - .icon { - width: 1.4em; - height: 100%; - margin-right: calc(var(--spacing) * 0.5); - fill: var(--color-surfacelighter); - } - - .label { - max-width: 30em; - color: var(--color-surfacelighter); - } - - &:focus { - border-color: var(--color-surfacelighter); - } - - &:hover { - border-color: transparent; - background-color: var(--color-primarylight); - } - } - } - - &.checkbox-container { - .checkbox { - --icon-size: 1.2em; - --icon-color: var(--color-surface); - --icon-background-color: transparent; - - display: flex; - flex-direction: row; - align-items: center; - cursor: pointer; - - .label { - width: 100%; - margin-left: 0.5em; - color: var(--color-surfacelight); - } - - &:global(.checked) { - --icon-color: var(--color-surfacelight); - --icon-background-color: var(--color-primary); - } - - &:focus, &:hover { - --icon-color: var(--color-surfacelighter); - - .label { - color: var(--color-surfacelighter); - } - } - } - } - - &.text-container { - .text { - display: flex; - flex-direction: row; - align-items: center; - - .icon { - margin-right: calc(var(--spacing) * 0.5); - width: var(--spacing); - 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); - } - - .label { - width: 100%; - color: var(--color-surfacelighter); - } - } - } - - &.color-container { - .color-picker { - width: var(--input-width); - height: calc(var(--input-width) * 0.08); - border: calc(var(--focusable-border-size) * 0.5) solid transparent; - cursor: pointer; - - &:focus { - border-color: var(--color-surfacelighter); - } - - &:hover { - border-color: transparent; - } - } - } - } - - >:last-child { - margin-bottom: 0; - } } - >:not(:last-child) { - border-bottom: calc(var(--focusable-border-size) * 0.5) solid var(--color-primary); + .scroll-container { + padding: 0 2rem; + flex: 1; + overflow-y: auto; + + >:not(:last-child) { + border-bottom: calc(var(--focusable-border-size) * 0.5) solid var(--color-primary); + } } } } - -:global(.popup-container) { - .popup-content { - width: var(--input-width); - border: calc(var(--focusable-border-size) * 0.5) solid var(--color-primary); - background-color: var(--color-surfacelighter); - - .option { - padding: calc(var(--spacing) * 0.5); - width: 100%; - color: var(--color-backgrounddarker); - cursor: pointer; - - &.selected { - color: var(--color-surfacelighter); - background-color: var(--color-primarydark); - } - - &:hover { - color: var(--color-surfacelighter); - background-color: var(--color-primary); - } - } - } -} \ No newline at end of file diff --git a/src/routes/Settings/useSettings.js b/src/routes/Settings/useSettings.js new file mode 100644 index 000000000..bca76cb46 --- /dev/null +++ b/src/routes/Settings/useSettings.js @@ -0,0 +1,22 @@ +const React = require('react'); + +module.exports = (devTestWithUser) => React.useState({ + "user": devTestWithUser ? { + "_id": "neo", + "email": "neo@example.com", + "avatar": "https://www.thenational.ae/image/policy:1.891803:1566372420/AC17-Matrix-20-04.jpg?f=16x9&w=1200&$p$f$w=5867e40", + } : null, + "ui_language": "eng", + "default_subtitles_language": "bul", + "default_subtitles_size": "100%", + "subtitles_background": "", + "subtitles_color": "#ffffff", + "subtitles_outline_color": "#000", + "auto-play_next_episode": true, + "pause_playback_when_minimized": false, + "hardware-accelerated_decoding": true, + "launch_player_in_a_separate_window_(advanced)": true, + "caching": "2048", + "torrent_profile": "profile-default", + "streaming_server_is_available.": true, +}); \ No newline at end of file