Merge branch 'development' of https://github.com/Stremio/stremio-web into interface-language

This commit is contained in:
Tim 2023-03-10 13:57:41 +01:00
commit 2275f40eaa
12 changed files with 305 additions and 63 deletions

14
package-lock.json generated
View file

@ -12,7 +12,7 @@
"@babel/runtime": "7.16.0",
"@sentry/browser": "6.13.3",
"@stremio/stremio-colors": "5.0.1",
"@stremio/stremio-core-web": "0.44.7",
"@stremio/stremio-core-web": "0.44.11",
"@stremio/stremio-icons": "4.0.0",
"@stremio/stremio-video": "0.0.24",
"a-color-picker": "1.2.1",
@ -2702,9 +2702,9 @@
"integrity": "sha512-Dt3PYmy1DZ473QNs99KYXVWQPHtpIl37VUY0+gCEvvuCqE1fRrZIJtZ9KbysUKonvO7WwdQDztgcW0iGoc1dEA=="
},
"node_modules/@stremio/stremio-core-web": {
"version": "0.44.7",
"resolved": "https://registry.npmjs.org/@stremio/stremio-core-web/-/stremio-core-web-0.44.7.tgz",
"integrity": "sha512-hkeYLfL1On4TMBHn87Onrp93aeRuTh4YXMKdDR1Vz5YikPOiPEq/JRoLLmmSSsFEdifs6Egu+A0qiggTttepOA==",
"version": "0.44.11",
"resolved": "https://registry.npmjs.org/@stremio/stremio-core-web/-/stremio-core-web-0.44.11.tgz",
"integrity": "sha512-KkNEZenQ+94ylYEMl/AqvL1Md4p+xwqg/02FKuVxjFBEoKyXEDXCgOKOI7wbbXdZJe8tnvk+XFq39jzAiTinSw==",
"dependencies": {
"@babel/runtime": "7.16.0"
}
@ -16795,9 +16795,9 @@
"integrity": "sha512-Dt3PYmy1DZ473QNs99KYXVWQPHtpIl37VUY0+gCEvvuCqE1fRrZIJtZ9KbysUKonvO7WwdQDztgcW0iGoc1dEA=="
},
"@stremio/stremio-core-web": {
"version": "0.44.7",
"resolved": "https://registry.npmjs.org/@stremio/stremio-core-web/-/stremio-core-web-0.44.7.tgz",
"integrity": "sha512-hkeYLfL1On4TMBHn87Onrp93aeRuTh4YXMKdDR1Vz5YikPOiPEq/JRoLLmmSSsFEdifs6Egu+A0qiggTttepOA==",
"version": "0.44.11",
"resolved": "https://registry.npmjs.org/@stremio/stremio-core-web/-/stremio-core-web-0.44.11.tgz",
"integrity": "sha512-KkNEZenQ+94ylYEMl/AqvL1Md4p+xwqg/02FKuVxjFBEoKyXEDXCgOKOI7wbbXdZJe8tnvk+XFq39jzAiTinSw==",
"requires": {
"@babel/runtime": "7.16.0"
}

View file

@ -15,7 +15,7 @@
"@babel/runtime": "7.16.0",
"@sentry/browser": "6.13.3",
"@stremio/stremio-colors": "5.0.1",
"@stremio/stremio-core-web": "0.44.7",
"@stremio/stremio-core-web": "0.44.11",
"@stremio/stremio-icons": "4.0.0",
"@stremio/stremio-video": "0.0.24",
"a-color-picker": "1.2.1",

View file

@ -107,7 +107,7 @@ const App = () => {
i18n.changeLanguage(state.profile.settings.interfaceLanguage);
}
};
if (services.core.active) {
const onWindowFocus = () => {
services.core.transport.dispatch({
action: 'Ctx',
args: {
@ -126,6 +126,10 @@ const App = () => {
action: 'SyncLibraryWithAPI'
}
});
};
if (services.core.active) {
onWindowFocus();
window.addEventListener('focus', onWindowFocus);
services.core.transport.on('CoreEvent', onCoreEvent);
services.core.transport
.getState('ctx')
@ -133,6 +137,7 @@ const App = () => {
.catch((e) => console.error(e));
}
return () => {
window.removeEventListener('focus', onWindowFocus);
services.core.transport.off('CoreEvent', onCoreEvent);
};
}, [initialized]);

View file

@ -19,6 +19,10 @@ const ServicesToaster = () => {
break;
}
if (args.error.type === 'Other' && args.error.code === 3 && args.source.event === 'AddonInstalled' && args.source.args.transport_url.startsWith('https://www.strem.io/trakt/addon')) {
break;
}
toast.show({
type: 'error',
title: args.source.event,

View file

@ -34,6 +34,7 @@ const ControlBar = ({
onToggleInfoMenu,
onToggleSpeedMenu,
onToggleVideosMenu,
onToggleOptionsMenu,
...props
}) => {
const { chromecast } = useServices();
@ -51,6 +52,9 @@ const ControlBar = ({
const onVideosButtonMouseDown = React.useCallback((event) => {
event.nativeEvent.videosMenuClosePrevented = true;
}, []);
const onOptionsButtonMouseDown = React.useCallback((event) => {
event.nativeEvent.optionsMenuClosePrevented = true;
}, []);
const onPlayPauseButtonClick = React.useCallback(() => {
if (paused) {
if (typeof onPlayRequested === 'function') {
@ -102,6 +106,11 @@ const ControlBar = ({
onToggleVideosMenu();
}
}, [onToggleVideosMenu]);
const onOptionsButtonClick = React.useCallback(() => {
if (typeof onToggleOptionsMenu === 'function') {
onToggleOptionsMenu();
}
}, [onToggleOptionsMenu]);
const onChromecastButtonClick = React.useCallback(() => {
chromecast.transport.requestSession();
}, []);
@ -179,6 +188,9 @@ const ControlBar = ({
:
null
}
<Button className={styles['control-bar-button']} tabIndex={-1} onMouseDown={onOptionsButtonMouseDown} onClick={onOptionsButtonClick}>
<Icon className={styles['icon']} icon={'ic_more'} />
</Button>
</div>
</div>
</div>
@ -206,7 +218,8 @@ ControlBar.propTypes = {
onToggleSubtitlesMenu: PropTypes.func,
onToggleInfoMenu: PropTypes.func,
onToggleSpeedMenu: PropTypes.func,
onToggleVideosMenu: PropTypes.func
onToggleVideosMenu: PropTypes.func,
onToggleOptionsMenu: PropTypes.func,
};
module.exports = ControlBar;

View file

@ -0,0 +1,92 @@
// Copyright (C) 2017-2022 Smart code 203358507
const React = require('react');
const PropTypes = require('prop-types');
const classnames = require('classnames');
const Icon = require('@stremio/stremio-icons/dom');
const { Button, useToast } = require('stremio/common');
// const { useServices } = require('stremio/services');
const styles = require('./styles');
const OptionsMenu = ({ className, stream }) => {
// const { core } = useServices();
const toast = useToast();
const streamUrl = React.useMemo(() => {
return stream !== null ?
stream.deepLinks &&
stream.deepLinks.externalPlayer &&
typeof stream.deepLinks.externalPlayer.download === 'string' ?
stream.deepLinks.externalPlayer.download
:
null
:
null;
}, [stream]);
const onCopyStreamButtonClick = React.useCallback(() => {
if (streamUrl !== null) {
navigator.clipboard.writeText(streamUrl)
.then(() => {
toast.show({
type: 'success',
title: 'Copied',
message: 'Stream link was copied to your clipboard',
timeout: 3000
});
})
.catch((e) => {
console.error(e);
toast.show({
type: 'error',
title: 'Error',
message: `Failed to copy stream link: ${streamUrl}`,
timeout: 3000
});
});
}
}, [streamUrl]);
const onDownloadVideoButtonClick = React.useCallback(() => {
if (streamUrl !== null) {
window.open(streamUrl);
}
}, [streamUrl]);
// const onExternalPlayerButtonClick = React.useCallback(() => {
// if (streamUrl !== null) {
// core.transport.dispatch({
// action: 'StreamingServer',
// args: {
// action: 'PlayOnDevice',
// args: {
// device: 'vlc',
// source: streamUrl,
// }
// }
// });
// }
// }, [streamUrl]);
const onMouseDown = React.useCallback((event) => {
event.nativeEvent.optionsMenuClosePrevented = true;
}, []);
return (
<div className={classnames(className, styles['options-menu-container'])} onMouseDown={onMouseDown}>
<Button className={classnames(styles['option-container'], { 'disabled': stream === null })} disabled={stream === null} onClick={onCopyStreamButtonClick}>
<Icon className={styles['icon']} icon={'ic_link'} />
<div className={styles['label']}>Copy Stream Link</div>
</Button>
<Button className={classnames(styles['option-container'], { 'disabled': stream === null })} disabled={stream === null}onClick={onDownloadVideoButtonClick}>
<Icon className={styles['icon']} icon={'ic_downloads'} />
<div className={styles['label']}>Download Video</div>
</Button>
{/* <Button className={classnames(styles['option-container'], { 'disabled': stream === null })} disabled={stream === null} onClick={onExternalPlayerButtonClick}>
<Icon className={styles['icon']} icon={'ic_vlc'} />
<div className={styles['label']}>Play in External Player</div>
</Button> */}
</div>
);
};
OptionsMenu.propTypes = {
className: PropTypes.string,
stream: PropTypes.object
};
module.exports = OptionsMenu;

View file

@ -0,0 +1,5 @@
// Copyright (C) 2017-2022 Smart code 203358507
const OptionsMenu = require('./OptionsMenu');
module.exports = OptionsMenu;

View file

@ -0,0 +1,37 @@
// Copyright (C) 2017-2022 Smart code 203358507
@import (reference) '~@stremio/stremio-colors/less/stremio-colors.less';
.options-menu-container {
width: 15rem;
.option-container {
display: flex;
flex-direction: row;
align-items: center;
height: 4rem;
.icon {
flex: none;
width: 1.4rem;
height: 1.4rem;
margin: 1.3rem;
fill: @color-surface-light5-90;
}
.label {
flex: 1;
max-height: 2.4em;
font-weight: 400;
color: @color-surface-light5-90;
}
&:hover {
background-color: @color-background-light2;
}
&:global(.disabled) {
opacity: 0.5;
}
}
}

View file

@ -13,6 +13,7 @@ const BufferingLoader = require('./BufferingLoader');
const ControlBar = require('./ControlBar');
const NextVideoPopup = require('./NextVideoPopup');
const InfoMenu = require('./InfoMenu');
const OptionsMenu = require('./OptionsMenu');
const VideosMenu = require('./VideosMenu');
const SubtitlesMenu = require('./SubtitlesMenu');
const SpeedMenu = require('./SpeedMenu');
@ -29,26 +30,6 @@ const Player = ({ urlParams, queryParams }) => {
queryParams.has('maxAudioChannels') ? parseInt(queryParams.get('maxAudioChannels'), 10) : null
];
}, [queryParams]);
const [player, timeChanged, pausedChanged, ended, pushToLibrary] = usePlayer(urlParams);
const [settings, updateSettings] = useSettings();
const streamingServer = useStreamingServer();
const routeFocused = useRouteFocused();
const toast = useToast();
const [, , , toggleFullscreen] = useFullscreen();
const [casting, setCasting] = React.useState(() => {
return chromecast.active && chromecast.transport.getCastState() === cast.framework.CastState.CONNECTED;
});
const [immersed, setImmersed] = React.useState(true);
const setImmersedDebounced = React.useCallback(debounce(setImmersed, 3000), []);
const [subtitlesMenuOpen, , closeSubtitlesMenu, toggleSubtitlesMenu] = useBinaryState(false);
const [infoMenuOpen, , closeInfoMenu, toggleInfoMenu] = useBinaryState(false);
const [speedMenuOpen, , closeSpeedMenu, toggleSpeedMenu] = useBinaryState(false);
const [videosMenuOpen, , closeVideosMenu, toggleVideosMenu] = useBinaryState(false);
const [nextVideoPopupOpen, openNextVideoPopup, closeNextVideoPopup] = useBinaryState(false);
const nextVideoPopupDismissed = React.useRef(false);
const defaultSubtitlesSelected = React.useRef(false);
const defaultAudioTrackSelected = React.useRef(false);
const [error, setError] = React.useState(null);
const [videoState, setVideoState] = React.useReducer(
(videoState, nextVideoState) => ({ ...videoState, ...nextVideoState }),
{
@ -61,6 +42,7 @@ const Player = ({ urlParams, queryParams }) => {
volume: null,
muted: null,
playbackSpeed: null,
videoParams: null,
audioTracks: [],
selectedAudioTrackId: null,
subtitlesTracks: [],
@ -80,6 +62,27 @@ const Player = ({ urlParams, queryParams }) => {
extraSubtitlesOutlineColor: null
}
);
const [player, timeChanged, pausedChanged, ended, pushToLibrary] = usePlayer(urlParams, videoState.videoParams);
const [settings, updateSettings] = useSettings();
const streamingServer = useStreamingServer();
const routeFocused = useRouteFocused();
const toast = useToast();
const [, , , toggleFullscreen] = useFullscreen();
const [casting, setCasting] = React.useState(() => {
return chromecast.active && chromecast.transport.getCastState() === cast.framework.CastState.CONNECTED;
});
const [immersed, setImmersed] = React.useState(true);
const setImmersedDebounced = React.useCallback(debounce(setImmersed, 3000), []);
const [optionsMenuOpen, , closeOptionsMenu, toggleOptionsMenu] = useBinaryState(false);
const [subtitlesMenuOpen, , closeSubtitlesMenu, toggleSubtitlesMenu] = useBinaryState(false);
const [infoMenuOpen, , closeInfoMenu, toggleInfoMenu] = useBinaryState(false);
const [speedMenuOpen, , closeSpeedMenu, toggleSpeedMenu] = useBinaryState(false);
const [videosMenuOpen, , closeVideosMenu, toggleVideosMenu] = useBinaryState(false);
const [nextVideoPopupOpen, openNextVideoPopup, closeNextVideoPopup] = useBinaryState(false);
const nextVideoPopupDismissed = React.useRef(false);
const defaultSubtitlesSelected = React.useRef(false);
const defaultAudioTrackSelected = React.useRef(false);
const [error, setError] = React.useState(null);
const videoRef = React.useRef(null);
const dispatch = React.useCallback((action, options) => {
if (videoRef.current !== null) {
@ -215,6 +218,9 @@ const Player = ({ urlParams, queryParams }) => {
toggleFullscreen();
}, [toggleFullscreen]);
const onContainerMouseDown = React.useCallback((event) => {
if (!event.nativeEvent.optionsMenuClosePrevented) {
closeOptionsMenu();
}
if (!event.nativeEvent.subtitlesMenuClosePrevented) {
closeSubtitlesMenu();
}
@ -439,7 +445,7 @@ const Player = ({ urlParams, queryParams }) => {
const onKeyDown = (event) => {
switch (event.code) {
case 'Space': {
if (!subtitlesMenuOpen && !infoMenuOpen && !videosMenuOpen && !speedMenuOpen&& videoState.paused !== null) {
if (!subtitlesMenuOpen && !infoMenuOpen && !videosMenuOpen && !speedMenuOpen && !optionsMenuOpen && videoState.paused !== null) {
if (videoState.paused) {
onPlayRequested();
} else {
@ -450,7 +456,7 @@ const Player = ({ urlParams, queryParams }) => {
break;
}
case 'ArrowRight': {
if (!subtitlesMenuOpen && !infoMenuOpen && !videosMenuOpen && !speedMenuOpen && videoState.time !== null) {
if (!subtitlesMenuOpen && !infoMenuOpen && !videosMenuOpen && !speedMenuOpen && !optionsMenuOpen && videoState.time !== null) {
const seekTimeMultiplier = event.shiftKey ? 3 : 1;
onSeekRequested(videoState.time + (settings.seekTimeDuration * seekTimeMultiplier));
}
@ -458,7 +464,7 @@ const Player = ({ urlParams, queryParams }) => {
break;
}
case 'ArrowLeft': {
if (!subtitlesMenuOpen && !infoMenuOpen && !videosMenuOpen && !speedMenuOpen && videoState.time !== null) {
if (!subtitlesMenuOpen && !infoMenuOpen && !videosMenuOpen && !speedMenuOpen && !optionsMenuOpen && videoState.time !== null) {
const seekTimeMultiplier = event.shiftKey ? 3 : 1;
onSeekRequested(videoState.time - (settings.seekTimeDuration * seekTimeMultiplier));
}
@ -466,20 +472,21 @@ const Player = ({ urlParams, queryParams }) => {
break;
}
case 'ArrowUp': {
if (!subtitlesMenuOpen && !infoMenuOpen && !videosMenuOpen && !speedMenuOpen && videoState.volume !== null) {
if (!subtitlesMenuOpen && !infoMenuOpen && !videosMenuOpen && !speedMenuOpen && !optionsMenuOpen && videoState.volume !== null) {
onVolumeChangeRequested(videoState.volume + 5);
}
break;
}
case 'ArrowDown': {
if (!subtitlesMenuOpen && !infoMenuOpen && !videosMenuOpen && !speedMenuOpen && videoState.volume !== null) {
if (!subtitlesMenuOpen && !infoMenuOpen && !videosMenuOpen && !speedMenuOpen && !optionsMenuOpen && videoState.volume !== null) {
onVolumeChangeRequested(videoState.volume - 5);
}
break;
}
case 'KeyS': {
closeOptionsMenu();
closeInfoMenu();
closeSpeedMenu();
closeVideosMenu();
@ -492,6 +499,7 @@ const Player = ({ urlParams, queryParams }) => {
break;
}
case 'KeyI': {
closeOptionsMenu();
closeSubtitlesMenu();
closeSpeedMenu();
closeVideosMenu();
@ -502,6 +510,7 @@ const Player = ({ urlParams, queryParams }) => {
break;
}
case 'KeyR': {
closeOptionsMenu();
closeInfoMenu();
closeSubtitlesMenu();
closeVideosMenu();
@ -512,6 +521,7 @@ const Player = ({ urlParams, queryParams }) => {
break;
}
case 'KeyV': {
closeOptionsMenu();
closeInfoMenu();
closeSubtitlesMenu();
closeSpeedMenu();
@ -522,6 +532,7 @@ const Player = ({ urlParams, queryParams }) => {
break;
}
case 'Escape': {
closeOptionsMenu();
closeSubtitlesMenu();
closeInfoMenu();
closeSpeedMenu();
@ -537,7 +548,7 @@ const Player = ({ urlParams, queryParams }) => {
return () => {
window.removeEventListener('keydown', onKeyDown);
};
}, [player.metaItem, settings.seekTimeDuration, routeFocused, subtitlesMenuOpen, infoMenuOpen, videosMenuOpen, speedMenuOpen, videoState.paused, videoState.time, videoState.volume, videoState.audioTracks, videoState.subtitlesTracks, videoState.extraSubtitlesTracks, videoState.playbackSpeed, toggleSubtitlesMenu, toggleInfoMenu, toggleVideosMenu]);
}, [player.metaItem, settings.seekTimeDuration, routeFocused, subtitlesMenuOpen, infoMenuOpen, videosMenuOpen, speedMenuOpen, optionsMenuOpen, videoState.paused, videoState.time, videoState.volume, videoState.audioTracks, videoState.subtitlesTracks, videoState.extraSubtitlesTracks, videoState.playbackSpeed, toggleSubtitlesMenu, toggleInfoMenu, toggleVideosMenu]);
React.useLayoutEffect(() => {
return () => {
setImmersedDebounced.cancel();
@ -546,7 +557,7 @@ const Player = ({ urlParams, queryParams }) => {
};
}, []);
return (
<div className={classnames(styles['player-container'], { [styles['immersed']]: immersed && !casting && videoState.paused !== null && !videoState.paused && !subtitlesMenuOpen && !infoMenuOpen && !speedMenuOpen && !videosMenuOpen && !nextVideoPopupOpen })}
<div className={classnames(styles['player-container'], { [styles['immersed']]: immersed && !casting && videoState.paused !== null && !videoState.paused && !subtitlesMenuOpen && !infoMenuOpen && !speedMenuOpen && !videosMenuOpen && !nextVideoPopupOpen && !optionsMenuOpen })}
onMouseDown={onContainerMouseDown}
onMouseMove={onContainerMouseMove}
onMouseOver={onContainerMouseMove}
@ -579,7 +590,7 @@ const Player = ({ urlParams, queryParams }) => {
<div className={styles['error-label']} title={error.message}>{error.message}</div>
{
player.selected !== null ?
<Button {...player.selected.stream.deepLinks.externalPlayer} className={styles['playlist-button']} title={'Open in external player'} target={'_blank'}>
<Button className={styles['playlist-button']} title={'Open in external player'} href={player.selected.stream.deepLinks.externalPlayer.href} download={player.selected.stream.deepLinks.externalPlayer.fileName} target={'_blank'}>
<Icon className={styles['icon']} icon={'ic_downloads'} />
<div className={styles['label']}>Open in external player</div>
</Button>
@ -591,7 +602,7 @@ const Player = ({ urlParams, queryParams }) => {
null
}
{
subtitlesMenuOpen || infoMenuOpen || videosMenuOpen || speedMenuOpen ?
subtitlesMenuOpen || infoMenuOpen || videosMenuOpen || speedMenuOpen || optionsMenuOpen ?
<div className={styles['layer']} />
:
null
@ -622,6 +633,7 @@ const Player = ({ urlParams, queryParams }) => {
onUnmuteRequested={onUnmuteRequested}
onVolumeChangeRequested={onVolumeChangeRequested}
onSeekRequested={onSeekRequested}
onToggleOptionsMenu={toggleOptionsMenu}
onToggleSubtitlesMenu={toggleSubtitlesMenu}
onToggleInfoMenu={toggleInfoMenu}
onToggleSpeedMenu={toggleSpeedMenu}
@ -699,6 +711,15 @@ const Player = ({ urlParams, queryParams }) => {
:
null
}
{
optionsMenuOpen ?
<OptionsMenu
className={classnames(styles['layer'], styles['menu-layer'])}
stream={player.selected.stream}
/>
:
null
}
</div>
);
};

View file

@ -32,7 +32,7 @@ const map = (player) => ({
player.metaItem,
});
const usePlayer = (urlParams) => {
const usePlayer = (urlParams, videoParams) => {
const { core } = useServices();
const { decodeStream } = useCoreSuspender();
const stream = decodeStream(urlParams.stream);
@ -44,6 +44,7 @@ const usePlayer = (urlParams) => {
model: 'Player',
args: {
stream,
videoParams,
streamRequest: typeof urlParams.streamTransportUrl === 'string' && typeof urlParams.type === 'string' && typeof urlParams.videoId === 'string' ?
{
base: urlParams.streamTransportUrl,
@ -85,7 +86,7 @@ const usePlayer = (urlParams) => {
action: 'Unload'
};
}
}, [urlParams]);
}, [urlParams, videoParams]);
const timeChanged = React.useCallback((time, duration, device) => {
core.transport.dispatch({
action: 'Player',

View file

@ -7,9 +7,10 @@ const { useTranslation } = require('react-i18next');
const Icon = require('@stremio/stremio-icons/dom');
const { useRouteFocused } = require('stremio-router');
const { useServices } = require('stremio/services');
const { Button, Checkbox, MainNavBars, Multiselect, ColorInput, TextInput, ModalDialog, useProfile, useStreamingServer, useBinaryState, withCoreSuspender } = require('stremio/common');
const { Button, Checkbox, MainNavBars, Multiselect, ColorInput, TextInput, ModalDialog, useProfile, useStreamingServer, useBinaryState, withCoreSuspender, useToast } = require('stremio/common');
const useProfileSettingsInputs = require('./useProfileSettingsInputs');
const useStreamingServerSettingsInputs = require('./useStreamingServerSettingsInputs');
const useDataExport = require('./useDataExport');
const styles = require('./styles');
const GENERAL_SECTION = 'general';
@ -22,7 +23,9 @@ const Settings = () => {
const { core } = useServices();
const { routeFocused } = useRouteFocused();
const profile = useProfile();
const [dataExport, loadDataExport] = useDataExport();
const streamingServer = useStreamingServer();
const toast = useToast();
const {
interfaceLanguageSelect,
subtitlesLanguageSelect,
@ -49,6 +52,11 @@ const Settings = () => {
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 [
{
@ -74,17 +82,31 @@ const Settings = () => {
}
});
}, []);
const authenticateTraktOnClick = React.useCallback(() => {
// TODO
}, []);
const importFacebookOnClick = React.useCallback(() => {
// TODO
}, []);
const toggleTraktOnClick = React.useCallback(() => {
if (!isTraktAuthenticated && profile.auth !== null && profile.auth.user !== null && typeof profile.auth.user._id === 'string') {
window.open(`https://www.strem.io/trakt/auth/${profile.auth.user._id}`);
setTraktAuthStarted(true);
} else {
core.transport.dispatch({
action: 'Ctx',
args: {
action: 'LogoutTrakt'
}
});
}
}, [isTraktAuthenticated, profile.auth]);
const subscribeCalendarOnClick = React.useCallback(() => {
// TODO
const url = `webcal://www.strem.io/calendar/${profile.auth.user._id}.ics`;
window.open(url);
toast.show({
type: 'success',
title: 'Calendar has been added to your default caldendar app',
timeout: 25000
});
//Stremio 4 emits not documented event subscribeCalendar
}, []);
const exportDataOnClick = React.useCallback(() => {
// TODO
loadDataExport();
}, []);
const reloadStreamingServer = React.useCallback(() => {
core.transport.dispatch({
@ -130,6 +152,22 @@ const Settings = () => {
const sectionsContainerOnScorll = React.useCallback(throttle(() => {
updateSelectedSectionId();
}, 50), []);
React.useEffect(() => {
if (isTraktAuthenticated && traktAuthStarted) {
core.transport.dispatch({
action: 'Ctx',
args: {
action: 'InstallTraktAddon'
}
});
setTraktAuthStarted(false);
}
}, [isTraktAuthenticated, traktAuthStarted]);
React.useEffect(() => {
if (dataExport.exportUrl !== null && typeof dataExport.exportUrl === 'string') {
window.open(dataExport.exportUrl);
}
}, [dataExport.exportUrl]);
React.useLayoutEffect(() => {
if (routeFocused) {
updateSelectedSectionId();
@ -217,31 +255,24 @@ const Settings = () => {
<div className={styles['option-name-container']}>
<div className={styles['label']}>Trakt Scrobbling</div>
</div>
<Button className={classnames(styles['option-input-container'], styles['button-container'])} title={'Authenticate'} disabled={true} tabIndex={-1} onClick={authenticateTraktOnClick}>
<Button className={classnames(styles['option-input-container'], styles['button-container'])} title={'Authenticate'} disabled={profile.auth === null} tabIndex={-1} onClick={toggleTraktOnClick}>
<Icon className={styles['icon']} icon={'ic_trakt'} />
<div className={styles['label']}>{ t('SETTINGS_TRAKT_AUTHENTICATE') }</div>
</Button>
</div>
<div className={styles['option-container']}>
<div className={styles['option-name-container']}>
<div className={styles['label']}>Facebook</div>
</div>
<Button className={classnames(styles['option-input-container'], styles['button-container'])} title={'Import'} disabled={true} tabIndex={-1} onClick={importFacebookOnClick}>
<Icon className={styles['icon']} icon={'ic_facebook'} />
<div className={styles['label']}>{ t('SETTINGS_FACEBOOK_IMPORT') }</div>
<div className={styles['label']}>
{ profile.auth !== null && profile.auth.user !== null && profile.auth.user.trakt !== null ? t('LOG_OUT') : t('SETTINGS_TRAKT_AUTHENTICATE') }
</div>
</Button>
</div>
<div className={styles['option-container']}>
<div className={styles['option-name-container']}>
<div className={styles['label']}>{ t('Calendar') }</div>
</div>
<Button className={classnames(styles['option-input-container'], styles['button-container'])} title={'Subscribe'} disabled={true} tabIndex={-1} onClick={subscribeCalendarOnClick}>
<Button className={classnames(styles['option-input-container'], styles['button-container'])} title={'Subscribe'} disabled={!(profile.auth && profile.auth.user && profile.auth.user._id)} tabIndex={-1} onClick={subscribeCalendarOnClick}>
<Icon className={styles['icon']} icon={'ic_calendar'} />
<div className={styles['label']}>{ t('SETTINGS_CALENDAR_SUBSCRIBE') }</div>
</Button>
</div>
<div className={styles['option-container']}>
<Button className={classnames(styles['option-input-container'], styles['link-container'])} title={t('SETTINGS_DATA_EXPORT')} disabled={true} tabIndex={-1} onClick={exportDataOnClick}>
<Button className={classnames(styles['option-input-container'], styles['link-container'])} title={t('SETTINGS_DATA_EXPORT')} tabIndex={-1} onClick={exportDataOnClick}>
<div className={styles['label']}>{ t('SETTINGS_DATA_EXPORT') }</div>
</Button>
</div>
@ -564,6 +595,7 @@ const Settings = () => {
onCloseRequest={closeConfigureServerUrlModal}>
<TextInput
ref={configureServerUrlInputRef}
autoFocus={true}
className={styles['server-url-input']}
type={'text'}
defaultValue={streamingServerUrlInput.value}

View file

@ -0,0 +1,32 @@
// Copyright (C) 2017-2022 Smart code 203358507
const React = require('react');
const { useServices } = require('stremio/services');
const { useModelState } = require('stremio/common');
const map = (dataExport) => ({
...dataExport,
exportUrl: dataExport !== null && dataExport.exportUrl !== null && dataExport.exportUrl.type === 'Ready' ?
dataExport.exportUrl.content
:
null
});
const useDataExport = () => {
const { core } = useServices();
const loadDataExport = React.useCallback(() => {
core.transport.dispatch({
action: 'Load',
args: {
model: 'DataExport',
}
}, 'data_export');
}, []);
const dataExport = useModelState({ model: 'data_export', map });
return [
dataExport,
loadDataExport
];
};
module.exports = useDataExport;