diff --git a/src/common/ColorInput/ColorInput.js b/src/common/ColorInput/ColorInput.js index eefcc2c18..f9a682eb9 100644 --- a/src/common/ColorInput/ColorInput.js +++ b/src/common/ColorInput/ColorInput.js @@ -1,41 +1,19 @@ const React = require('react'); const PropTypes = require('prop-types'); const AColorPicker = require('a-color-picker'); -const Icon = require('stremio-icons/dom'); -const { Modal } = require('stremio-router'); const Button = require('stremio/common/Button'); const useBinaryState = require('stremio/common/useBinaryState'); +const ModalDialog = require('stremio/common/ModalDialog'); const useDataset = require('stremio/common/useDataset'); const ColorPicker = require('./ColorPicker'); -const styles = require('./styles'); const COLOR_FORMAT = 'hexcss4'; const ColorInput = ({ className, value, onChange, ...props }) => { value = AColorPicker.parseColor(value, COLOR_FORMAT); const dataset = useDataset(props); - const [modalOpen, openModal, closeModal] = useBinaryState(false); + const [modalOpen, setModalOpen, setModalClosed] = useBinaryState(false); const [tempValue, setTempValue] = React.useState(value); - const pickerLabelOnClick = React.useCallback((event) => { - if (typeof props.onClick === 'function') { - props.onClick(event); - } - - if (!event.nativeEvent.openModalPrevented) { - openModal(); - } - }, [props.onClick]); - const modalContainerOnClick = React.useCallback((event) => { - event.nativeEvent.openModalPrevented = true; - }, []); - const modalContainerOnMouseDown = React.useCallback((event) => { - if (!event.nativeEvent.closeModalPrevented) { - closeModal(); - } - }, []); - const modalContentOnMouseDown = React.useCallback((event) => { - event.nativeEvent.closeModalPrevented = true; - }, []); const colorPickerOnInput = React.useCallback((event) => { setTempValue(event.value); }, []); @@ -49,34 +27,23 @@ const ColorInput = ({ className, value, onChange, ...props }) => { nativeEvent: event.nativeEvent }); } - - closeModal(); + setModalClosed(); }, [onChange, tempValue, dataset]); React.useEffect(() => { setTempValue(value); }, [value, modalOpen]); return ( - - - - - - + + + : null } - + ); }; diff --git a/src/common/ColorInput/styles.less b/src/common/ColorInput/styles.less deleted file mode 100644 index 8242c6e67..000000000 --- a/src/common/ColorInput/styles.less +++ /dev/null @@ -1,81 +0,0 @@ -.color-input-modal-container { - display: flex; - flex-direction: row; - align-items: center; - justify-content: center; - pointer-events: auto; - background-color: var(--color-backgrounddarker40); - - .color-input-container { - flex: none; - display: flex; - flex-direction: column; - align-items: center; - max-width: 25rem; - padding: 1rem; - background-color: var(--color-surfacelighter); - - .header-container { - flex: none; - align-self: stretch; - display: flex; - flex-direction: row; - align-items: flex-start; - - .title { - flex: 1; - margin-right: 1rem; - font-size: 1.2rem; - max-height: 2.4em; - } - - .close-button-container { - flex: none; - width: 1.5rem; - height: 1.5rem; - padding: 0.25rem; - - &:hover, &:focus { - background-color: var(--color-surfacedark20); - } - - &:focus { - outline-color: var(--color-surfacedarker); - } - - .icon { - display: block; - width: 100%; - height: 100%; - fill: var(--color-surfacedarker); - } - } - } - - .color-picker { - flex: none; - margin: 1rem; - } - - .submit-button-container { - flex: none; - align-self: stretch; - padding: 1rem; - background-color: var(--color-signal5); - - &:hover, &:focus { - filter: brightness(1.2); - } - - &:focus { - outline-color: var(--color-surfacedarker); - } - - .label { - max-height: 2.4em; - text-align: center; - color: var(--color-surfacelighter); - } - } - } -} \ No newline at end of file diff --git a/src/common/Multiselect/Multiselect.js b/src/common/Multiselect/Multiselect.js index 7a251849b..f5b81f751 100644 --- a/src/common/Multiselect/Multiselect.js +++ b/src/common/Multiselect/Multiselect.js @@ -98,12 +98,16 @@ const Multiselect = ({ className, direction, title, renderLabelContent, renderLa
{ options.length > 0 ? - options.map(({ label, value }) => ( - - )) + options.map(({ label, value }) => { + const isSelected = selected.includes(value); + const title = typeof label === 'string' ? label : value; + return ( + + ) + }) :
No options available
diff --git a/src/routes/Settings/SectionsList/SectionsList.js b/src/routes/Settings/SectionsList/SectionsList.js index c835738b2..d56de298a 100644 --- a/src/routes/Settings/SectionsList/SectionsList.js +++ b/src/routes/Settings/SectionsList/SectionsList.js @@ -1,28 +1,36 @@ const React = require('react'); const PropTypes = require('prop-types'); -const { Button, Dropdown, Checkbox, ColorInput } = require('stremio/common'); +const { Button, Multiselect, 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]); + onPreferenceChanged(id, preferences[id] === 'true' ? 'false' : 'true'); }; const colorChanged = React.useCallback((event) => { - const id = event.currentTarget.dataset.id; - const color = event.nativeEvent.value; + const id = event.dataset.id; + const color = event.value; onPreferenceChanged(id, color); }, [onPreferenceChanged]); const updateDropdown = React.useCallback((event) => { - var data = event.currentTarget.dataset; - onPreferenceChanged(data.name, data.value); + const name = event.dataset.name; + const value = event.reactEvent.currentTarget.dataset.value; + onPreferenceChanged(name, value); }, [onPreferenceChanged]); + const updateStreamingDropdown = React.useCallback((event) => { + const name = event.dataset.name; + const value = event.reactEvent.currentTarget.dataset.value; + const newPrefs = { ...preferences.streaming, [name]: value }; + onPreferenceChanged('streaming', newPrefs); + }, [onPreferenceChanged, preferences.streaming]); + const checkUser = React.useCallback((event) => { - if(! preferences.user) { + 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(); @@ -37,6 +45,45 @@ const SectionsList = React.forwardRef(({ className, sections, preferences, onPre 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 formatBytes = inBytes => { + if (inBytes === '0') return 'no caching'; + if (inBytes === 'Infinity') return '∞'; + + const bytes = parseInt(inBytes, 10); + + const kilo = 1024; + const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + + const power = Math.floor(Math.log(bytes) / Math.log(kilo)); + + // More than 1024 yotta bytes + if (power >= sizes.length) { + power = sizes.length - 1; + } + return parseFloat((bytes / Math.pow(kilo, power)).toFixed(2)) + ' ' + sizes[power]; + } + const cacheSizes = ['0', '2147483648', '5368709120', '10737418240', 'Infinity']; + const mkCacheSizeOptions = sizes => sizes.map(size => ({ + label: formatBytes(size), // TODO: translation + value: size.toString(), + })) + const supportedProfiles = ['default', 'soft', 'fast']; + const mkProfiles = profiles => profiles.map(profile => ({ + label: profile[0].toUpperCase() + profile.slice(1).toLowerCase(), // TODO: translation + value: profile, + })) + const [cachingOptions, setCachingOptions] = React.useState(mkProfiles(supportedProfiles)); + const [streamingProfiles, setStreamingProfiles] = React.useState(mkProfiles(supportedProfiles)); + React.useEffect(() => { + if (!preferences.streaming || typeof preferences.streaming.cacheSize === 'undefined') return; + setCachingOptions(mkCacheSizeOptions([...new Set(cacheSizes.concat(preferences.streaming.cacheSize))])); + }, [preferences.streaming && preferences.streaming.cacheSize]); + React.useEffect(() => { + if (preferences.streaming && preferences.streaming.profile && !supportedProfiles.includes(preferences.streaming.profile)) { + setStreamingProfiles(mkProfiles(supportedProfiles.concat(preferences.streaming.profile))); + } + }, [preferences.streaming && preferences.streaming.profile]); + const sectionsElements = sections.map((section) =>
{section.id}
@@ -104,11 +151,49 @@ const SectionsList = React.forwardRef(({ className, sections, preferences, onPre
); + } else if (input.type === 'streaming') { + return ( + preferences.streaming_loaded + ? + + { + // The streaming server settings are shown only if server is available + preferences.streaming_error + ? + null + : + +
+
Caching
+ +
+
+
Torrent Profile
+ +
+
+ } + {/* From here there is only presentation */} +
+
Streaming server URL: {preferences.server_url}
+
+
+
+ +
{'Streaming server is ' + (preferences.streaming_error ? 'not ' : '') + 'available.'}{preferences.streaming_error && ' Reason: ' + preferences.streaming_error}
+
+
+
+ : +
+
Loading streaming settgins...
+
+ ); } else if (input.type === 'select') { return (
{input.header ?
{input.header}
: null} - +
); } else if (input.type === 'link') { @@ -134,7 +219,7 @@ const SectionsList = React.forwardRef(({ className, sections, preferences, onPre return (
{input.header ?
{input.header}
: null} - +
{input.label}
@@ -153,7 +238,13 @@ const SectionsList = React.forwardRef(({ className, sections, preferences, onPre return (
{input.header ?
{input.header}
: null} - + +
+ ); + } else if (input.type === 'info') { + return ( +
+
{input.header} {preferences[input.id]}
); } diff --git a/src/routes/Settings/SectionsList/styles.less b/src/routes/Settings/SectionsList/styles.less index d7b647535..85e687d99 100644 --- a/src/routes/Settings/SectionsList/styles.less +++ b/src/routes/Settings/SectionsList/styles.less @@ -1,20 +1,32 @@ :import('~stremio/common/Checkbox/styles.less') { checkbox-icon: icon; } +:import('~stremio/common/Multiselect/styles.less') { + menu-container: menu-container; +} + +.menu-container { + max-height: 30rem; + overflow-y: auto; +} .section { - padding: 4rem 2rem; + padding: 4rem 0; + margin: 0 2rem; + width: var(--input-width); + overflow: visible; .section-header { - margin: 0 1.5rem 1.5rem 1.5rem; + margin: 1.5rem 0; font-size: 2rem; color: var(--color-surfacelighter); } .input-container { - margin: 1.5rem; + margin: 2rem 0; display: flex; flex-direction: column; + overflow: visible; .input-header { margin-bottom: 0.5rem; @@ -51,14 +63,13 @@ &.select-container { .dropdown { height: 3rem; - width: var(--input-width); } } &.link-container { - margin: 1rem 1.5rem; - + margin: 0; .link { + padding: .75rem 0; display: block; color: var(--color-secondarylight); @@ -75,7 +86,6 @@ &.button-container { .button { padding: 0.7rem; - width: var(--input-width); min-height: calc(var(--input-width) * 0.09); display: flex; align-items: center; @@ -132,10 +142,12 @@ } &.text-container { + margin: 0; .text { display: flex; flex-direction: row; align-items: center; + padding: .75rem 0; .icon { margin-right: 0.5rem; @@ -160,7 +172,7 @@ &.color-container { .color-picker { - width: var(--input-width); + box-shadow: inset 0px 0px .2rem 0px var(--color-surfacelighter20); height: calc(var(--input-width) * 0.08); cursor: pointer; diff --git a/src/routes/Settings/Settings.js b/src/routes/Settings/Settings.js index d7018c9d3..90d3a725d 100644 --- a/src/routes/Settings/Settings.js +++ b/src/routes/Settings/Settings.js @@ -6,21 +6,20 @@ const SectionsList = require('./SectionsList'); const { settingsSections } = require('./constants'); const useSettings = require('./useSettings'); -const devTestWithUser = true; - const Settings = () => { - const [preferences, setPreferences] = useSettings(devTestWithUser); - const sections = React.useMemo(()=>Object.keys(settingsSections) + const [preferences, setPreferences] = useSettings(); + const [dynamicSections, setDynamicSections] = React.useState(settingsSections); + // TODO: The Streaming section should be handled separately + const sections = React.useMemo(()=>Object.keys(dynamicSections) .map((section) => ({ id: section, - inputs: settingsSections[section], + inputs: dynamicSections[section], ref: React.createRef() - })), []); + })), [dynamicSections]); + const [selectedSectionId, setSelectedSectionId] = React.useState(sections[0].id); const scrollContainerRef = React.useRef(null); - ///////////////// - const updatePreference = (option, value) => { setPreferences({ ...preferences, [option]: value }); } diff --git a/src/routes/Settings/constants.js b/src/routes/Settings/constants.js index 563bbd8c2..9a3a95b9d 100644 --- a/src/routes/Settings/constants.js +++ b/src/routes/Settings/constants.js @@ -1,27 +1,28 @@ +const languageOptions = [ { "label": "аҧсуа бызшәа", "value": "abk" }, { "label": "Afaraf", "value": "aar" }, { "label": "Afrikaans", "value": "afr" }, { "label": "Akan", "value": "aka" }, { "label": "gjuha shqipe", "value": "sqi" }, { "label": "አማርኛ", "value": "amh" }, { "label": "العربية", "value": "ara" }, { "label": "aragonés", "value": "arg" }, { "label": "Հայերեն", "value": "hye" }, { "label": "অসমীয়া", "value": "asm" }, { "label": "авар мацӀ", "value": "ava" }, { "label": "avesta", "value": "ave" }, { "label": "aymar aru", "value": "aym" }, { "label": "azərbaycan dili", "value": "aze" }, { "label": "bamanankan", "value": "bam" }, { "label": "башҡорт теле", "value": "bak" }, { "label": "euskara", "value": "eus" }, { "label": "беларуская мова", "value": "bel" }, { "label": "বাংলা", "value": "ben" }, { "label": "भोजपुरी", "value": "bih" }, { "label": "Bislama", "value": "bis" }, { "label": "bosanski jezik", "value": "bos" }, { "label": "brezhoneg", "value": "bre" }, { "label": "български език", "value": "bul" }, { "label": "ဗမာစာ", "value": "mya" }, { "label": "català", "value": "cat" }, { "label": "Chamoru", "value": "cha" }, { "label": "нохчийн мотт", "value": "che" }, { "label": "chiCheŵa", "value": "nya" }, { "label": "中文 (Zhōngwén)", "value": "zho" }, { "label": "чӑваш чӗлхи", "value": "chv" }, { "label": "Kernewek", "value": "cor" }, { "label": "corsu", "value": "cos" }, { "label": "ᓀᐦᐃᔭᐍᐏᐣ", "value": "cre" }, { "label": "hrvatski jezik", "value": "hrv" }, { "label": "čeština", "value": "ces" }, { "label": "dansk", "value": "dan" }, { "label": "ދިވެހި", "value": "div" }, { "label": "Nederlands", "value": "nld" }, { "label": "རྫོང་ཁ", "value": "dzo" }, { "label": "English", "value": "eng" }, { "label": "Esperanto", "value": "epo" }, { "label": "eesti", "value": "est" }, { "label": "Eʋegbe", "value": "ewe" }, { "label": "føroyskt", "value": "fao" }, { "label": "vosa Vakaviti", "value": "fij" }, { "label": "suomi", "value": "fin" }, { "label": "français", "value": "fre" }, { "label": "Fulfulde", "value": "ful" }, { "label": "galego", "value": "glg" }, { "label": "ქართული", "value": "kat" }, { "label": "Deutsch", "value": "ger" }, { "label": "ελληνικά", "value": "ell" }, { "label": "Avañe'ẽ", "value": "grn" }, { "label": "ગુજરાતી", "value": "guj" }, { "label": "Kreyòl ayisyen", "value": "hat" }, { "label": "Hausa", "value": "hau" }, { "label": "עברית", "value": "heb" }, { "label": "Otjiherero", "value": "her" }, { "label": "हिन्दी", "value": "hin" }, { "label": "Hiri Motu", "value": "hmo" }, { "label": "magyar", "value": "hun" }, { "label": "Interlingua", "value": "ina" }, { "label": "Bahasa Indonesia", "value": "ind" }, { "label": "Interlingue", "value": "ile" }, { "label": "Gaeilge", "value": "gle" }, { "label": "Asụsụ Igbo", "value": "ibo" }, { "label": "Iñupiaq", "value": "ipk" }, { "label": "Ido", "value": "ido" }, { "label": "Íslenska", "value": "isl" }, { "label": "italiano", "value": "ita" }, { "label": "ᐃᓄᒃᑎᑐᑦ", "value": "iku" }, { "label": "日本語 (にほんご)", "value": "jpn" }, { "label": "basa Jawa", "value": "jav" }, { "label": "kalaallisut", "value": "kal" }, { "label": "ಕನ್ನಡ", "value": "kan" }, { "label": "Kanuri", "value": "kau" }, { "label": "कश्मीरी", "value": "kas" }, { "label": "қазақ тілі", "value": "kaz" }, { "label": "ខ្មែរ", "value": "khm" }, { "label": "Gĩkũyũ", "value": "kik" }, { "label": "Ikinyarwanda", "value": "kin" }, { "label": "Кыргызча", "value": "kir" }, { "label": "коми кыв", "value": "kom" }, { "label": "KiKongo", "value": "kon" }, { "label": "한국어 (韓國語)", "value": "kor" }, { "label": "Kurdî", "value": "kur" }, { "label": "Kuanyama", "value": "kua" }, { "label": "latine", "value": "lat" }, { "label": "Lëtzebuergesch", "value": "ltz" }, { "label": "Luganda", "value": "lug" }, { "label": "Limburgs", "value": "lim" }, { "label": "Lingála", "value": "lin" }, { "label": "ພາສາລາວ", "value": "lao" }, { "label": "lietuvių kalba", "value": "lit" }, { "label": "Tshiluba", "value": "lub" }, { "label": "latviešu valoda", "value": "lav" }, { "label": "Gaelg", "value": "glv" }, { "label": "македонски јазик", "value": "mkd" }, { "label": "fiteny malagasy", "value": "mlg" }, { "label": "bahasa Melayu", "value": "msa" }, { "label": "മലയാളം", "value": "mal" }, { "label": "Malti", "value": "mlt" }, { "label": "te reo Māori", "value": "mri" }, { "label": "मराठी", "value": "mar" }, { "label": "Kajin M̧ajeļ", "value": "mah" }, { "label": "монгол", "value": "mon" }, { "label": "Ekakairũ Naoero", "value": "nau" }, { "label": "Diné bizaad", "value": "nav" }, { "label": "Norsk bokmål", "value": "nob" }, { "label": "isiNdebele", "value": "nde" }, { "label": "नेपाली", "value": "nep" }, { "label": "Owambo", "value": "ndo" }, { "label": "Norsk nynorsk", "value": "nno" }, { "label": "Norsk", "value": "nor" }, { "label": "ꆈꌠ꒿ Nuosuhxop", "value": "iii" }, { "label": "isiNdebele", "value": "nbl" }, { "label": "occitan", "value": "oci" }, { "label": "ᐊᓂᔑᓈᐯᒧᐎᓐ", "value": "oji" }, { "label": "ѩзыкъ словѣньскъ", "value": "chu" }, { "label": "Afaan Oromoo", "value": "orm" }, { "label": "ଓଡ଼ିଆ", "value": "ori" }, { "label": "ирон æвзаг", "value": "oss" }, { "label": "ਪੰਜਾਬੀ", "value": "pan" }, { "label": "पाऴि", "value": "pli" }, { "label": "فارسی", "value": "fas" }, { "label": "język polski", "value": "pol" }, { "label": "پښتو", "value": "pus" }, { "label": "português", "value": "por" }, { "label": "português Brazil", "value": "pob" }, { "label": "Runa Simi", "value": "que" }, { "label": "rumantsch grischun", "value": "roh" }, { "label": "Ikirundi", "value": "run" }, { "label": "limba română", "value": "ron" }, { "label": "русский язык", "value": "rus" }, { "label": "संस्कृतम्", "value": "san" }, { "label": "sardu", "value": "srd" }, { "label": "सिन्धी", "value": "snd" }, { "label": "Davvisámegiella", "value": "sme" }, { "label": "gagana fa'a Samoa", "value": "smo" }, { "label": "yângâ tî sängö", "value": "sag" }, { "label": "српски језик", "value": "srp" }, { "label": "Gàidhlig", "value": "gla" }, { "label": "chiShona", "value": "sna" }, { "label": "සිංහල", "value": "sin" }, { "label": "slovenčina", "value": "slk" }, { "label": "slovenski jezik", "value": "slv" }, { "label": "Soomaaliga", "value": "som" }, { "label": "Sesotho", "value": "sot" }, { "label": "español", "value": "spa" }, { "label": "Basa Sunda", "value": "sun" }, { "label": "Kiswahili", "value": "swa" }, { "label": "SiSwati", "value": "ssw" }, { "label": "Svenska", "value": "swe" }, { "label": "தமிழ்", "value": "tam" }, { "label": "తెలుగు", "value": "tel" }, { "label": "тоҷикӣ", "value": "tgk" }, { "label": "ไทย", "value": "tha" }, { "label": "ትግርኛ", "value": "tir" }, { "label": "བོད་ཡིག", "value": "bod" }, { "label": "Türkmen", "value": "tuk" }, { "label": "Wikang Tagalog", "value": "tgl" }, { "label": "Setswana", "value": "tsn" }, { "label": "faka Tonga", "value": "ton" }, { "label": "Türkçe", "value": "tur" }, { "label": "Xitsonga", "value": "tso" }, { "label": "татар теле", "value": "tat" }, { "label": "Twi", "value": "twi" }, { "label": "Reo Tahiti", "value": "tah" }, { "label": "Uyƣurqə", "value": "uig" }, { "label": "українська мова", "value": "ukr" }, { "label": "اردو", "value": "urd" }, { "label": "O'zbek", "value": "uzb" }, { "label": "Tshivenḓa", "value": "ven" }, { "label": "Tiếng Việt", "value": "vie" }, { "label": "Volapük", "value": "vol" }, { "label": "walon", "value": "wln" }, { "label": "Cymraeg", "value": "cym" }, { "label": "Wollof", "value": "wol" }, { "label": "Frysk", "value": "fry" }, { "label": "isiXhosa", "value": "xho" }, { "label": "ייִדיש", "value": "yid" }, { "label": "Yorùbá", "value": "yor" }, { "label": "Saɯ cueŋƅ", "value": "zha" }, { "label": "isiZulu", "value": "zul" } ]; + 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' }, + { 'id': 'user', 'type': 'user' }, + { 'id': 'language', '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': 'show_vid_overview', 'label': 'Show videos overview', 'type': 'checkbox' }, ], '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)' }, + { 'id': 'add-ons', 'label': 'ADD-ONS', 'type': 'button', 'icon': 'ic_addons', 'href': '#/addons' }, + { 'id': 'subtitles_language', 'header': 'Default Subtitles Language', 'label': 'Default Subtitles Language', 'type': 'select', 'options': languageOptions }, + { 'id': 'subtitles_size', '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': 'subtitles_background', 'header': 'Subtitles Background', 'label': 'Subtitles background', 'type': 'select', 'options': [{ 'label': 'None', 'value': '' }, { 'label': 'Solid', 'value': 'solid' }, { 'label': 'Transparent', 'value': 'transparent' }] }, + { 'id': 'subtitles_color', 'header': 'Subtitles color', 'label': 'Subtitles color', 'type': 'color' }, + { 'id': 'subtitles_outline_color', 'header': 'Subtitles outline color', 'label': 'Subtitles outline color', 'type': 'color' }, + { 'id': 'autoplay_next_vid', 'label': 'Auto-play next episode', 'type': 'checkbox' }, + { 'id': 'pause_on_lost_focus', 'label': 'Pause playback when not in focus', 'type': 'checkbox' }, + { 'id': 'use_external_player', 'label': 'Launch player in a separate window (advanced)', 'type': 'checkbox' }, ], '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.' } + { 'id': 'streaming', 'type': 'streaming' }, ] }; + module.exports = { settingsSections, }; diff --git a/src/routes/Settings/useSettings.js b/src/routes/Settings/useSettings.js index bca76cb46..4f71eba0e 100644 --- a/src/routes/Settings/useSettings.js +++ b/src/routes/Settings/useSettings.js @@ -1,22 +1,68 @@ const React = require('react'); +const { useServices } = require('stremio/services'); -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 +const IGNORED_SETTINGS = Object.freeze(['user', 'streaming']); + +module.exports = () => { + const { core } = useServices(); + + const [settings, setSettings] = React.useState({ + user: null, + streaming: {}, + streaming_loaded: false, + streaming_error: "" + }); + + React.useEffect(() => { + const onNewState = () => { + const { ctx, streaming_server_settings } = core.getState() + try { + const newSettings = { + ...settings, + ...ctx.content.settings, + user: ctx.content.auth ? ctx.content.auth.user : null, + streaming: streaming_server_settings && streaming_server_settings.ready || {}, + streaming_loaded: streaming_server_settings && !!(streaming_server_settings.error || streaming_server_settings.ready), + streaming_error: streaming_server_settings && streaming_server_settings.error || "", + }; + setSettings(newSettings); + } catch (e) { + console.log('Cannot update settings state', e); + } + }; + const onStoreError = ({ event, args }) => { + if (event !== "SettingsStoreError") return; + // TODO: Notify with maybe a toast? + console.log(args) + } + + core.on('NewModel', onNewState); + core.on('Event', onStoreError); + + onNewState(); + + return () => { + // Destructor function + core.off('NewModel', onNewState); + core.off('Event', onStoreError); + }; + }, []); + + const setTheSettings = React.useCallback(newSettings => { + const event = { action: 'Settings', args: { args: {} } }; + // This can be done with React.useEffect and newSettings.streaming as dependency + const streamingServerSettingChanged = settings.streaming && Object.keys(newSettings.streaming) + .some(prop => settings.streaming[prop] !== newSettings.streaming[prop]); + if (streamingServerSettingChanged) { + event.args = { settings: 'StoreStreamingServer', args: newSettings.streaming }; + } else { + event.args.settings = 'Store'; + Object.keys(newSettings) + .filter(prop => !IGNORED_SETTINGS.includes(prop)) + .forEach(key => event.args.args[key] = newSettings[key].toString()); + } + core.dispatch(event); + }, [settings]) + + return [settings, setTheSettings]; +};