diff --git a/package-lock.json b/package-lock.json
index 6ac917efc..d0377fe32 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -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"
}
diff --git a/package.json b/package.json
index e3c33933f..e01a49d35 100755
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/src/App/App.js b/src/App/App.js
index 97e32b920..3344d5645 100644
--- a/src/App/App.js
+++ b/src/App/App.js
@@ -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]);
diff --git a/src/App/ServicesToaster.js b/src/App/ServicesToaster.js
index 43b076c9a..d3fdd3461 100644
--- a/src/App/ServicesToaster.js
+++ b/src/App/ServicesToaster.js
@@ -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,
diff --git a/src/routes/Player/ControlBar/ControlBar.js b/src/routes/Player/ControlBar/ControlBar.js
index af672369e..2ffc02c7d 100644
--- a/src/routes/Player/ControlBar/ControlBar.js
+++ b/src/routes/Player/ControlBar/ControlBar.js
@@ -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
}
+
@@ -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;
diff --git a/src/routes/Player/OptionsMenu/OptionsMenu.js b/src/routes/Player/OptionsMenu/OptionsMenu.js
new file mode 100644
index 000000000..a310dc66c
--- /dev/null
+++ b/src/routes/Player/OptionsMenu/OptionsMenu.js
@@ -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 (
+
+
+
+ {/*
*/}
+
+ );
+};
+
+OptionsMenu.propTypes = {
+ className: PropTypes.string,
+ stream: PropTypes.object
+};
+
+module.exports = OptionsMenu;
diff --git a/src/routes/Player/OptionsMenu/index.js b/src/routes/Player/OptionsMenu/index.js
new file mode 100644
index 000000000..069b002b3
--- /dev/null
+++ b/src/routes/Player/OptionsMenu/index.js
@@ -0,0 +1,5 @@
+// Copyright (C) 2017-2022 Smart code 203358507
+
+const OptionsMenu = require('./OptionsMenu');
+
+module.exports = OptionsMenu;
diff --git a/src/routes/Player/OptionsMenu/styles.less b/src/routes/Player/OptionsMenu/styles.less
new file mode 100644
index 000000000..54ebd0368
--- /dev/null
+++ b/src/routes/Player/OptionsMenu/styles.less
@@ -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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/routes/Player/Player.js b/src/routes/Player/Player.js
index e797c28cb..6f27e0b11 100644
--- a/src/routes/Player/Player.js
+++ b/src/routes/Player/Player.js
@@ -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 (
- {
{error.message}
{
player.selected !== null ?
-
);
};
diff --git a/src/routes/Player/usePlayer.js b/src/routes/Player/usePlayer.js
index 9acd16396..632cf81b5 100644
--- a/src/routes/Player/usePlayer.js
+++ b/src/routes/Player/usePlayer.js
@@ -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',
diff --git a/src/routes/Settings/Settings.js b/src/routes/Settings/Settings.js
index f00acfddf..81f147e2e 100644
--- a/src/routes/Settings/Settings.js
+++ b/src/routes/Settings/Settings.js
@@ -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 = () => {
-
+
- { t('SETTINGS_TRAKT_AUTHENTICATE') }
-
-
-
-
-
-
- { t('SETTINGS_FACEBOOK_IMPORT') }
+
+ { profile.auth !== null && profile.auth.user !== null && profile.auth.user.trakt !== null ? t('LOG_OUT') : t('SETTINGS_TRAKT_AUTHENTICATE') }
+
-
+
{ t('SETTINGS_CALENDAR_SUBSCRIBE') }
-
+
{ t('SETTINGS_DATA_EXPORT') }
@@ -564,6 +595,7 @@ const Settings = () => {
onCloseRequest={closeConfigureServerUrlModal}>
({
+ ...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;