mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-08-16 19:53:38 +00:00
Split settings into several files
This commit is contained in:
parent
7a1f42d4fd
commit
92a0522faa
5 changed files with 175 additions and 183 deletions
103
src/routes/Settings/SectionsList/SectionsList.js
Normal file
103
src/routes/Settings/SectionsList/SectionsList.js
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
const React = require('react');
|
||||
const { Button, Dropdown, Checkbox, ColorInput } = require('stremio/common');
|
||||
const Icon = require('stremio-icons/dom/Icon');
|
||||
const classnames = require('classnames');
|
||||
const styles = require('../styles');
|
||||
|
||||
const SectionsList = React.forwardRef(({className, sections, preferences, onPreferenceChanged, onScroll }, ref) => {
|
||||
const scrollContainerRef = ref;
|
||||
const toggleCheckbox = (id) => {
|
||||
onPreferenceChanged(id, !preferences[id]);
|
||||
};
|
||||
|
||||
const colorChanged = (id, color) => {
|
||||
onPreferenceChanged(id, color);
|
||||
};
|
||||
|
||||
const updateDropdown = (event) => {
|
||||
var data = event.currentTarget.dataset;
|
||||
onPreferenceChanged(data.name, data.value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div ref={scrollContainerRef} className={className} onScroll={onScroll}>
|
||||
{sections.map((section) =>
|
||||
<div key={section.id} ref={section.ref} className={styles['section']} data-section={section.id}>
|
||||
<div className={styles['section-header']}>{section.id}</div>
|
||||
{(section.inputs || [])
|
||||
.map((input) => {
|
||||
if (input.type === 'user') {
|
||||
return (
|
||||
<div key={input.id} className={classnames(styles['input-container'], styles['user-container'])}>
|
||||
{
|
||||
!preferences[input.id]
|
||||
?
|
||||
<div style={{ backgroundImage: `url('/images/anonymous.png')` }} className={styles['avatar']} />
|
||||
:
|
||||
<div style={{ backgroundImage: `url('${this.props.avatar}'), url('/images/default_avatar.png')` }} className={styles['avatar']} />
|
||||
}
|
||||
<div className={styles['email']}>{!preferences[input.id] ? 'Anonymous user' : preferences[input.id].email}</div>
|
||||
</div>
|
||||
);
|
||||
} else if (input.type === 'select') {
|
||||
return (
|
||||
<div key={input.id} className={classnames(styles['input-container'], styles['select-container'])}>
|
||||
{input.header ? <div className={styles['input-header']}>{input.header}</div> : null}
|
||||
<Dropdown options={input.options} selected={[preferences[input.id]]} name={input.id} key={input.id} className={styles['dropdown']} onSelect={updateDropdown} />
|
||||
</div>
|
||||
);
|
||||
} else if (input.type === 'link') {
|
||||
return (
|
||||
<div key={input.id} className={classnames(styles['input-container'], styles['link-container'])}>
|
||||
{input.header ? <div className={styles['input-header']}>{input.header}</div> : null}
|
||||
<Button ref={input.ref} className={styles['link']} type={input.type} href={input.href} target={'_blank'}>
|
||||
<div className={styles['label']}>{input.label}</div>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
} else if (input.type === 'button') {
|
||||
return (
|
||||
<div key={input.id} className={classnames(styles['input-container'], styles['button-container'])}>
|
||||
{input.header ? <div className={styles['input-header']}>{input.header}</div> : null}
|
||||
<Button ref={input.ref} className={styles['button']} type={input.type}>
|
||||
{input.icon ? <Icon className={styles['icon']} icon={input.icon} /> : null}
|
||||
<div className={styles['label']}>{input.label}</div>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
} else if (input.type === 'checkbox') {
|
||||
return (
|
||||
<div key={input.id} className={classnames(styles['input-container'], styles['checkbox-container'])}>
|
||||
{input.header ? <div className={styles['input-header']}>{input.header}</div> : null}
|
||||
<Checkbox ref={input.ref} className={styles['checkbox']} checked={preferences[input.id]} onClick={toggleCheckbox.bind(null, input.id)}>
|
||||
<div className={styles['label']}>{input.label}</div>
|
||||
</Checkbox>
|
||||
</div>
|
||||
);
|
||||
} else if (input.type === 'static-text') {
|
||||
return (
|
||||
<div key={input.id} className={classnames(styles['input-container'], styles['text-container'])}>
|
||||
{input.header ? <div className={styles['input-header']}>{input.header}</div> : null}
|
||||
<div className={styles['text']}>
|
||||
{input.icon ? <Icon className={styles[input.icon === 'ic_x' ? 'x-icon' : 'icon']} icon={input.icon} /> : null}
|
||||
<div className={styles['label']}>{input.label}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else if (input.type === 'color') {
|
||||
return (
|
||||
<div key={input.id} className={classnames(styles['input-container'], styles['color-container'])}>
|
||||
{input.header ? <div className={styles['input-header']}>{input.header}</div> : null}
|
||||
<ColorInput className={styles['color-picker']} defaultValue={preferences[input.id]} tabIndex={'-1'} onChange={colorChanged.bind(null, input.id)} />
|
||||
{/* <TextInput className={styles['color-picker']} type={input.type} defaultValue={input.color} tabIndex={'-1'} /> */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
module.exports = SectionsList;
|
||||
3
src/routes/Settings/SectionsList/index.js
Normal file
3
src/routes/Settings/SectionsList/index.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
const SectionsList = require('./SectionsList');
|
||||
|
||||
module.exports = SectionsList;
|
||||
18
src/routes/Settings/SectionsSelector/SectionsSelector.js
Normal file
18
src/routes/Settings/SectionsSelector/SectionsSelector.js
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
const React = require('react');
|
||||
const { Button } = require('stremio/common');
|
||||
const classnames = require('classnames');
|
||||
const styles = require('../styles');
|
||||
|
||||
const SectionsSelector = ({ className, sections, selectedSectionId, onSelectedSection }) => {
|
||||
return (
|
||||
<div className={className}>
|
||||
{sections.map((section) =>
|
||||
<Button key={section.id} className={classnames(styles['section-label'], { [styles['selected']]: selectedSectionId === section.id })} type={'button'} data-section={section.id} onClick={onSelectedSection}>
|
||||
{section.id}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
module.exports = SectionsSelector;
|
||||
3
src/routes/Settings/SectionsSelector/index.js
Normal file
3
src/routes/Settings/SectionsSelector/index.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
const SectionsSelector = require('./SectionsSelector');
|
||||
|
||||
module.exports = SectionsSelector;
|
||||
|
|
@ -1,128 +1,76 @@
|
|||
const React = require('react');
|
||||
const classnames = require('classnames');
|
||||
const { Button, Dropdown, Checkbox, NavBar, ColorInput } = require('stremio/common');
|
||||
const Icon = require('stremio-icons/dom/Icon');
|
||||
const { NavBar } = require('stremio/common');
|
||||
const styles = require('./styles');
|
||||
const SectionsSelector = require('./SectionsSelector');
|
||||
const SectionsList = require('./SectionsList');
|
||||
const { settingsSections, SECTIONS_ORDER } = require('./constants');
|
||||
|
||||
const settingsData = [
|
||||
{ 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' }
|
||||
];
|
||||
|
||||
const SECTIONS_ORDER = {
|
||||
'General': 1,
|
||||
'Player': 2,
|
||||
'Streaming': 3
|
||||
const settingsValues = {
|
||||
"user": null,
|
||||
"ui_language": "eng",
|
||||
"default_subtitles_language": "",
|
||||
"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,
|
||||
};
|
||||
|
||||
const Settings = () => {
|
||||
const [selectedSectionId, setSelectedSectionId] = React.useState(null);
|
||||
const [sections, setSections] = React.useState([]);
|
||||
const [inputs, setInputs] = React.useState([]);
|
||||
const sections = Object.keys(settingsSections)
|
||||
.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,
|
||||
inputs: settingsSections[section].inputs,
|
||||
ref: React.createRef()
|
||||
}));
|
||||
const [selectedSectionId, setSelectedSectionId] = React.useState(sections[0].id);
|
||||
const [preferences, setPreferences] = React.useState(settingsValues);
|
||||
const scrollContainerRef = React.useRef(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
const theSections = settingsData.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()
|
||||
}));
|
||||
setSections(theSections);
|
||||
|
||||
if (theSections.length > 0 && (selectedSectionId === null || !theSections.find(({ id }) => id === selectedSectionId))) {
|
||||
setSelectedSectionId(theSections[0].id);
|
||||
}
|
||||
|
||||
setInputs(settingsData.map((setting) => ({
|
||||
...setting,
|
||||
id: setting.label,
|
||||
ref: React.createRef(),
|
||||
active: !!(inputs.find(({ id }) => id === setting.label) || {}).active
|
||||
})));
|
||||
}, []);
|
||||
|
||||
/////////////////
|
||||
|
||||
const toggleCheckbox = (id) => {
|
||||
setInputs(inputs.map((input) => ({
|
||||
...input,
|
||||
value: id === input.id ? !input.value : input.value
|
||||
})));
|
||||
};
|
||||
|
||||
const colorChanged = (id, color) => {
|
||||
setInputs(inputs.map((input) => ({
|
||||
...input,
|
||||
value: id === input.id ? color : input.value
|
||||
})));
|
||||
};
|
||||
|
||||
const updateDropdown = (event) => {
|
||||
var data = event.currentTarget.dataset;
|
||||
setInputs(inputs.map((input) => ({
|
||||
...input,
|
||||
value: data.name === input.id ? data.value : input.value,
|
||||
})));
|
||||
};
|
||||
const updatePreference = (option, value) => {
|
||||
const newPrefs = { ...preferences };
|
||||
newPrefs[option] = value;
|
||||
setPreferences(newPrefs);
|
||||
}
|
||||
|
||||
const changeSection = (event) => {
|
||||
const currentSectionId = event.currentTarget.dataset.section;
|
||||
const section = sections.find((section) => section.id === currentSectionId);
|
||||
setSelectedSectionId(currentSectionId);
|
||||
//setSelectedSectionId(currentSectionId);
|
||||
scrollContainerRef.current.scrollTo({
|
||||
top: section.ref.current.offsetTop,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
};
|
||||
|
||||
const onScroll = (event) => {
|
||||
const current = event.currentTarget;
|
||||
if (sections.length <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (current.scrollTop + current.clientHeight === current.scrollHeight) {
|
||||
const updateSection = React.useCallback((event) => {
|
||||
const scrollContainer = event.currentTarget;
|
||||
if (scrollContainer.scrollTop + scrollContainer.clientHeight === scrollContainer.scrollHeight) {
|
||||
setSelectedSectionId(sections[sections.length - 1].id);
|
||||
} else {
|
||||
for (let i = sections.length - 1;i >= 0;i--) {
|
||||
if (sections[i].ref.current.offsetTop <= current.scrollTop) {
|
||||
if (sections[i].ref.current.offsetTop <= scrollContainer.scrollTop) {
|
||||
setSelectedSectionId(sections[i].id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={styles['settings-parent-container']}>
|
||||
|
|
@ -133,91 +81,8 @@ const Settings = () => {
|
|||
fullscreenButton={true}
|
||||
navMenu={true} />
|
||||
<div className={styles['settings-container']}>
|
||||
<div className={styles['side-menu']}>
|
||||
{sections.map((section) =>
|
||||
<Button key={section.id} className={classnames(styles['section-label'], { [styles['selected']]: selectedSectionId === section.id })} type={'button'} data-section={section.id} onClick={changeSection}>
|
||||
{section.id}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<div ref={scrollContainerRef} className={styles['scroll-container']} onScroll={onScroll}>
|
||||
{sections.map((section) =>
|
||||
<div key={section.id} ref={section.ref} className={styles['section']} data-section={section.id}>
|
||||
<div className={styles['section-header']}>{section.id}</div>
|
||||
{inputs.filter((input) => input.section === section.id)
|
||||
.map((input) => {
|
||||
if (input.type === 'user') {
|
||||
return (
|
||||
<div key={input.id} className={classnames(styles['input-container'], styles['user-container'])}>
|
||||
{
|
||||
input.email.length === 0
|
||||
?
|
||||
<div style={{ backgroundImage: `url('/images/anonymous.png')` }} className={styles['avatar']} />
|
||||
:
|
||||
<div style={{ backgroundImage: `url('${this.props.avatar}'), url('/images/default_avatar.png')` }} className={styles['avatar']} />
|
||||
}
|
||||
<div className={styles['email']}>{input.email.length === 0 ? 'Anonymous user' : input.email}</div>
|
||||
</div>
|
||||
);
|
||||
} else if (input.type === 'select') {
|
||||
return (
|
||||
<div key={input.id} className={classnames(styles['input-container'], styles['select-container'])}>
|
||||
{input.header ? <div className={styles['input-header']}>{input.header}</div> : null}
|
||||
{/* TODO: provide proper data instead of mapping the options */}
|
||||
<Dropdown options={input.options.map(o => ({ value: o, label: o }))} selected={[input.value]} name={input.id} key={input.id} className={styles['dropdown']} onSelect={updateDropdown} />
|
||||
</div>
|
||||
);
|
||||
} else if (input.type === 'link') {
|
||||
return (
|
||||
<div key={input.id} className={classnames(styles['input-container'], styles['link-container'])}>
|
||||
{input.header ? <div className={styles['input-header']}>{input.header}</div> : null}
|
||||
<Button ref={input.ref} className={styles['link']} type={input.type} href={input.href} target={'_blank'}>
|
||||
<div className={styles['label']}>{input.label}</div>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
} else if (input.type === 'button') {
|
||||
return (
|
||||
<div key={input.id} className={classnames(styles['input-container'], styles['button-container'])}>
|
||||
{input.header ? <div className={styles['input-header']}>{input.header}</div> : null}
|
||||
<Button ref={input.ref} className={styles['button']} type={input.type}>
|
||||
{input.icon ? <Icon className={styles['icon']} icon={input.icon} /> : null}
|
||||
<div className={styles['label']}>{input.label}</div>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
} else if (input.type === 'checkbox') {
|
||||
return (
|
||||
<div key={input.id} className={classnames(styles['input-container'], styles['checkbox-container'])}>
|
||||
{input.header ? <div className={styles['input-header']}>{input.header}</div> : null}
|
||||
<Checkbox ref={input.ref} className={styles['checkbox']} checked={input.value} onClick={toggleCheckbox.bind(null, input.id)}>
|
||||
<div className={styles['label']}>{input.label}</div>
|
||||
</Checkbox>
|
||||
</div>
|
||||
);
|
||||
} else if (input.type === 'static-text') {
|
||||
return (
|
||||
<div key={input.id} className={classnames(styles['input-container'], styles['text-container'])}>
|
||||
{input.header ? <div className={styles['input-header']}>{input.header}</div> : null}
|
||||
<div className={styles['text']}>
|
||||
{input.icon ? <Icon className={styles[input.icon === 'ic_x' ? 'x-icon' : 'icon']} icon={input.icon} /> : null}
|
||||
<div className={styles['label']}>{input.label}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else if (input.type === 'color') {
|
||||
return (
|
||||
<div key={input.id} className={classnames(styles['input-container'], styles['color-container'])}>
|
||||
{input.header ? <div className={styles['input-header']}>{input.header}</div> : null}
|
||||
<ColorInput className={styles['color-picker']} defaultValue={input.color} tabIndex={'-1'} onChange={colorChanged.bind(null, input.id)} />
|
||||
{/* <TextInput className={styles['color-picker']} type={input.type} defaultValue={input.color} tabIndex={'-1'} /> */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<SectionsSelector className={styles['side-menu']} sections={sections} selectedSectionId={selectedSectionId} onSelectedSection={changeSection} />
|
||||
<SectionsList ref={scrollContainerRef} className={styles['scroll-container']} sections={sections} preferences={preferences} onPreferenceChanged={updatePreference} onScroll={updateSection} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in a new issue