mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-03-24 04:47:46 +00:00
commit
d7ec4e5643
15 changed files with 685 additions and 592 deletions
62
src/common/ColorInput/ColorInput.js
Normal file
62
src/common/ColorInput/ColorInput.js
Normal file
|
|
@ -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 (
|
||||
<React.Fragment>
|
||||
<Button className={className} title={selectedColor} onClick={showColorInput} style={{ backgroundColor: value }}></Button>
|
||||
{
|
||||
colorInputVisible
|
||||
?
|
||||
<Modal className={styles['color-input-modal']} onMouseDown={modalBackgroundOnClick}>
|
||||
<div className={styles['color-input-container']}>
|
||||
<Button onClick={closeColorInput}>
|
||||
<Icon className={styles['x-icon']} icon={'ic_x'} />
|
||||
</Button>
|
||||
<h1>Choose a color:</h1>
|
||||
<ColorPicker className={styles['color-input']} value={selectedColor} onChange={setSelectedColor} />
|
||||
<Button className={styles['button']} data-id={id} onClick={confirmColorInput}>Select</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
:
|
||||
null
|
||||
}
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
ColorInput.propTypes = {
|
||||
className: PropTypes.string,
|
||||
id: PropTypes.string.isRequired,
|
||||
value: PropTypes.string,
|
||||
onChange: PropTypes.func
|
||||
};
|
||||
|
||||
module.exports = ColorInput;
|
||||
4
src/common/ColorInput/index.js
Normal file
4
src/common/ColorInput/index.js
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
const ColorInput = require('./ColorInput');
|
||||
|
||||
module.exports = ColorInput;
|
||||
|
||||
49
src/common/ColorInput/styles.less
Normal file
49
src/common/ColorInput/styles.less
Normal file
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
196
src/routes/Settings/SectionsList/SectionsList.js
Normal file
196
src/routes/Settings/SectionsList/SectionsList.js
Normal file
|
|
@ -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) =>
|
||||
<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 (
|
||||
<React.Fragment key={'user'}>
|
||||
<div className={classnames(styles['input-container'], styles['user-container'])}>
|
||||
{
|
||||
!preferences.user
|
||||
?
|
||||
<div style={{ backgroundImage: `url('/images/anonymous.png')` }} className={styles['avatar']} />
|
||||
:
|
||||
<div style={{ backgroundImage: `url('${preferences.user.avatar}'), url('/images/default_avatar.png')` }} className={styles['avatar']} />
|
||||
}
|
||||
<div className={styles['email']}>{!preferences.user ? 'Anonymous user' : preferences.user.email}</div>
|
||||
</div>
|
||||
<div className={classnames(styles['input-container'], styles['button-container'])}>
|
||||
<Button className={styles['button']} href={'#/intro'}>
|
||||
<div className={styles['label']}>{preferences.user ? 'LOG OUT' : 'SIGN IN'}</div>
|
||||
</Button>
|
||||
</div>
|
||||
<div className={classnames(styles['input-container'], styles['link-container'])}>
|
||||
<Button className={styles['link']} href={changePasswordUrl} target={'_blank'} onClick={checkUser}>
|
||||
<div className={styles['label']}>{'Change password'}</div>
|
||||
</Button>
|
||||
</div>
|
||||
<div className={classnames(styles['input-container'], styles['text-container'])}>
|
||||
<div className={styles['text']}>
|
||||
<div className={styles['label']}>{'Import options'}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={classnames(styles['input-container'], styles['link-container'])}>
|
||||
<Button className={styles['link']} href={'https://www.stremio.com/#TODO:install-facebook-addon'} target={'_blank'} onClick={checkUser}>
|
||||
<div className={styles['label']}>{'Import from Facebook'}</div>
|
||||
</Button>
|
||||
</div>
|
||||
<div className={classnames(styles['input-container'], styles['link-container'])}>
|
||||
<Button className={styles['link']} href={'https://www.stremio.com/#TODO:export-user-data'} target={'_blank'} onClick={checkUser}>
|
||||
<div className={styles['label']}>{'Export user data'}</div>
|
||||
</Button>
|
||||
</div>
|
||||
<div className={classnames(styles['input-container'], styles['link-container'])}>
|
||||
<Button className={styles['link']} href={webCalUrl} target={'_blank'} onClick={checkUser}>
|
||||
<div className={styles['label']}>{'Subscribe to calendar'}</div>
|
||||
</Button>
|
||||
</div>
|
||||
<div className={classnames(styles['input-container'], styles['link-container'])}>
|
||||
<Button className={styles['link']} href={'https://stremio.zendesk.com/'} target={'_blank'}>
|
||||
<div className={styles['label']}>{'Contact support'}</div>
|
||||
</Button>
|
||||
</div>
|
||||
<div className={classnames(styles['input-container'], styles['link-container'])}>
|
||||
<Button className={styles['link']} href={'https://docs.google.com/forms/d/e/1FAIpQLScubrlTpDMIPUUBlhZ5lwcXl3HxzKfunIMCX5Jnp-cDyglWjQ/viewform?usp=sf_link'} target={'_blank'}>
|
||||
<div className={styles['label']}>{'Request account deletion'}</div>
|
||||
</Button>
|
||||
</div>
|
||||
<div className={classnames(styles['input-container'], styles['button-container'])}>
|
||||
<div className={styles['input-header']}>{'Trakt Scrobbling'}</div>
|
||||
<Button className={styles['button']}>
|
||||
<Icon className={styles['icon']} icon={'ic_trackt'} />
|
||||
<div className={styles['label']}>{preferences.user && preferences.user.trakt ? 'ALREADY UTHENTIATED' : 'AUTHENTIATE'}</div>
|
||||
</Button>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
} 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']} href={input.href} target={getTargetFor(input.href)}>
|
||||
<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']} href={input.href}>
|
||||
{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']} id={input.id} value={preferences[input.id]} onChange={colorChanged} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div ref={ref} className={className} onScroll={onScroll}>
|
||||
{sectionsElements}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
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;
|
||||
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;
|
||||
181
src/routes/Settings/SectionsList/styles.less
Normal file
181
src/routes/Settings/SectionsList/styles.less
Normal file
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
28
src/routes/Settings/SectionsSelector/SectionsSelector.js
Normal file
28
src/routes/Settings/SectionsSelector/SectionsSelector.js
Normal file
|
|
@ -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 (
|
||||
<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>
|
||||
);
|
||||
};
|
||||
|
||||
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;
|
||||
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;
|
||||
26
src/routes/Settings/SectionsSelector/styles.less
Normal file
26
src/routes/Settings/SectionsSelector/styles.less
Normal file
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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 (
|
||||
<Popup ref={ref} className={'popup-container'} onOpen={activate.bind(null, id)} onClose={deactivate.bind(null, id)}>
|
||||
<Popup.Label>
|
||||
<Input className={classnames(styles['bar-button'], { 'active': active })} type={'button'}>
|
||||
<div className={styles['value']}>{value}</div>
|
||||
<Icon className={styles['icon']} icon={'ic_arrow_down'} />
|
||||
</Input>
|
||||
</Popup.Label>
|
||||
<Popup.Menu>
|
||||
<div className={styles['popup-content']}>
|
||||
{options.map((option) =>
|
||||
<div key={option} className={classnames(styles['option'], { [styles['selected']]: value === option })} data-option={option} data-id={id} onClick={onClick}>{option}</div>
|
||||
)}
|
||||
</div>
|
||||
</Popup.Menu>
|
||||
</Popup>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
return (
|
||||
<div className={styles['settings-parent-container']}>
|
||||
<NavBar
|
||||
className={styles['nav-bar']}
|
||||
backButton={true}
|
||||
addonsButton={true}
|
||||
fullscreenButton={true}
|
||||
navMenu={true} />
|
||||
<div className={styles['settings-container']}>
|
||||
<div className={styles['side-menu']}>
|
||||
{this.state.sections.map((section) =>
|
||||
<Input key={section.id} className={classnames(styles['section-label'], { [styles['selected']]: this.state.selectedSectionId === section.id })} type={'button'} data-section={section.id} onClick={this.changeSection}>
|
||||
{section.id}
|
||||
</Input>
|
||||
)}
|
||||
</div>
|
||||
<div ref={this.scrollContainerRef} className={styles['scroll-container']} onScroll={this.onScroll}>
|
||||
{this.state.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>
|
||||
{this.state.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}
|
||||
{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
|
||||
})}
|
||||
</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}
|
||||
<Input ref={input.ref} className={styles['link']} type={input.type} href={input.href} target={'_blank'}>{input.label}</Input>
|
||||
</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}
|
||||
<Input 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>
|
||||
</Input>
|
||||
</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={this.toggleCheckbox.bind(null, input.label)}>
|
||||
<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}
|
||||
<Input 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={sectionListOnScorll} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
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;
|
||||
|
|
|
|||
27
src/routes/Settings/constants.js
Normal file
27
src/routes/Settings/constants.js
Normal file
|
|
@ -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,
|
||||
};
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
import Settings from './Settings';
|
||||
const Settings = require('./Settings');
|
||||
|
||||
export default Settings;
|
||||
module.exports = Settings;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
22
src/routes/Settings/useSettings.js
Normal file
22
src/routes/Settings/useSettings.js
Normal file
|
|
@ -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,
|
||||
});
|
||||
Loading…
Reference in a new issue