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 ? - @@ -591,7 +602,7 @@ const Player = ({ urlParams, queryParams }) => { null } { - subtitlesMenuOpen || infoMenuOpen || videosMenuOpen || speedMenuOpen ? + subtitlesMenuOpen || infoMenuOpen || videosMenuOpen || speedMenuOpen || optionsMenuOpen ?
: 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 ? + + : + 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 = () => {
Trakt Scrobbling
- -
-
-
-
Facebook
-
-
{ t('Calendar') }
-
-
@@ -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;