mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-03-11 21:27:05 +00:00
feat: Support Multiple Server URLs in the settings
This commit is contained in:
parent
b23204aa34
commit
d12766ecad
34 changed files with 925 additions and 191 deletions
92
src/common/Checkbox/Checkbox.less
Normal file
92
src/common/Checkbox/Checkbox.less
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
.checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.checkbox label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.checkbox-container {
|
||||
position: relative;
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
border: 2px solid var(--primary-accent-color);
|
||||
border-radius: 0.25rem;
|
||||
background-color: transparent;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: background-color 0.2s, border-color 0.2s;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
user-select: none;
|
||||
|
||||
&:focus {
|
||||
outline: var(--focus-outline-size) solid var(--primary-accent-color);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--overlay-color);
|
||||
}
|
||||
}
|
||||
|
||||
.checkbox-container input[type='checkbox'] {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.checkbox-icon {
|
||||
color: var(--primary-foreground-color);
|
||||
width: 1rem;
|
||||
}
|
||||
|
||||
.checkbox-label {
|
||||
margin-left: 0.75rem;
|
||||
color: var(--primary-foreground-color);
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.checkbox-checked .checkbox-container {
|
||||
background-color: var(--primary-accent-color);
|
||||
border-color: var(--primary-accent-color);
|
||||
}
|
||||
|
||||
.checkbox-checked .checkbox-icon {
|
||||
color: var(--secondary-foreground-color);
|
||||
}
|
||||
|
||||
.checkbox-unchecked .checkbox-container {
|
||||
background-color: transparent;
|
||||
border-color: var(--primary-accent-color);
|
||||
}
|
||||
|
||||
.checkbox-disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.checkbox-disabled .checkbox-container {
|
||||
background-color: var(--overlay-color);
|
||||
border-color: var(--overlay-color);
|
||||
}
|
||||
|
||||
.checkbox-disabled .checkbox-label {
|
||||
color: var(--primary-foreground-color);
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.checkbox-error .checkbox-container {
|
||||
border-color: var(--color-reddit);
|
||||
}
|
||||
|
||||
.checkbox-error .checkbox-label {
|
||||
color: var(--color-reddit);
|
||||
}
|
||||
86
src/common/Checkbox/Checkbox.tsx
Normal file
86
src/common/Checkbox/Checkbox.tsx
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
import React, { useState, useEffect, DetailedHTMLProps, HTMLAttributes } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import styles from './Checkbox.less';
|
||||
import Icon from '@stremio/stremio-icons/react';
|
||||
|
||||
type Props = {
|
||||
disabled?: boolean;
|
||||
value?: boolean;
|
||||
className?: string;
|
||||
onChange?: (checked: boolean) => void;
|
||||
ariaLabel?: string;
|
||||
error?: string;
|
||||
};
|
||||
|
||||
const Checkbox = ({ disabled, value, className, onChange, ariaLabel, error }: Props) => {
|
||||
const [isChecked, setIsChecked] = useState(false);
|
||||
const [isError, setIsError] = useState(false);
|
||||
const [isDisabled, setIsDisabled] = useState(disabled);
|
||||
|
||||
const handleChangeCheckbox = () => {
|
||||
if (disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsChecked(!isChecked);
|
||||
onChange && onChange(!isChecked);
|
||||
};
|
||||
|
||||
const handleEnterPress = (event: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>) => {
|
||||
if ((event.key === 'Enter' || event.key === ' ') && !disabled) {
|
||||
setIsChecked(!isChecked);
|
||||
onChange && onChange(!isChecked);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => setIsDisabled(disabled), [disabled]);
|
||||
|
||||
useEffect(() => setIsError(!!error), [error]);
|
||||
|
||||
useEffect(() => {
|
||||
const checked = typeof value === 'boolean' ? value : false;
|
||||
setIsChecked(checked);
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={styles['checkbox']}>
|
||||
<label>
|
||||
<div
|
||||
className={classNames({
|
||||
[styles[className || '']]: !!className,
|
||||
[styles['checkbox-checked']]: isChecked,
|
||||
[styles['checkbox-unchecked']]: !isChecked,
|
||||
[styles['checkbox-error']]: isError,
|
||||
[styles['checkbox-disabled']]: isDisabled,
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={styles['checkbox-container']}
|
||||
role={'input'}
|
||||
tabIndex={0}
|
||||
onKeyDown={handleEnterPress}
|
||||
>
|
||||
<input
|
||||
type={'checkbox'}
|
||||
onChange={handleChangeCheckbox}
|
||||
aria-label={ariaLabel}
|
||||
tabIndex={-1}
|
||||
disabled={disabled}
|
||||
/>
|
||||
{
|
||||
isChecked ?
|
||||
<Icon name={'checkmark'} className={styles['checkbox-icon']} />
|
||||
: null
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Checkbox;
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
const Checkbox = require('./Checkbox');
|
||||
|
||||
module.exports = Checkbox;
|
||||
5
src/common/Checkbox/index.ts
Normal file
5
src/common/Checkbox/index.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
import Checkbox from './Checkbox';
|
||||
|
||||
export default Checkbox;
|
||||
|
|
@ -8,13 +8,13 @@ const { useTranslation } = require('react-i18next');
|
|||
const { default: Icon } = require('@stremio/stremio-icons/react');
|
||||
const { useRouteFocused } = require('stremio-router');
|
||||
const Button = require('stremio/common/Button');
|
||||
const TextInput = require('stremio/common/TextInput');
|
||||
const useTorrent = require('stremio/common/useTorrent');
|
||||
const { withCoreSuspender } = require('stremio/common/CoreSuspender');
|
||||
const useSearchHistory = require('./useSearchHistory');
|
||||
const useLocalSearch = require('./useLocalSearch');
|
||||
const styles = require('./styles');
|
||||
const useBinaryState = require('stremio/common/useBinaryState');
|
||||
const { default: TextInput } = require('stremio/common/TextInput');
|
||||
|
||||
const SearchBar = React.memo(({ className, query, active }) => {
|
||||
const { t } = useTranslation();
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ const React = require('react');
|
|||
const PropTypes = require('prop-types');
|
||||
const classnames = require('classnames');
|
||||
const { default: Icon } = require('@stremio/stremio-icons/react');
|
||||
const TextInput = require('stremio/common/TextInput');
|
||||
const { default: TextInput } = require('../TextInput');
|
||||
const SearchBarPlaceholder = require('./SearchBarPlaceholder');
|
||||
const styles = require('./styles');
|
||||
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ const { default: Icon } = require('@stremio/stremio-icons/react');
|
|||
const { useRouteFocused } = require('stremio-router');
|
||||
const { useServices } = require('stremio/services');
|
||||
const useToast = require('stremio/common/Toast/useToast');
|
||||
const { default: TextInput } = require('../TextInput');
|
||||
const Button = require('stremio/common/Button');
|
||||
const TextInput = require('stremio/common/TextInput');
|
||||
const styles = require('./styles');
|
||||
|
||||
const SharePrompt = ({ className, url }) => {
|
||||
|
|
|
|||
|
|
@ -1,43 +0,0 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const classnames = require('classnames');
|
||||
const styles = require('./styles');
|
||||
|
||||
const TextInput = React.forwardRef((props, ref) => {
|
||||
const onKeyDown = React.useCallback((event) => {
|
||||
if (typeof props.onKeyDown === 'function') {
|
||||
props.onKeyDown(event);
|
||||
}
|
||||
|
||||
if (event.key === 'Enter' && !event.nativeEvent.submitPrevented && typeof props.onSubmit === 'function') {
|
||||
props.onSubmit(event);
|
||||
}
|
||||
}, [props.onKeyDown, props.onSubmit]);
|
||||
return (
|
||||
<input
|
||||
size={1}
|
||||
autoCorrect={'off'}
|
||||
autoCapitalize={'off'}
|
||||
autoComplete={'off'}
|
||||
spellCheck={false}
|
||||
tabIndex={0}
|
||||
{...props}
|
||||
ref={ref}
|
||||
className={classnames(props.className, styles['text-input'], { 'disabled': props.disabled })}
|
||||
onKeyDown={onKeyDown}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
TextInput.displayName = 'TextInput';
|
||||
|
||||
TextInput.propTypes = {
|
||||
className: PropTypes.string,
|
||||
disabled: PropTypes.bool,
|
||||
onKeyDown: PropTypes.func,
|
||||
onSubmit: PropTypes.func
|
||||
};
|
||||
|
||||
module.exports = TextInput;
|
||||
49
src/common/TextInput/TextInput.tsx
Normal file
49
src/common/TextInput/TextInput.tsx
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
import React from 'react';
|
||||
import classnames from 'classnames';
|
||||
import styles from './styles.less';
|
||||
|
||||
type Props = React.InputHTMLAttributes<HTMLInputElement> & {
|
||||
className?: string;
|
||||
disabled?: boolean;
|
||||
onKeyDown?: (event: React.KeyboardEvent<HTMLInputElement>) => void;
|
||||
onSubmit?: (event: React.KeyboardEvent<HTMLInputElement>) => void;
|
||||
};
|
||||
|
||||
const TextInput = React.forwardRef<HTMLInputElement, Props>((props, ref) => {
|
||||
const { onSubmit, className, disabled, ...rest } = props;
|
||||
|
||||
const onKeyDown = React.useCallback((event: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (typeof props.onKeyDown === 'function') {
|
||||
props.onKeyDown(event);
|
||||
}
|
||||
|
||||
if (
|
||||
event.key === 'Enter' &&
|
||||
!(event.nativeEvent as any).submitPrevented &&
|
||||
typeof onSubmit === 'function'
|
||||
) {
|
||||
onSubmit(event);
|
||||
}
|
||||
}, [props.onKeyDown, onSubmit]);
|
||||
|
||||
return (
|
||||
<input
|
||||
size={1}
|
||||
autoCorrect={'off'}
|
||||
autoCapitalize={'off'}
|
||||
autoComplete={'off'}
|
||||
spellCheck={false}
|
||||
tabIndex={0}
|
||||
ref={ref}
|
||||
className={classnames(className, styles['text-input'], { disabled })}
|
||||
onKeyDown={onKeyDown}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
TextInput.displayName = 'TextInput';
|
||||
|
||||
export default TextInput;
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
const TextInput = require('./TextInput');
|
||||
|
||||
module.exports = TextInput;
|
||||
5
src/common/TextInput/index.ts
Normal file
5
src/common/TextInput/index.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
import TextInput from './TextInput';
|
||||
|
||||
export default TextInput;
|
||||
|
|
@ -6,21 +6,21 @@ const classnames = require('classnames');
|
|||
const Button = require('stremio/common/Button');
|
||||
const styles = require('./styles');
|
||||
|
||||
const Checkbox = React.forwardRef(({ className, checked, children, ...props }, ref) => {
|
||||
const Toggle = React.forwardRef(({ className, checked, children, ...props }, ref) => {
|
||||
return (
|
||||
<Button {...props} ref={ref} className={classnames(className, styles['checkbox-container'], { 'checked': checked })}>
|
||||
<Button {...props} ref={ref} className={classnames(className, styles['toggle-container'], { 'checked': checked })}>
|
||||
<div className={styles['toggle']} />
|
||||
{children}
|
||||
</Button>
|
||||
);
|
||||
});
|
||||
|
||||
Checkbox.displayName = 'Checkbox';
|
||||
Toggle.displayName = 'Toggle';
|
||||
|
||||
Checkbox.propTypes = {
|
||||
Toggle.propTypes = {
|
||||
className: PropTypes.string,
|
||||
checked: PropTypes.bool,
|
||||
children: PropTypes.node
|
||||
};
|
||||
|
||||
module.exports = Checkbox;
|
||||
module.exports = Toggle;
|
||||
5
src/common/Toggle/index.js
Normal file
5
src/common/Toggle/index.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
const Toggle = require('./Toggle');
|
||||
|
||||
module.exports = Toggle;
|
||||
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
@thumb-size: calc(@height - @thumb-margin);
|
||||
|
||||
.checkbox-container {
|
||||
.toggle-container {
|
||||
position: relative;
|
||||
|
||||
.toggle {
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
const AddonDetailsModal = require('./AddonDetailsModal');
|
||||
const Button = require('./Button');
|
||||
const Checkbox = require('./Checkbox');
|
||||
const Toggle = require('./Toggle');
|
||||
const { default: Chips } = require('./Chips');
|
||||
const ColorInput = require('./ColorInput');
|
||||
const ContinueWatchingItem = require('./ContinueWatchingItem');
|
||||
|
|
@ -25,7 +25,7 @@ const SearchBar = require('./SearchBar');
|
|||
const StreamingServerWarning = require('./StreamingServerWarning');
|
||||
const SharePrompt = require('./SharePrompt');
|
||||
const Slider = require('./Slider');
|
||||
const TextInput = require('./TextInput');
|
||||
const { default: TextInput } = require('./TextInput');
|
||||
const { ToastProvider, useToast } = require('./Toast');
|
||||
const { TooltipProvider, Tooltip } = require('./Tooltips');
|
||||
const comparatorWithPriorities = require('./comparatorWithPriorities');
|
||||
|
|
@ -47,11 +47,12 @@ const useStreamingServer = require('./useStreamingServer');
|
|||
const useTorrent = require('./useTorrent');
|
||||
const useTranslate = require('./useTranslate');
|
||||
const EventModal = require('./EventModal');
|
||||
const { default: Checkbox } = require('./Checkbox');
|
||||
|
||||
module.exports = {
|
||||
AddonDetailsModal,
|
||||
Button,
|
||||
Checkbox,
|
||||
Toggle,
|
||||
Chips,
|
||||
ColorInput,
|
||||
ContinueWatchingItem,
|
||||
|
|
@ -77,6 +78,7 @@ module.exports = {
|
|||
SharePrompt,
|
||||
Slider,
|
||||
TextInput,
|
||||
Checkbox,
|
||||
ToastProvider,
|
||||
useToast,
|
||||
TooltipProvider,
|
||||
|
|
|
|||
96
src/common/useStreamingServerUrls.js
Normal file
96
src/common/useStreamingServerUrls.js
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
import { useCallback } from 'react';
|
||||
import { useServices } from 'stremio/services';
|
||||
import { useToast } from './Toast';
|
||||
import useModelState from './useModelState';
|
||||
import useProfile from './useProfile';
|
||||
|
||||
const useStreamingServerUrls = () => {
|
||||
const { core } = useServices();
|
||||
const profile = useProfile();
|
||||
const toast = useToast();
|
||||
const ctx = useModelState({ model: 'ctx' });
|
||||
|
||||
const streamingServerUrls = ctx.streamingServerUrls.sort((a, b) => {
|
||||
const dateA = new Date(a._mtime).getTime();
|
||||
const dateB = new Date(b._mtime).getTime();
|
||||
return dateA - dateB;
|
||||
})
|
||||
|
||||
const onAdd = useCallback((url) => {
|
||||
const isValidUrl = (url) => {
|
||||
try {
|
||||
new URL(url);
|
||||
return true;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
if (isValidUrl(url)) {
|
||||
toast.show({
|
||||
type: 'success',
|
||||
title: 'New URL added',
|
||||
message: 'The new URL has been added successfully',
|
||||
timeout: 4000
|
||||
});
|
||||
|
||||
core.transport.dispatch({
|
||||
action: 'Ctx',
|
||||
args: {
|
||||
action: 'AddServerUrl',
|
||||
args: url,
|
||||
}
|
||||
});
|
||||
} else {
|
||||
toast.show({
|
||||
type: 'error',
|
||||
title: 'Invalid URL',
|
||||
message: 'Please provide a valid URL',
|
||||
timeout: 4000
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
const onDelete = useCallback((url) => {
|
||||
core.transport.dispatch({
|
||||
action: 'Ctx',
|
||||
args: {
|
||||
action: 'DeleteServerUrl',
|
||||
args: url,
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
const onSelect = useCallback((url) => {
|
||||
core.transport.dispatch({
|
||||
action: 'Ctx',
|
||||
args: {
|
||||
action: 'UpdateSettings',
|
||||
args: {
|
||||
...profile.settings,
|
||||
streamingServerUrl: url
|
||||
}
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
const onReload = useCallback(() => {
|
||||
core.transport.dispatch({
|
||||
action: 'StreamingServer',
|
||||
args: {
|
||||
action: 'Reload'
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
const actions = {
|
||||
onAdd,
|
||||
onDelete,
|
||||
onSelect,
|
||||
onReload
|
||||
}
|
||||
|
||||
return { streamingServerUrls, actions };
|
||||
};
|
||||
|
||||
export default useStreamingServerUrls;
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
const ConsentCheckbox = require('./ConsentCheckbox');
|
||||
|
||||
module.exports = ConsentCheckbox;
|
||||
|
|
@ -3,11 +3,11 @@
|
|||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const classnames = require('classnames');
|
||||
const { Button, Checkbox } = require('stremio/common');
|
||||
const { Button, Toggle } = require('stremio/common');
|
||||
const styles = require('./styles');
|
||||
|
||||
const ConsentCheckbox = React.forwardRef(({ className, label, link, href, onToggle, ...props }, ref) => {
|
||||
const checkboxOnClick = React.useCallback((event) => {
|
||||
const ConsentToggle = React.forwardRef(({ className, label, link, href, onToggle, ...props }, ref) => {
|
||||
const toggleOnClick = React.useCallback((event) => {
|
||||
if (typeof props.onClick === 'function') {
|
||||
props.onClick(event);
|
||||
}
|
||||
|
|
@ -24,7 +24,7 @@ const ConsentCheckbox = React.forwardRef(({ className, label, link, href, onTogg
|
|||
event.nativeEvent.togglePrevented = true;
|
||||
}, []);
|
||||
return (
|
||||
<Checkbox {...props} ref={ref} className={classnames(className, styles['consent-checkbox-container'])} onClick={checkboxOnClick}>
|
||||
<Toggle {...props} ref={ref} className={classnames(className, styles['consent-toogle-container'])} onClick={toggleOnClick}>
|
||||
<div className={styles['label']}>
|
||||
{label}
|
||||
{' '}
|
||||
|
|
@ -37,13 +37,13 @@ const ConsentCheckbox = React.forwardRef(({ className, label, link, href, onTogg
|
|||
null
|
||||
}
|
||||
</div>
|
||||
</Checkbox>
|
||||
</Toggle>
|
||||
);
|
||||
});
|
||||
|
||||
ConsentCheckbox.displayName = 'ConsentCheckbox';
|
||||
ConsentToggle.displayName = 'ConsentToggle';
|
||||
|
||||
ConsentCheckbox.propTypes = {
|
||||
ConsentToggle.propTypes = {
|
||||
className: PropTypes.string,
|
||||
checked: PropTypes.bool,
|
||||
label: PropTypes.string,
|
||||
|
|
@ -53,4 +53,4 @@ ConsentCheckbox.propTypes = {
|
|||
onClick: PropTypes.func
|
||||
};
|
||||
|
||||
module.exports = ConsentCheckbox;
|
||||
module.exports = ConsentToggle;
|
||||
5
src/routes/Intro/ConsentToggle/index.js
Normal file
5
src/routes/Intro/ConsentToggle/index.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
const ConsentToggle = require('./ConsentToggle');
|
||||
|
||||
module.exports = ConsentToggle;
|
||||
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
@import (reference) '~@stremio/stremio-colors/less/stremio-colors.less';
|
||||
|
||||
:import('~stremio/common/Checkbox/styles.less') {
|
||||
:import('~stremio/common/Toggle/styles.less') {
|
||||
checkbox-icon: icon;
|
||||
}
|
||||
|
||||
.consent-checkbox-container {
|
||||
.consent-toogle-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const { TextInput } = require('stremio/common');
|
||||
const { default: TextInput } = require('stremio/common/TextInput');
|
||||
|
||||
const CredentialsTextInput = React.forwardRef((props, ref) => {
|
||||
const onKeyDown = React.useCallback((event) => {
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ const { Modal, useRouteFocused } = require('stremio-router');
|
|||
const { useServices } = require('stremio/services');
|
||||
const { Button, Image, useBinaryState } = require('stremio/common');
|
||||
const CredentialsTextInput = require('./CredentialsTextInput');
|
||||
const ConsentCheckbox = require('./ConsentCheckbox');
|
||||
const ConsentToggle = require('./ConsentToggle');
|
||||
const PasswordResetModal = require('./PasswordResetModal');
|
||||
const useFacebookLogin = require('./useFacebookLogin');
|
||||
const styles = require('./styles');
|
||||
|
|
@ -54,7 +54,7 @@ const Intro = ({ queryParams }) => {
|
|||
error: '',
|
||||
[action.name]: action.value
|
||||
};
|
||||
case 'toggle-checkbox':
|
||||
case 'toogle-checkbox':
|
||||
return {
|
||||
...state,
|
||||
error: '',
|
||||
|
|
@ -210,13 +210,13 @@ const Intro = ({ queryParams }) => {
|
|||
termsRef.current.focus();
|
||||
}, []);
|
||||
const toggleTermsAccepted = React.useCallback(() => {
|
||||
dispatch({ type: 'toggle-checkbox', name: 'termsAccepted' });
|
||||
dispatch({ type: 'toogle-checkbox', name: 'termsAccepted' });
|
||||
}, []);
|
||||
const togglePrivacyPolicyAccepted = React.useCallback(() => {
|
||||
dispatch({ type: 'toggle-checkbox', name: 'privacyPolicyAccepted' });
|
||||
dispatch({ type: 'toogle-checkbox', name: 'privacyPolicyAccepted' });
|
||||
}, []);
|
||||
const toggleMarketingAccepted = React.useCallback(() => {
|
||||
dispatch({ type: 'toggle-checkbox', name: 'marketingAccepted' });
|
||||
dispatch({ type: 'toogle-checkbox', name: 'marketingAccepted' });
|
||||
}, []);
|
||||
const switchFormOnClick = React.useCallback(() => {
|
||||
const queryParams = new URLSearchParams([['form', state.form === SIGNUP_FORM ? LOGIN_FORM : SIGNUP_FORM]]);
|
||||
|
|
@ -307,27 +307,27 @@ const Intro = ({ queryParams }) => {
|
|||
onChange={confirmPasswordOnChange}
|
||||
onSubmit={confirmPasswordOnSubmit}
|
||||
/>
|
||||
<ConsentCheckbox
|
||||
<ConsentToggle
|
||||
ref={termsRef}
|
||||
className={styles['consent-checkbox']}
|
||||
className={styles['consent-toggle']}
|
||||
label={'I have read and agree with the Stremio'}
|
||||
link={'Terms and conditions'}
|
||||
href={'https://www.stremio.com/tos'}
|
||||
checked={state.termsAccepted}
|
||||
onToggle={toggleTermsAccepted}
|
||||
/>
|
||||
<ConsentCheckbox
|
||||
<ConsentToggle
|
||||
ref={privacyPolicyRef}
|
||||
className={styles['consent-checkbox']}
|
||||
className={styles['consent-toggle']}
|
||||
label={'I have read and agree with the Stremio'}
|
||||
link={'Privacy Policy'}
|
||||
href={'https://www.stremio.com/privacy'}
|
||||
checked={state.privacyPolicyAccepted}
|
||||
onToggle={togglePrivacyPolicyAccepted}
|
||||
/>
|
||||
<ConsentCheckbox
|
||||
<ConsentToggle
|
||||
ref={marketingRef}
|
||||
className={styles['consent-checkbox']}
|
||||
className={styles['consent-toggle']}
|
||||
label={'I agree to receive marketing communications from Stremio'}
|
||||
checked={state.marketingAccepted}
|
||||
onToggle={toggleMarketingAccepted}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ const React = require('react');
|
|||
const PropTypes = require('prop-types');
|
||||
const classnames = require('classnames');
|
||||
const { t } = require('i18next');
|
||||
const { Image, SearchBar, Checkbox } = require('stremio/common');
|
||||
const { Image, SearchBar, Toggle } = require('stremio/common');
|
||||
const SeasonsBar = require('./SeasonsBar');
|
||||
const Video = require('./Video');
|
||||
const styles = require('./styles');
|
||||
|
|
@ -84,9 +84,9 @@ const VideosList = ({ className, metaItem, libraryItem, season, seasonOnSelect,
|
|||
<React.Fragment>
|
||||
{
|
||||
showNotificationsToggle && libraryItem ?
|
||||
<Checkbox className={styles['notifications-checkbox']} checked={!libraryItem.state.noNotif} onClick={toggleNotifications}>
|
||||
<Toggle className={styles['notifications-toggle']} checked={!libraryItem.state.noNotif} onClick={toggleNotifications}>
|
||||
{t('DETAIL_RECEIVE_NOTIF_SERIES')}
|
||||
</Checkbox>
|
||||
</Toggle>
|
||||
:
|
||||
null
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.notifications-checkbox {
|
||||
.notifications-toggle {
|
||||
flex: none;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
|
|
|||
|
|
@ -7,11 +7,12 @@ const { useTranslation } = require('react-i18next');
|
|||
const { default: Icon } = require('@stremio/stremio-icons/react');
|
||||
const { useRouteFocused } = require('stremio-router');
|
||||
const { useServices } = require('stremio/services');
|
||||
const { Button, Checkbox, MainNavBars, Multiselect, ColorInput, TextInput, ModalDialog, useProfile, usePlatform, useStreamingServer, useBinaryState, withCoreSuspender, useToast, useModelState } = require('stremio/common');
|
||||
const { Button, Toggle, MainNavBars, Multiselect, ColorInput, useProfile, usePlatform, useStreamingServer, withCoreSuspender, useToast } = require('stremio/common');
|
||||
const useProfileSettingsInputs = require('./useProfileSettingsInputs');
|
||||
const useStreamingServerSettingsInputs = require('./useStreamingServerSettingsInputs');
|
||||
const useDataExport = require('./useDataExport');
|
||||
const styles = require('./styles');
|
||||
const { default: URLsManager } = require('./URLsManager/URLsManager');
|
||||
|
||||
const GENERAL_SECTION = 'general';
|
||||
const PLAYER_SECTION = 'player';
|
||||
|
|
@ -35,16 +36,15 @@ const Settings = () => {
|
|||
subtitlesBackgroundColorInput,
|
||||
subtitlesOutlineColorInput,
|
||||
audioLanguageSelect,
|
||||
surroundSoundCheckbox,
|
||||
surroundSoundToggle,
|
||||
seekTimeDurationSelect,
|
||||
seekShortTimeDurationSelect,
|
||||
escExitFullscreenCheckbox,
|
||||
escExitFullscreenToggle,
|
||||
playInExternalPlayerSelect,
|
||||
nextVideoPopupDurationSelect,
|
||||
bingeWatchingCheckbox,
|
||||
playInBackgroundCheckbox,
|
||||
hardwareDecodingCheckbox,
|
||||
streamingServerUrlInput
|
||||
bingeWatchingToggle,
|
||||
playInBackgroundToggle,
|
||||
hardwareDecodingToggle,
|
||||
} = useProfileSettingsInputs(profile);
|
||||
const {
|
||||
streamingServerRemoteUrlInput,
|
||||
|
|
@ -53,34 +53,11 @@ const Settings = () => {
|
|||
torrentProfileSelect,
|
||||
transcodingProfileSelect,
|
||||
} = useStreamingServerSettingsInputs(streamingServer);
|
||||
const [configureServerUrlModalOpen, openConfigureServerUrlModal, closeConfigureServerUrlModal] = useBinaryState(false);
|
||||
const configureServerUrlInputRef = React.useRef(null);
|
||||
const configureServerUrlOnSubmit = React.useCallback(() => {
|
||||
streamingServerUrlInput.onChange(configureServerUrlInputRef.current.value);
|
||||
closeConfigureServerUrlModal();
|
||||
}, [streamingServerUrlInput]);
|
||||
const [traktAuthStarted, setTraktAuthStarted] = React.useState(false);
|
||||
const isTraktAuthenticated = React.useMemo(() => {
|
||||
return profile.auth !== null && profile.auth.user !== null && profile.auth.user.trakt !== null &&
|
||||
(Date.now() / 1000) < (profile.auth.user.trakt.created_at + profile.auth.user.trakt.expires_in);
|
||||
}, [profile.auth]);
|
||||
const configureServerUrlModalButtons = React.useMemo(() => {
|
||||
return [
|
||||
{
|
||||
className: styles['cancel-button'],
|
||||
label: 'Cancel',
|
||||
props: {
|
||||
onClick: closeConfigureServerUrlModal
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Submit',
|
||||
props: {
|
||||
onClick: configureServerUrlOnSubmit,
|
||||
}
|
||||
}
|
||||
];
|
||||
}, [configureServerUrlOnSubmit]);
|
||||
const logoutButtonOnClick = React.useCallback(() => {
|
||||
core.transport.dispatch({
|
||||
action: 'Ctx',
|
||||
|
|
@ -118,14 +95,6 @@ const Settings = () => {
|
|||
const exportDataOnClick = React.useCallback(() => {
|
||||
loadDataExport();
|
||||
}, []);
|
||||
const reloadStreamingServer = React.useCallback(() => {
|
||||
core.transport.dispatch({
|
||||
action: 'StreamingServer',
|
||||
args: {
|
||||
action: 'Reload'
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
const onCopyRemoteUrlClick = React.useCallback(() => {
|
||||
if (streamingServer.remoteUrl) {
|
||||
navigator.clipboard.writeText(streamingServer.remoteUrl);
|
||||
|
|
@ -192,11 +161,7 @@ const Settings = () => {
|
|||
if (routeFocused) {
|
||||
updateSelectedSectionId();
|
||||
}
|
||||
closeConfigureServerUrlModal();
|
||||
}, [routeFocused]);
|
||||
const ctx = useModelState({ model: 'ctx' });
|
||||
console.log(profile); // eslint-disable-line no-console
|
||||
console.log(ctx); // eslint-disable-line no-console
|
||||
return (
|
||||
<MainNavBars className={styles['settings-container']} route={'settings'}>
|
||||
<div className={classnames(styles['settings-content'], 'animation-fade-in')}>
|
||||
|
|
@ -372,9 +337,9 @@ const Settings = () => {
|
|||
<div className={styles['option-name-container']}>
|
||||
<div className={styles['label']}>{ t('SETTINGS_FULLSCREEN_EXIT') }</div>
|
||||
</div>
|
||||
<Checkbox
|
||||
className={classnames(styles['option-input-container'], styles['checkbox-container'])}
|
||||
{...escExitFullscreenCheckbox}
|
||||
<Toggle
|
||||
className={classnames(styles['option-input-container'], styles['toogle-container'])}
|
||||
{...escExitFullscreenToggle}
|
||||
/>
|
||||
</div>
|
||||
:
|
||||
|
|
@ -435,10 +400,10 @@ const Settings = () => {
|
|||
<div className={styles['option-name-container']}>
|
||||
<div className={styles['label']}>{ t('SETTINGS_SURROUND_SOUND') }</div>
|
||||
</div>
|
||||
<Checkbox
|
||||
className={classnames(styles['option-input-container'], styles['checkbox-container'])}
|
||||
<Toggle
|
||||
className={classnames(styles['option-input-container'], styles['toogle-container'])}
|
||||
tabIndex={-1}
|
||||
{...surroundSoundCheckbox}
|
||||
{...surroundSoundToggle}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -469,11 +434,11 @@ const Settings = () => {
|
|||
<div className={styles['option-name-container']}>
|
||||
<div className={styles['label']}>{ t('SETTINGS_PLAY_IN_BACKGROUND') }</div>
|
||||
</div>
|
||||
<Checkbox
|
||||
className={classnames(styles['option-input-container'], styles['checkbox-container'])}
|
||||
<Toggle
|
||||
className={classnames(styles['option-input-container'], styles['toogle-container'])}
|
||||
disabled={true}
|
||||
tabIndex={-1}
|
||||
{...playInBackgroundCheckbox}
|
||||
{...playInBackgroundToggle}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -486,9 +451,9 @@ const Settings = () => {
|
|||
<div className={styles['option-name-container']}>
|
||||
<div className={styles['label']}>{ t('AUTO_PLAY') }</div>
|
||||
</div>
|
||||
<Checkbox
|
||||
className={classnames(styles['option-input-container'], styles['checkbox-container'])}
|
||||
{...bingeWatchingCheckbox}
|
||||
<Toggle
|
||||
className={classnames(styles['option-input-container'], styles['toogle-container'])}
|
||||
{...bingeWatchingToggle}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles['option-container']}>
|
||||
|
|
@ -520,22 +485,18 @@ const Settings = () => {
|
|||
<div className={styles['option-name-container']}>
|
||||
<div className={styles['label']}>{ t('SETTINGS_HWDEC') }</div>
|
||||
</div>
|
||||
<Checkbox
|
||||
className={classnames(styles['option-input-container'], styles['checkbox-container'])}
|
||||
<Toggle
|
||||
className={classnames(styles['option-input-container'], styles['toogle-container'])}
|
||||
disabled={true}
|
||||
tabIndex={-1}
|
||||
{...hardwareDecodingCheckbox}
|
||||
{...hardwareDecodingToggle}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div ref={streamingServerSectionRef} className={styles['section-container']}>
|
||||
<div className={styles['section-title']}>{ t('SETTINGS_NAV_STREAMING') }</div>
|
||||
<div className={styles['option-container']}>
|
||||
<Button className={classnames(styles['option-input-container'], styles['button-container'])} title={'Reload'} onClick={reloadStreamingServer}>
|
||||
<div className={styles['label']}>{ t('RELOAD') }</div>
|
||||
</Button>
|
||||
</div>
|
||||
<div className={styles['option-container']}>
|
||||
<URLsManager />
|
||||
{/* <div className={styles['option-container']}>
|
||||
<div className={styles['option-name-container']}>
|
||||
<div className={styles['label']}>{ t('STATUS') }</div>
|
||||
</div>
|
||||
|
|
@ -555,8 +516,8 @@ const Settings = () => {
|
|||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles['option-container']}>
|
||||
</div> */}
|
||||
{/* <div className={styles['option-container']}>
|
||||
<div className={styles['option-name-container']}>
|
||||
<div className={styles['label']}>Url</div>
|
||||
</div>
|
||||
|
|
@ -566,7 +527,7 @@ const Settings = () => {
|
|||
<Icon className={styles['icon']} name={'settings'} />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div> */}
|
||||
{
|
||||
streamingServerRemoteUrlInput.value !== null ?
|
||||
<div className={styles['option-container']}>
|
||||
|
|
@ -782,7 +743,7 @@ const Settings = () => {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
{/* {
|
||||
configureServerUrlModalOpen ?
|
||||
<ModalDialog
|
||||
className={styles['configure-server-url-modal-container']}
|
||||
|
|
@ -801,7 +762,7 @@ const Settings = () => {
|
|||
</ModalDialog>
|
||||
:
|
||||
null
|
||||
}
|
||||
} */}
|
||||
</MainNavBars>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
194
src/routes/Settings/URLsManager/Item/Item.less
Normal file
194
src/routes/Settings/URLsManager/Item/Item.less
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
@import (reference) '~stremio/common/screen-sizes.less';
|
||||
|
||||
.item {
|
||||
display: flex;
|
||||
padding: 1rem 1.5rem;
|
||||
border-radius: var(--border-radius);
|
||||
transition: 0.3s all ease-in-out;
|
||||
background-color: transparent;
|
||||
border: 2px solid transparent;
|
||||
justify-content: space-between;
|
||||
position: relative;
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.checkbox {
|
||||
|
||||
}
|
||||
|
||||
.label {
|
||||
color: var(--primary-foreground-color);
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin-right: 5rem;
|
||||
|
||||
.status {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.icon {
|
||||
width: 0.75rem;
|
||||
height: 0.75rem;
|
||||
border-radius: 1rem;
|
||||
|
||||
&.ready {
|
||||
background-color: var(--secondary-accent-color);
|
||||
}
|
||||
|
||||
&.error {
|
||||
background-color: var(--color-trakt);
|
||||
}
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 1rem;
|
||||
color: var(--primary-foreground-color);
|
||||
}
|
||||
}
|
||||
|
||||
.delete {
|
||||
position: absolute;
|
||||
right: 1.5rem;
|
||||
top: 50%;
|
||||
display: none;
|
||||
gap: 0.5rem;
|
||||
padding: 0.25rem;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: transparent;
|
||||
transition: 0.3s all ease-in-out;
|
||||
border-radius: var(--border-radius);
|
||||
transform: translateY(-50%);
|
||||
width: 3rem;
|
||||
opacity: 0.6;
|
||||
|
||||
.icon {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
color: var(--primary-foreground-color);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--overlay-color);
|
||||
opacity: 1;
|
||||
|
||||
.icon {
|
||||
color: var(--color-trakt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.add {
|
||||
padding: 0.5rem 1.5rem;
|
||||
gap: 1rem;
|
||||
|
||||
.input {
|
||||
background-color: var(--overlay-color);
|
||||
border-radius: var(--border-radius);
|
||||
color: var(--primary-foreground-color);
|
||||
padding: 0.5rem 0.75rem;
|
||||
border: 1px solid transparent;
|
||||
width: 70%;
|
||||
|
||||
&:focus {
|
||||
border: 1px solid var(--primary-foreground-color);
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: 0.25rem;
|
||||
margin-right: 0;
|
||||
|
||||
.add, .cancel {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
padding: 0.25rem;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: transparent;
|
||||
transition: 0.3s all ease-in-out;
|
||||
border-radius: var(--border-radius);
|
||||
width: 3rem;
|
||||
opacity: 0.6;
|
||||
|
||||
.icon {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
color: var(--primary-foreground-color);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
background-color: var(--overlay-color);
|
||||
}
|
||||
}
|
||||
|
||||
.add {
|
||||
.icon {
|
||||
width: 1.8rem;
|
||||
height: 1.8rem;
|
||||
}
|
||||
&:hover {
|
||||
.icon {
|
||||
color: var(--secondary-accent-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cancel {
|
||||
&:hover {
|
||||
.icon {
|
||||
color: var(--color-trakt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
border: 2px solid transparent;
|
||||
background-color: var(--overlay-color);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
&:hover {
|
||||
background-color: var(--overlay-color);
|
||||
|
||||
.actions {
|
||||
.delete {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: @minimum) {
|
||||
.item {
|
||||
padding: 1rem 0.5rem;
|
||||
|
||||
.actions {
|
||||
margin-right: 4rem;
|
||||
|
||||
.delete {
|
||||
right: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
&.add {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
148
src/routes/Settings/URLsManager/Item/Item.tsx
Normal file
148
src/routes/Settings/URLsManager/Item/Item.tsx
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
import React, { useState, useCallback, ChangeEvent } from 'react';
|
||||
import { useProfile } from 'stremio/common';
|
||||
import Button from 'stremio/common/Button';
|
||||
import useStreamingServer from 'stremio/common/useStreamingServer';
|
||||
import TextInput from 'stremio/common/TextInput';
|
||||
import Icon from '@stremio/stremio-icons/react';
|
||||
import styles from './Item.less';
|
||||
import classNames from 'classnames';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Checkbox from 'stremio/common/Checkbox';
|
||||
|
||||
type ViewModeProps = {
|
||||
url: string;
|
||||
onDelete?: (url: string) => void;
|
||||
onSelect?: (url: string) => void;
|
||||
}
|
||||
|
||||
const ViewMode = ({ url, onDelete, onSelect }: ViewModeProps) => {
|
||||
const { t } = useTranslation();
|
||||
const streamingServer = useStreamingServer();
|
||||
const profile = useProfile();
|
||||
const selected = profile.settings.streamingServerUrl === url;
|
||||
|
||||
const handleDelete = () => {
|
||||
onDelete?.(url);
|
||||
};
|
||||
|
||||
const handleSelect = () => {
|
||||
onSelect?.(url);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={styles['content']}>
|
||||
<Checkbox value={selected} onChange={handleSelect} />
|
||||
<div className={styles['label']}>{url}</div>
|
||||
</div>
|
||||
<div className={styles['actions']}>
|
||||
{
|
||||
selected ?
|
||||
<div className={styles['status']}>
|
||||
<div className={classNames(styles['icon'], { [styles['ready']]: streamingServer.settings?.type === 'Ready' }, { [styles['error']]: streamingServer.settings?.type === 'Err' })} />
|
||||
<div className={styles['label']}>
|
||||
{
|
||||
streamingServer.settings === null ?
|
||||
'NotLoaded'
|
||||
:
|
||||
streamingServer.settings.type === 'Ready' ?
|
||||
t('SETTINGS_SERVER_STATUS_ONLINE')
|
||||
:
|
||||
streamingServer.settings.type === 'Err' ?
|
||||
t('SETTINGS_SERVER_STATUS_ERROR')
|
||||
:
|
||||
streamingServer.settings.type
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
: null
|
||||
}
|
||||
<Button className={styles['delete']} onClick={handleDelete}>
|
||||
<Icon name={'close'} className={styles['icon']} />
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
type AddModeProps = {
|
||||
inputValue: string;
|
||||
handleValueChange: (event: ChangeEvent<HTMLInputElement>) => void;
|
||||
onAdd?: (url: string) => void;
|
||||
onCancel?: () => void;
|
||||
}
|
||||
|
||||
const AddMode = ({ inputValue, handleValueChange, onAdd, onCancel }: AddModeProps) => {
|
||||
const handleAdd = () => {
|
||||
if (inputValue.trim()) {
|
||||
onAdd?.(inputValue);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<TextInput
|
||||
className={styles['input']}
|
||||
value={inputValue}
|
||||
onChange={handleValueChange}
|
||||
/>
|
||||
<div className={styles['actions']}>
|
||||
<Button className={styles['add']} onClick={handleAdd}>
|
||||
<Icon name={'checkmark'} className={styles['icon']} />
|
||||
</Button>
|
||||
<Button className={styles['cancel']} onClick={onCancel}>
|
||||
<Icon name={'close'} className={styles['icon']} />
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
type Props =
|
||||
| {
|
||||
mode: 'add';
|
||||
onAdd?: (url: string) => void;
|
||||
onCancel?: () => void;
|
||||
}
|
||||
| {
|
||||
mode: 'view';
|
||||
url: string;
|
||||
onDelete?: (url: string) => void;
|
||||
onSelect?: (url: string) => void;
|
||||
};
|
||||
|
||||
const Item = (props: Props) => {
|
||||
if (props.mode === 'add') {
|
||||
const { onAdd, onCancel } = props;
|
||||
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
|
||||
const handleValueChange = useCallback((event: ChangeEvent<HTMLInputElement>) => {
|
||||
setInputValue(event.target.value);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={classNames(styles['item'], styles['add'])}>
|
||||
<AddMode
|
||||
inputValue={inputValue}
|
||||
handleValueChange={handleValueChange}
|
||||
onAdd={onAdd}
|
||||
onCancel={onCancel}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
} else if (props.mode === 'view') {
|
||||
const { url, onDelete, onSelect } = props;
|
||||
|
||||
return (
|
||||
<div className={classNames(styles['item'])}>
|
||||
<ViewMode url={url} onDelete={onDelete} onSelect={onSelect} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default Item;
|
||||
|
||||
5
src/routes/Settings/URLsManager/Item/index.ts
Normal file
5
src/routes/Settings/URLsManager/Item/index.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
import Item from './Item'
|
||||
|
||||
export default Item;
|
||||
81
src/routes/Settings/URLsManager/URLsManager.less
Normal file
81
src/routes/Settings/URLsManager/URLsManager.less
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
.wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 35rem;
|
||||
margin-bottom: 2rem;
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
|
||||
.label {
|
||||
font-size: 1rem;
|
||||
color: var(--primary-foreground-color);
|
||||
font-weight: 400;
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
padding: 1.5rem 0;
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.add-url {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0.5rem 1.5rem;
|
||||
background-color: var(--secondary-accent-color);
|
||||
transition: 0.3s all ease-in-out;
|
||||
border-radius: 1.5rem;
|
||||
color: var(--primary-foreground-color);
|
||||
border: 2px solid transparent;
|
||||
|
||||
.icon {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
color: var(--primary-foreground-color);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: transparent;
|
||||
border: 2px solid var(--primary-foreground-color);
|
||||
}
|
||||
}
|
||||
|
||||
.reload {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0.5rem 1.5rem;
|
||||
background-color: var(--overlay-color);
|
||||
border-radius: 1.5rem;
|
||||
transition: 0.3s all ease-in-out;
|
||||
color: var(--primary-foreground-color);
|
||||
border: 2px solid transparent;
|
||||
|
||||
.icon {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
color: var(--primary-foreground-color);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: transparent;
|
||||
border: 2px solid var(--primary-foreground-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
61
src/routes/Settings/URLsManager/URLsManager.tsx
Normal file
61
src/routes/Settings/URLsManager/URLsManager.tsx
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import styles from './URLsManager.less';
|
||||
import Button from 'stremio/common/Button';
|
||||
import Item from './Item';
|
||||
import Icon from '@stremio/stremio-icons/react';
|
||||
import useStreamingServerUrls from 'stremio/common/useStreamingServerUrls';
|
||||
|
||||
const URLsManager = () => {
|
||||
const { t } = useTranslation();
|
||||
const [addMode, setAddMode] = useState(false);
|
||||
const { streamingServerUrls, actions } = useStreamingServerUrls();
|
||||
|
||||
const onAdd = () => {
|
||||
setAddMode(true);
|
||||
}
|
||||
|
||||
const onCancel = () => {
|
||||
setAddMode(false);
|
||||
};
|
||||
|
||||
const handleAddUrl = useCallback((url: string) => {
|
||||
actions.onAdd(url);
|
||||
setAddMode(false);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={styles['wrapper']}>
|
||||
<div className={styles['header']}>
|
||||
<div className={styles['label']}>URL</div>
|
||||
<div className={styles['label']}>{ t('STATUS') }</div>
|
||||
</div>
|
||||
<div className={styles['content']}>
|
||||
{
|
||||
streamingServerUrls.map((url: StreamingServerUrl, index: number) => (
|
||||
<Item mode={'view'} key={index} {...url} {...actions} />
|
||||
))
|
||||
}
|
||||
{
|
||||
addMode ?
|
||||
<Item mode={'add'} onAdd={handleAddUrl} onCancel={onCancel} />
|
||||
: null
|
||||
}
|
||||
</div>
|
||||
<div className={styles['footer']}>
|
||||
<Button label={'Add URL'} className={styles['add-url']} onClick={onAdd}>
|
||||
<Icon name={'add'} className={styles['icon']} />
|
||||
{ t('ADD_URL') }
|
||||
</Button>
|
||||
<Button className={styles['reload']} title={'Reload'} onClick={actions.onReload}>
|
||||
<Icon name={'reset'} className={styles['icon']} />
|
||||
<div className={styles['label']}>{t('RELOAD')}</div>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default URLsManager;
|
||||
5
src/routes/Settings/URLsManager/index.ts
Normal file
5
src/routes/Settings/URLsManager/index.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
import URLsManager from './URLsManager';
|
||||
|
||||
export default URLsManager;
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
@import (reference) '~@stremio/stremio-colors/less/stremio-colors.less';
|
||||
@import (reference) '~stremio/common/screen-sizes.less';
|
||||
|
||||
:import('~stremio/common/Checkbox/styles.less') {
|
||||
:import('~stremio/common/Toggle/styles.less') {
|
||||
checkbox-icon: icon;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ const useProfileSettingsInputs = (profile) => {
|
|||
});
|
||||
}
|
||||
}), [profile.settings]);
|
||||
const surroundSoundCheckbox = React.useMemo(() => ({
|
||||
const surroundSoundToggle = React.useMemo(() => ({
|
||||
checked: profile.settings.surroundSound,
|
||||
onClick: () => {
|
||||
core.transport.dispatch({
|
||||
|
|
@ -151,7 +151,7 @@ const useProfileSettingsInputs = (profile) => {
|
|||
});
|
||||
}
|
||||
}), [profile.settings]);
|
||||
const escExitFullscreenCheckbox = React.useMemo(() => ({
|
||||
const escExitFullscreenToggle = React.useMemo(() => ({
|
||||
checked: profile.settings.escExitFullscreen,
|
||||
onClick: () => {
|
||||
core.transport.dispatch({
|
||||
|
|
@ -261,7 +261,7 @@ const useProfileSettingsInputs = (profile) => {
|
|||
});
|
||||
}
|
||||
}), [profile.settings]);
|
||||
const bingeWatchingCheckbox = React.useMemo(() => ({
|
||||
const bingeWatchingToggle = React.useMemo(() => ({
|
||||
checked: profile.settings.bingeWatching,
|
||||
onClick: () => {
|
||||
core.transport.dispatch({
|
||||
|
|
@ -276,7 +276,7 @@ const useProfileSettingsInputs = (profile) => {
|
|||
});
|
||||
}
|
||||
}), [profile.settings]);
|
||||
const playInBackgroundCheckbox = React.useMemo(() => ({
|
||||
const playInBackgroundToggle = React.useMemo(() => ({
|
||||
checked: profile.settings.playInBackground,
|
||||
onClick: () => {
|
||||
core.transport.dispatch({
|
||||
|
|
@ -291,7 +291,7 @@ const useProfileSettingsInputs = (profile) => {
|
|||
});
|
||||
}
|
||||
}), [profile.settings]);
|
||||
const hardwareDecodingCheckbox = React.useMemo(() => ({
|
||||
const hardwareDecodingToggle = React.useMemo(() => ({
|
||||
checked: profile.settings.hardwareDecoding,
|
||||
onClick: () => {
|
||||
core.transport.dispatch({
|
||||
|
|
@ -306,21 +306,6 @@ const useProfileSettingsInputs = (profile) => {
|
|||
});
|
||||
}
|
||||
}), [profile.settings]);
|
||||
const streamingServerUrlInput = React.useMemo(() => ({
|
||||
value: profile.settings.streamingServerUrl,
|
||||
onChange: (value) => {
|
||||
core.transport.dispatch({
|
||||
action: 'Ctx',
|
||||
args: {
|
||||
action: 'UpdateSettings',
|
||||
args: {
|
||||
...profile.settings,
|
||||
streamingServerUrl: value
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}), [profile.settings]);
|
||||
return {
|
||||
interfaceLanguageSelect,
|
||||
subtitlesLanguageSelect,
|
||||
|
|
@ -329,16 +314,15 @@ const useProfileSettingsInputs = (profile) => {
|
|||
subtitlesBackgroundColorInput,
|
||||
subtitlesOutlineColorInput,
|
||||
audioLanguageSelect,
|
||||
surroundSoundCheckbox,
|
||||
escExitFullscreenCheckbox,
|
||||
surroundSoundToggle,
|
||||
escExitFullscreenToggle,
|
||||
seekTimeDurationSelect,
|
||||
seekShortTimeDurationSelect,
|
||||
playInExternalPlayerSelect,
|
||||
nextVideoPopupDurationSelect,
|
||||
bingeWatchingCheckbox,
|
||||
playInBackgroundCheckbox,
|
||||
hardwareDecodingCheckbox,
|
||||
streamingServerUrlInput
|
||||
bingeWatchingToggle,
|
||||
playInBackgroundToggle,
|
||||
hardwareDecodingToggle,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
8
src/types/models/Ctx.d.ts
vendored
8
src/types/models/Ctx.d.ts
vendored
|
|
@ -67,8 +67,16 @@ type SearchHistoryItem = {
|
|||
|
||||
type SearchHistory = SearchHistoryItem[];
|
||||
|
||||
type StreamingServerUrl = {
|
||||
url: string,
|
||||
_mtime: Date,
|
||||
};
|
||||
|
||||
type StreamingServerUrls = StreamingServerUrl[];
|
||||
|
||||
type Ctx = {
|
||||
profile: Profile,
|
||||
notifications: Notifications,
|
||||
searchHistory: SearchHistory,
|
||||
streamingServerUrls: StreamingServerUrls,
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue