From 5fbfd9c81efe8a7732169a90d72111c4941ba007 Mon Sep 17 00:00:00 2001 From: Steve Richter Date: Mon, 21 Jun 2021 21:20:44 -0400 Subject: [PATCH] Implement Last.fm account linking UI --- core/agents/lastfm/token_received.html | 18 +++- ui/src/i18n/en.json | 9 +- ui/src/personal/LastfmScrobbleToggle.js | 122 ++++++++++++++++++++++++ ui/src/personal/Personal.js | 4 +- ui/src/personal/ScrobbleToggle.js | 84 ---------------- ui/src/utils/openInNewTab.js | 1 + 6 files changed, 149 insertions(+), 89 deletions(-) create mode 100644 ui/src/personal/LastfmScrobbleToggle.js delete mode 100644 ui/src/personal/ScrobbleToggle.js diff --git a/core/agents/lastfm/token_received.html b/core/agents/lastfm/token_received.html index 1bbd6a256..775a68228 100644 --- a/core/agents/lastfm/token_received.html +++ b/core/agents/lastfm/token_received.html @@ -1 +1,17 @@ -Success! Your account is linked to Last.FM. You can close this tab now. + + + + + Account Linking Success + + +

+ Success! Your account is linked to Last.fm. You can close this tab now. +

+ + + diff --git a/ui/src/i18n/en.json b/ui/src/i18n/en.json index f1d67903a..56e10aebf 100644 --- a/ui/src/i18n/en.json +++ b/ui/src/i18n/en.json @@ -297,7 +297,11 @@ "delete_user_title": "Delete user '%{name}'", "delete_user_content": "Are you sure you want to delete this user and all their data (including playlists and preferences)?", "notifications_blocked": "You have blocked Notifications for this site in your browser's settings", - "notifications_not_available": "This browser does not support desktop notifications or you are not accessing Navidrome over https" + "notifications_not_available": "This browser does not support desktop notifications or you are not accessing Navidrome over https", + "lastfmLinkSuccess": "Last.fm successfully linked and scrobbling enabled", + "lastfmLinkFailure": "Last.fm could not be linked", + "lastfmUnlinkSuccess": "Last.fm unlinked and scrobbling disabled", + "lastfmUnlinkFailure": "Last.fm could not unlinked" }, "menu": { "library": "Library", @@ -310,7 +314,8 @@ "theme": "Theme", "language": "Language", "defaultView": "Default View", - "desktop_notifications": "Desktop Notifications" + "desktop_notifications": "Desktop Notifications", + "lastfmScrobbling": "Scrobble to Last.fm" } }, "albumList": "Albums", diff --git a/ui/src/personal/LastfmScrobbleToggle.js b/ui/src/personal/LastfmScrobbleToggle.js new file mode 100644 index 000000000..92faf307f --- /dev/null +++ b/ui/src/personal/LastfmScrobbleToggle.js @@ -0,0 +1,122 @@ +import { useEffect, useRef, useState } from 'react' +import { useNotify, useTranslate } from 'react-admin' +import { + FormControl, + FormControlLabel, + LinearProgress, + Switch, +} from '@material-ui/core' +import { useInterval } from '../common' +import config from '../config' +import { baseUrl, openInNewTab } from '../utils' +import { httpClient } from '../dataProvider' + +const Progress = (props) => { + const { setLinked, setCheckingLink } = props + const notify = useNotify() + let linkCheckDelay = 2000 + let linkChecks = 30 + const openedTab = useRef() + + useEffect(() => { + const callbackEndpoint = baseUrl( + `/api/lastfm/link/callback?uid=${localStorage.getItem('userId')}` + ) + const callbackUrl = `${window.location.origin}${callbackEndpoint}` + openedTab.current = openInNewTab( + `https://www.last.fm/api/auth/?api_key=${config.lastFMApiKey}&cb=${callbackUrl}` + ) + }, []) + + const endChecking = (success) => { + linkCheckDelay = null + setCheckingLink(false) + if (success) { + notify('message.lastfmLinkSuccess', 'success') + } else { + notify('message.lastfmLinkFailure', 'warning') + } + setLinked(success) + } + + useInterval(() => { + httpClient('/api/lastfm/link') + .then((response) => { + let result = false + if (response.json.status === true) { + result = true + endChecking(true) + } + return result + }) + .then((result) => { + if (!result && openedTab.current?.closed === true) { + endChecking(false) + result = true + } + return result + }) + .then((result) => { + if (!result && --linkChecks === 0) { + endChecking(false) + } + }) + .catch(() => { + endChecking(false) + }) + }, linkCheckDelay) + + return +} + +export const LastfmScrobbleToggle = (props) => { + const notify = useNotify() + const translate = useTranslate() + const [linked, setLinked] = useState(null) + const [checkingLink, setCheckingLink] = useState(false) + + const toggleScrobble = () => { + if (!linked) { + setCheckingLink(true) + } else { + httpClient('/api/lastfm/link', { method: 'DELETE' }) + .then(() => { + setLinked(false) + notify('message.lastfmUnlinkSuccess', 'success') + }) + .catch(() => notify('message.lastfmUnlinkFailure', 'warning')) + } + } + + useEffect(() => { + httpClient('/api/lastfm/link') + .then((response) => { + setLinked(response.json.status === true) + }) + .catch(() => { + setLinked(false) + }) + }, []) + + return ( + + + } + label={ + {translate('menu.personal.options.lastfmScrobbling')} + } + /> + {checkingLink && ( + + )} + + ) +} diff --git a/ui/src/personal/Personal.js b/ui/src/personal/Personal.js index f7f077062..8e0c8067e 100644 --- a/ui/src/personal/Personal.js +++ b/ui/src/personal/Personal.js @@ -5,7 +5,7 @@ import { SelectLanguage } from './SelectLanguage' import { SelectTheme } from './SelectTheme' import { SelectDefaultView } from './SelectDefaultView' import { NotificationsToggle } from './NotificationsToggle' -import { ScrobbleToggle } from './ScrobbleToggle' +import { LastfmScrobbleToggle } from './LastfmScrobbleToggle' import config from '../config' const useStyles = makeStyles({ @@ -24,7 +24,7 @@ const Personal = () => { - {config.devEnableScrobble && } + {config.devEnableScrobble && } ) diff --git a/ui/src/personal/ScrobbleToggle.js b/ui/src/personal/ScrobbleToggle.js deleted file mode 100644 index 06f870128..000000000 --- a/ui/src/personal/ScrobbleToggle.js +++ /dev/null @@ -1,84 +0,0 @@ -import { useState } from 'react' -import { useNotify, useTranslate } from 'react-admin' -import { - FormControl, - FormControlLabel, - LinearProgress, - Switch, -} from '@material-ui/core' -import { useInterval } from '../common' -import { baseUrl } from '../utils' - -const Progress = (props) => { - const { setLinked, setCheckingLink } = props - const translate = useTranslate() - const notify = useNotify() - let linkCheckDelay = 2000 - let linkChecks = 5 - // openInNewTab( - // '/api/lastfm/link' - // ) - - const endChecking = (success) => { - linkCheckDelay = null - setCheckingLink(false) - if (success) { - notify(translate('Last.fm successfully linked!'), 'success') - } else { - notify(translate('Last.fm could not be linked'), 'warning') - } - setLinked(success) - } - - useInterval(() => { - fetch(baseUrl(`/api/lastfm/link/status?c=${linkChecks}`)) - .then((response) => response.text()) - .then((response) => { - if (response === 'true') { - endChecking(true) - } - }) - .catch((error) => { - endChecking(false) - throw new Error(error) - }) - - if (--linkChecks === 0) { - endChecking(false) - } - }, linkCheckDelay) - - return linkChecks > 0 && -} - -export const ScrobbleToggle = (props) => { - const translate = useTranslate() - const [linked, setLinked] = useState(false) - const [checkingLink, setCheckingLink] = useState(false) - const toggleScrobble = () => { - if (!linked) { - return setCheckingLink(true) - } - setLinked(!linked) - } - - return ( - - - } - label={{translate('Scrobble to Last.FM')}} - /> - {checkingLink && ( - - )} - - ) -} diff --git a/ui/src/utils/openInNewTab.js b/ui/src/utils/openInNewTab.js index b5ad35612..ea34a0662 100644 --- a/ui/src/utils/openInNewTab.js +++ b/ui/src/utils/openInNewTab.js @@ -1,4 +1,5 @@ export const openInNewTab = (url) => { const win = window.open(url, '_blank') win.focus() + return win }