diff --git a/package-lock.json b/package-lock.json index 4e9b0c5..b9c3042 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "omp", - "version": "1.5.0", + "version": "1.5.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "omp", - "version": "1.5.0", + "version": "1.5.1", "dependencies": { "@azure/msal-browser": "^3.6.0", "@azure/msal-react": "^2.0.8", diff --git a/package.json b/package.json index 213e340..de8a2cc 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "omp", "description": "OneDrive Media Player", "private": true, - "version": "1.5.0", + "version": "1.5.1", "scripts": { "dev": "webpack serve", "build": "webpack --mode=production --node-env=production", diff --git a/src/App.tsx b/src/App.tsx index 35c097f..220c79c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,4 @@ -import { AuthenticatedTemplate, UnauthenticatedTemplate } from '@azure/msal-react' -import { Outlet } from 'react-router-dom' +import { Outlet, useLocation } from 'react-router-dom' import { Container, ThemeProvider, Paper } from '@mui/material' import Grid from '@mui/material/Unstable_Grid2' import NavBar from './pages/NavBar' @@ -10,15 +9,15 @@ import useUser from './hooks/graph/useUser' import useTheme from './hooks/ui/useTheme' import useSync from './hooks/graph/useSync' import useThemeColor from './hooks/ui/useThemeColor' -import SignIn from './pages/SignIn' +import LogIn from './pages/LogIn' import useUiStore from './store/useUiStore' import { useSpring, animated } from '@react-spring/web' import { useMemo } from 'react' const App = () => { const theme = useTheme() - const { accounts } = useUser() - useSync(accounts) + const { account } = useUser() + useSync() useThemeColor() const [coverColor] = useUiStore((state) => [state.coverColor]) @@ -35,6 +34,12 @@ const App = () => { [coverColor, theme.palette.background.default] ) + const location = useLocation() + const needLogin = useMemo( + () => (['/', '/history'].includes(location.pathname)) && !account, + [location, account] + ) + return ( { background: background, }} > - - - - - - + + + + + + + + + - - - - - - - + }}> + {needLogin ? : } + - - - + + - - - + diff --git a/src/hooks/graph/useFilesData.ts b/src/hooks/graph/useFilesData.ts index 809cff1..6774e19 100644 --- a/src/hooks/graph/useFilesData.ts +++ b/src/hooks/graph/useFilesData.ts @@ -1,9 +1,11 @@ import { getAppRootFiles, getFile, getFileThumbnails, getFiles, uploadAppRootJson } from '@/graph/graph' import { loginRequest } from '@/graph/authConfig' import useUser from './useUser' +import { useMsal } from '@azure/msal-react' const useFilesData = () => { - const { instance, accounts } = useUser() + const { account } = useUser() + const { instance } = useMsal() /** * 获取文件夹数据 @@ -11,7 +13,8 @@ const useFilesData = () => { * @returns */ const getFilesData = async (path: string) => { - const acquireToken = await instance.acquireTokenSilent({ ...loginRequest, account: accounts[0] }) + await instance.initialize() + const acquireToken = await instance.acquireTokenSilent({ ...loginRequest, account: account }) const response = await getFiles(path, acquireToken.accessToken) return response.value } @@ -22,7 +25,8 @@ const useFilesData = () => { * @returns */ const getFileData = async (filePath: string) => { - const acquireToken = await instance.acquireTokenSilent({ ...loginRequest, account: accounts[0] }) + await instance.initialize() + const acquireToken = await instance.acquireTokenSilent({ ...loginRequest, account: account }) const response = await getFile(filePath, acquireToken.accessToken) return response } @@ -33,19 +37,22 @@ const useFilesData = () => { * @returns */ const getFileThumbnailsData = async (itemId: string) => { - const acquireToken = await instance.acquireTokenSilent({ ...loginRequest, account: accounts[0] }) + await instance.initialize() + const acquireToken = await instance.acquireTokenSilent({ ...loginRequest, account: account }) const response = await getFileThumbnails(itemId, acquireToken.accessToken) return response } const getAppRootFilesData = async (filePath: string) => { - const acquireToken = await instance.acquireTokenSilent({ ...loginRequest, account: accounts[0] }) + await instance.initialize() + const acquireToken = await instance.acquireTokenSilent({ ...loginRequest, account: account }) const response = await getAppRootFiles(filePath, acquireToken.accessToken) return response } const uploadAppRootJsonData = async (fileName: string, fileContent: BodyInit) => { - const acquireToken = await instance.acquireTokenSilent({ ...loginRequest, account: accounts[0] }) + await instance.initialize() + const acquireToken = await instance.acquireTokenSilent({ ...loginRequest, account: account }) const response = await uploadAppRootJson(fileName, fileContent, acquireToken.accessToken) return response } diff --git a/src/hooks/graph/useSync.ts b/src/hooks/graph/useSync.ts index d4691d8..bd4ee4c 100644 --- a/src/hooks/graph/useSync.ts +++ b/src/hooks/graph/useSync.ts @@ -3,18 +3,17 @@ import useSWR from 'swr' import usePlaylistsStore from '@/store/usePlaylistsStore' import useHistoryStore from '@/store/useHistoryStore' import useFilesData from './useFilesData' -import { AccountInfo } from '@azure/msal-browser' import { File } from '@/types/file' import { Playlist } from '@/types/playlist' import { fetchJson } from '@/utils' +import useUser from './useUser' -const useSync = (accounts: AccountInfo[]) => { +const useSync = () => { + const { account } = useUser() const [historyList, updateHistoryList] = useHistoryStore((state) => [state.historyList, state.updateHistoryList]) const [playlists, updatePlaylists] = usePlaylistsStore((state) => [state.playlists, state.updatePlaylists]) const { getAppRootFilesData, uploadAppRootJsonData } = useFilesData() - const isLoggedIn = accounts.length > 0 - // 自动从 OneDrive 获取应用数据 const appDatafetcher = async () => { const appRootFiles = await getAppRootFilesData('/') @@ -36,7 +35,7 @@ const useSync = (accounts: AccountInfo[]) => { } } - const { data, error, isLoading } = useSWR<{ history: File[], playlists: Playlist[] }>(isLoggedIn ? 'fetchAppData' : null, appDatafetcher) + const { data, error, isLoading } = useSWR<{ history: File[], playlists: Playlist[] }>(account ? 'fetchAppData' : null, appDatafetcher) // 自动更新播放历史 useMemo( diff --git a/src/hooks/graph/useUser.ts b/src/hooks/graph/useUser.ts index 112674f..0149390 100644 --- a/src/hooks/graph/useUser.ts +++ b/src/hooks/graph/useUser.ts @@ -2,6 +2,7 @@ import { useMsal } from '@azure/msal-react' import { loginRequest } from '@/graph/authConfig' import usePlayQueueStore from '@/store/usePlayQueueStore' import useUiStore from '@/store/useUiStore' +import { AccountInfo } from '@azure/msal-browser' const useUser = () => { const { instance, accounts } = useMsal() @@ -19,11 +20,14 @@ const useUser = () => { usePlayQueueStore.persist.clearStorage() useUiStore.persist.clearStorage() instance.logoutRedirect({ - postLogoutRedirectUri: '/' + postLogoutRedirectUri: '/', }) + location.reload() } - return { instance, accounts, login, logout } + const account: AccountInfo | null = accounts[0] || null + + return { account, login, logout } } export default useUser \ No newline at end of file diff --git a/src/hooks/player/usePlayerCore.ts b/src/hooks/player/usePlayerCore.ts index 0cb079d..13e9a3d 100644 --- a/src/hooks/player/usePlayerCore.ts +++ b/src/hooks/player/usePlayerCore.ts @@ -205,6 +205,7 @@ const usePlayerCore = (player: HTMLVideoElement | null) => { if (!metaData) { const currentMetaData = playQueue.filter(item => item.index === currentIndex)[0] + updateCover('./cover.svg') updateCurrentMetaData( { title: currentMetaData?.fileName || 'Not playing', @@ -212,7 +213,6 @@ const usePlayerCore = (player: HTMLVideoElement | null) => { path: currentMetaData?.filePath, } ) - updateCover('./cover.webp') } if ( @@ -235,7 +235,7 @@ const usePlayerCore = (player: HTMLVideoElement | null) => { updateCover(URL.createObjectURL(new Blob([new Uint8Array(cover as ArrayBufferLike)], { type: 'image/png' }))) } } else { - updateCover('./cover.webp') + updateCover('./cover.svg') } } } diff --git a/src/hooks/ui/useTheme.ts b/src/hooks/ui/useTheme.ts index 83c5305..4e61447 100644 --- a/src/hooks/ui/useTheme.ts +++ b/src/hooks/ui/useTheme.ts @@ -1,23 +1,59 @@ +import usePlayerStore from '@/store/usePlayerStore' import useUiStore from '@/store/useUiStore' import { createTheme, useMediaQuery } from '@mui/material' -import { useEffect, useMemo } from 'react' +import { extractColors } from 'extract-colors' +import { useMemo } from 'react' const useTheme = () => { - const [coverColor, CoverThemeColor, colorMode] = useUiStore(state => [state.coverColor, state.CoverThemeColor, state.colorMode]) + const [ + coverColor, + CoverThemeColor, + colorMode, + updateCoverColor, + ] = useUiStore( + state => [ + state.coverColor, + state.CoverThemeColor, + state.colorMode, + state.updateCoverColor, + ] + ) + + const [cover] = usePlayerStore((state) => [state.cover]) - useEffect(() => { - if (colorMode === 'dark' || colorMode === 'light') - document.documentElement.setAttribute('data-theme', colorMode) - if (colorMode === 'auto') - document.documentElement.removeAttribute('data-theme') - return () => { - document.documentElement.removeAttribute('data-theme') - } - }, [colorMode]) + useMemo( + () => { + if (colorMode === 'dark' || colorMode === 'light') + document.documentElement.setAttribute('data-theme', colorMode) + if (colorMode === 'auto') + document.documentElement.removeAttribute('data-theme') + return () => { + document.documentElement.removeAttribute('data-theme') + } + }, + [colorMode] + ) const prefersColorSchemeDark = useMediaQuery('(prefers-color-scheme: dark)') const prefersDarkMode = colorMode === 'light' ? false : prefersColorSchemeDark || colorMode === 'dark' + // 从专辑封面提取颜色 + useMemo( + async () => { + if (cover !== './cover.svg') { + const colors = await extractColors(cover) + const lightColors = colors.filter(color => color.lightness < 0.7) + const darkColors = colors.filter(color => color.lightness > 0.5) + if (prefersDarkMode && darkColors.length > 0) + updateCoverColor(darkColors[0].hex) + else if (!prefersDarkMode && lightColors.length > 0) + updateCoverColor(lightColors[0].hex) + } + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [cover, prefersDarkMode] + ) + const colors = { primary: CoverThemeColor ? coverColor : prefersDarkMode ? '#df7ef9' : '#8e24aa', } diff --git a/src/hooks/ui/useThemeColor.ts b/src/hooks/ui/useThemeColor.ts index 7b32602..8e744dc 100644 --- a/src/hooks/ui/useThemeColor.ts +++ b/src/hooks/ui/useThemeColor.ts @@ -1,6 +1,8 @@ import { useEffect } from 'react' import useUiStore from '../../store/useUiStore' import useTheme from './useTheme' +import { blendHex } from '@/utils' +import { useMediaQuery } from '@mui/material' const useThemeColor = () => { const [ @@ -19,6 +21,8 @@ const useThemeColor = () => { const theme = useTheme() + const windowControlsOverlayOpen = useMediaQuery('(display-mode: window-controls-overlay)') + useEffect( () => { const themeColorLight = document.getElementById('themeColorLight') as HTMLMetaElement @@ -34,12 +38,14 @@ const useThemeColor = () => { themeColorDark.content = '#1e1e1e' } else if (audioViewIsShow && audioViewTheme === 'modern') { - themeColorLight.content = coverColor - themeColorDark.content = coverColor + + const color = blendHex(`${theme.palette.background.default}`, windowControlsOverlayOpen ? `${coverColor}30` : `${coverColor}33`) + themeColorLight.content = color + themeColorDark.content = color } } }, - [audioViewIsShow, audioViewTheme, coverColor, theme.palette.background.default, videoViewIsShow] + [audioViewIsShow, audioViewTheme, coverColor, theme.palette.background.default, videoViewIsShow, windowControlsOverlayOpen] ) } diff --git a/src/locales/en.json b/src/locales/en.json index 5d7c3b0..b312e9d 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -15,7 +15,7 @@ "account": "Account", "signIn": "Sign in", "signOut": "Sign out", - "signInAlert": "Please sign in to your Microsoft account to view your files" + "signInAlert": "Please use Microsoft account authorization to log in" }, "playlist": { "newPlaylist": "New Playlist", diff --git a/src/locales/zh-CN.json b/src/locales/zh-CN.json index 60662d4..9f5cab9 100644 --- a/src/locales/zh-CN.json +++ b/src/locales/zh-CN.json @@ -12,10 +12,10 @@ "openSourceDependencies": "开源库" }, "account": { - "account": "账号", + "account": "账户", "signIn": "登录", "signOut": "注销", - "signInAlert": "请登录微软账户以查看你的文件" + "signInAlert": "请使用微软账户授权登录" }, "playlist": { "newPlaylist": "新播放列表", diff --git a/src/main.tsx b/src/main.tsx index f9a52a5..84e4bcd 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -10,7 +10,7 @@ import '@fontsource/roboto/400.css' import '@fontsource/roboto/500.css' import '@fontsource/roboto/700.css' import 'react-virtualized/styles.css' -import router from './routers' +import router from './router' import './i18n' const msalInstance = new PublicClientApplication(msalConfig) diff --git a/src/pages/SignIn.tsx b/src/pages/LogIn.tsx similarity index 70% rename from src/pages/SignIn.tsx rename to src/pages/LogIn.tsx index 92f1bcd..e367146 100644 --- a/src/pages/SignIn.tsx +++ b/src/pages/LogIn.tsx @@ -1,15 +1,16 @@ -import { Button, Container, Link, Typography } from '@mui/material' +import { Button, Container, IconButton, Link, Typography } from '@mui/material' +import GitHubIcon from '@mui/icons-material/GitHub' import { useTranslation } from 'react-i18next' import useUser from '../hooks/graph/useUser' -const SignIn = () => { +const LogIn = () => { const { t } = useTranslation() const { login } = useUser() return ( { padding: '1rem', textAlign: 'center', }}> -
{/* Don't delete this */}
+ + +
{t('account.signInAlert')} @@ -31,4 +34,4 @@ const SignIn = () => { ) } -export default SignIn \ No newline at end of file +export default LogIn \ No newline at end of file diff --git a/src/pages/NavBar.tsx b/src/pages/NavBar.tsx index 00f62a3..cca3215 100644 --- a/src/pages/NavBar.tsx +++ b/src/pages/NavBar.tsx @@ -1,15 +1,14 @@ -import { Box, Typography, Link, Container, IconButton } from '@mui/material' -import GitHubIcon from '@mui/icons-material/GitHub' +import { Box, Typography, Container, IconButton, useMediaQuery } from '@mui/material' import MenuRoundedIcon from '@mui/icons-material/MenuRounded' -import { shallow } from 'zustand/shallow' import useUiStore from '../store/useUiStore' -import { AccountInfo } from '@azure/msal-browser' +import useUser from '@/hooks/graph/useUser' -const NavBar = ({ accounts }: { accounts: AccountInfo[] }) => { +const NavBar = () => { + const { account } = useUser() const [mobileSideBarOpen, updateMobileSideBarOpen] = useUiStore( - (state) => [state.mobileSideBarOpen, state.updateMobileSideBarOpen], - shallow + (state) => [state.mobileSideBarOpen, state.updateMobileSideBarOpen] ) + const windowControlsOverlayOpen = useMediaQuery('(display-mode: window-controls-overlay)') return ( { disableGutters={true} sx={{ display: 'flex', - justifyContent: 'space-between', + justifyContent: 'flex-start', alignItems: 'center', height: '100%', - px: { xs: '0.5rem', sm: 'calc(env(titlebar-area-height, 1.25rem) - env(titlebar-area-height, 0rem) + 0.25rem)' }, + px: { xs: '0.5rem', sm: windowControlsOverlayOpen ? '0.25rem' : '1.5rem' }, py: 'calc(env(titlebar-area-height, 0.5rem) - env(titlebar-area-height, 0rem) + 0.25rem)', }} > - - { - (accounts.length !== 0) && - updateMobileSideBarOpen(!mobileSideBarOpen)} - sx={{ display: { xs: '', sm: 'none' } }} - className='app-region-no-drag' - > - - - } - logo - updateMobileSideBarOpen(!mobileSideBarOpen)} + sx={{ display: { xs: '', sm: 'none' } }} + className='app-region-no-drag' > - OMP - - -
- { - (accounts.length == 0) && - - - - } -
+ + + } + logo + + OMP +
) diff --git a/src/pages/Player/Audio/Audio.tsx b/src/pages/Player/Audio/Audio.tsx index 6ef8a0d..80ebf0d 100644 --- a/src/pages/Player/Audio/Audio.tsx +++ b/src/pages/Player/Audio/Audio.tsx @@ -1,8 +1,6 @@ import { useTheme } from '@mui/material' -import usePlayerStore from '@/store/usePlayerStore' import useUiStore from '@/store/useUiStore' import { useMemo, useRef } from 'react' -import { extractColors } from 'extract-colors' import Classic from './Classic' import Modern from './Modern' import { animated, useSpring } from '@react-spring/web' @@ -16,26 +14,14 @@ const Audio = ({ player }: { player: HTMLVideoElement | null }) => { audioViewIsShow, audioViewTheme, updateAudioViewIsShow, - updateCoverColor, ] = useUiStore( (state) => [ state.audioViewIsShow, state.audioViewTheme, state.updateAudioViewIsShow, - state.updateCoverColor, ] ) - const [cover] = usePlayerStore((state) => [state.cover]) - - // 从专辑封面提取颜色 - useMemo( - () => (cover !== './cover.webp') && - extractColors(cover).then(color => updateCoverColor(color[0].hex)).catch(console.error), - // eslint-disable-next-line react-hooks/exhaustive-deps - [cover] - ) - const topRef = useRef(0) const [{ top, borderRadius }, api] = useSpring(() => ({ from: { @@ -69,9 +55,13 @@ const Audio = ({ player }: { player: HTMLVideoElement | null }) => { [audioViewIsShow] ) - const bind = useDrag(({ down, movement: [, my], last }) => { + const bind = useDrag(({ down, movement: [, my], last, event }) => { + if ('pointerType' in event && event.pointerType !== 'touch') { + return + } + if (last) { - if (my > window.innerHeight * 0.5) { + if (my > 40) { updateAudioViewIsShow(false) } else { topRef.current = 0 diff --git a/src/pages/Player/Audio/Classic.tsx b/src/pages/Player/Audio/Classic.tsx index 63b81a1..2f5327e 100644 --- a/src/pages/Player/Audio/Classic.tsx +++ b/src/pages/Player/Audio/Classic.tsx @@ -75,7 +75,7 @@ const Classic = ({ player, styles }: { player: HTMLVideoElement | null, styles: width: '100%', height: '100dvh', background: - (!backgroundIsShow || cover === './cover.webp') + (!backgroundIsShow || cover === './cover.svg') ? `linear-gradient(rgba(50, 50, 50, 0.6), ${coverColor}bb), #000` : `linear-gradient(rgba(50, 50, 50, 0.3), rgba(50, 50, 50, 0.3) ), url(${cover}) no-repeat center, #000`, backgroundSize: 'cover', @@ -85,7 +85,7 @@ const Classic = ({ player, styles }: { player: HTMLVideoElement | null, styles: }} > - + Cover diff --git a/src/pages/Player/PlayerControl.tsx b/src/pages/Player/PlayerControl.tsx index a4bbae6..361a8a4 100644 --- a/src/pages/Player/PlayerControl.tsx +++ b/src/pages/Player/PlayerControl.tsx @@ -163,7 +163,6 @@ const PlayerControl = ({ player }: { player: HTMLVideoElement | null }) => { height: '100%', objectFit: 'cover', borderRadius: '0.5rem', - // filter: (theme.palette.mode === 'light' && cover === './cover.webp') ? 'invert(1)' : 'none', }} /> } diff --git a/src/pages/Setting.tsx b/src/pages/Setting.tsx index 966e4a2..6b31be5 100644 --- a/src/pages/Setting.tsx +++ b/src/pages/Setting.tsx @@ -16,8 +16,7 @@ const ListItemTitle = ({ title }: { title: string }) => { } const Setting = () => { - const { accounts, logout } = useUser() - const account = accounts[0] + const { account, login, logout } = useUser() const { t } = useTranslation() const { clearLocalMetaData } = useLocalMetaDataStore() @@ -43,15 +42,20 @@ const Setting = () => { logout()}> - {t('account.signOut')} - + account + ? + : } > - {account.name && account.name.split(' ')[0]} + {account && {account.name?.split(' ')[0]}} - + { + account + ? + : + } + diff --git a/src/routers.tsx b/src/router.tsx similarity index 100% rename from src/routers.tsx rename to src/router.tsx diff --git a/src/store/usePlayerStore.ts b/src/store/usePlayerStore.ts index 91013c6..09a6f9e 100644 --- a/src/store/usePlayerStore.ts +++ b/src/store/usePlayerStore.ts @@ -7,7 +7,7 @@ const usePlayerStore = createWithEqualityFn((set) = metadataUpdate: false, playStatu: 'paused', isLoading: false, - cover: './cover.webp', + cover: './cover.svg', currentTime: 0, duration: 0, updateCurrentMetaData: (currentMetaData) => set(() => ({ currentMetaData: currentMetaData })), diff --git a/src/store/useUiStore.ts b/src/store/useUiStore.ts index aa73572..5142301 100644 --- a/src/store/useUiStore.ts +++ b/src/store/useUiStore.ts @@ -17,7 +17,7 @@ const useUiStore = createWithEqualityFn()( backgroundIsShow: true, shuffle: false, repeat: 'off', - coverColor: '#ffffff', + coverColor: '#8e24aa', CoverThemeColor: false, colorMode: 'auto', display: 'list', diff --git a/src/utils.ts b/src/utils.ts index 7464b64..84ed888 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -71,4 +71,22 @@ export const fetchJson = async (url: string) => { } catch (error) { console.error(error) } -} \ No newline at end of file +} + +export const hexToRgba = (hex: string) => { + const r = parseInt(hex.slice(1, 3), 16) + const g = parseInt(hex.slice(3, 5), 16) + const b = parseInt(hex.slice(5, 7), 16) + const a = hex.length > 7 ? parseInt(hex.slice(7, 9), 16) / 255 : 1 + return [r, g, b, a] +} + +export const blendHex = (colorHex1: string, colorHex2: string) => { + const colorRGBA1 = hexToRgba(colorHex1) + const colorRGBA2 = hexToRgba(colorHex2) + const red = colorRGBA1[0] * (1 - colorRGBA2[3]) + colorRGBA2[0] * colorRGBA2[3] + const green = colorRGBA1[1] * (1 - colorRGBA2[3]) + colorRGBA2[1] * colorRGBA2[3] + const blue = colorRGBA1[2] * (1 - colorRGBA2[3]) + colorRGBA2[2] * colorRGBA2[3] + const color = [Math.round(red), Math.round(green), Math.round(blue)] + return `rgb(${color.join(', ')})` +}