[feat]: Remote Improvements

- Make the interface a bit more responsive (menu, button changes)
- Enable disabling screen lock (needs better icon)
- Improve updated rating
This commit is contained in:
Kendall Garner 2023-11-04 20:27:24 -07:00
parent adc09e6bbf
commit a784fbd060
No known key found for this signature in database
GPG Key ID: 18D2767419676C87
13 changed files with 380 additions and 173 deletions

45
package-lock.json generated
View File

@ -5,7 +5,6 @@
"requires": true,
"packages": {
"": {
"name": "feishin",
"version": "0.5.1",
"hasInstallScript": true,
"license": "GPL-3.0",
@ -52,6 +51,7 @@
"nanoid": "^3.3.3",
"net": "^1.0.2",
"node-mpv": "github:jeffvli/Node-MPV",
"nosleep.js": "^0.12.0",
"overlayscrollbars": "^2.2.1",
"overlayscrollbars-react": "^0.5.1",
"react": "^18.2.0",
@ -219,6 +219,8 @@
"integrity": "sha512-prtg5f6zCERIaECeTZzd2fMtVjlfjhUcO+fBLQ6DXXdq5FljN+excVitJ2nogsusdf31LeqkjAfXZ7Xq+HmN8g==",
"dependencies": {
"@jridgewell/trace-mapping": "^0.3.17",
"@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents.3",
"chokidar": "^3.4.0",
"commander": "^4.0.1",
"convert-source-map": "^1.1.0",
"fs-readdir-recursive": "^1.1.0",
@ -2384,6 +2386,7 @@
"debug": "^4.1.1",
"env-paths": "^2.2.0",
"fs-extra": "^8.1.0",
"global-agent": "^3.0.0",
"got": "^11.8.5",
"progress": "^2.0.3",
"semver": "^6.2.0",
@ -2439,6 +2442,7 @@
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
"dev": true,
"dependencies": {
"graceful-fs": "^4.1.6",
"universalify": "^2.0.0"
},
"optionalDependencies": {
@ -2507,6 +2511,7 @@
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
"dev": true,
"dependencies": {
"graceful-fs": "^4.1.6",
"universalify": "^2.0.0"
},
"optionalDependencies": {
@ -2698,6 +2703,7 @@
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
"dev": true,
"dependencies": {
"graceful-fs": "^4.1.6",
"universalify": "^2.0.0"
},
"optionalDependencies": {
@ -2758,6 +2764,7 @@
"integrity": "sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==",
"dev": true,
"dependencies": {
"encoding": "^0.1.13",
"minipass": "^3.1.6",
"minipass-sized": "^1.0.3",
"minizlib": "^2.1.2"
@ -2945,6 +2952,7 @@
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
"dev": true,
"dependencies": {
"graceful-fs": "^4.1.6",
"universalify": "^2.0.0"
},
"optionalDependencies": {
@ -3729,6 +3737,7 @@
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
"dev": true,
"dependencies": {
"graceful-fs": "^4.1.6",
"universalify": "^2.0.0"
},
"optionalDependencies": {
@ -4504,6 +4513,7 @@
"dependencies": {
"camelcase": "^5.3.1",
"loader-utils": "^1.4.2",
"prettier": "*",
"schema-utils": "^2.0.1"
},
"optionalDependencies": {
@ -6031,6 +6041,7 @@
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
"dev": true,
"dependencies": {
"graceful-fs": "^4.1.6",
"universalify": "^2.0.0"
},
"optionalDependencies": {
@ -6943,6 +6954,7 @@
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
"dev": true,
"dependencies": {
"graceful-fs": "^4.1.6",
"universalify": "^2.0.0"
},
"optionalDependencies": {
@ -7199,6 +7211,7 @@
"dependencies": {
"anymatch": "~3.1.2",
"braces": "~3.0.2",
"fsevents": "~2.3.2",
"glob-parent": "~5.1.2",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",
@ -8637,6 +8650,7 @@
"app-builder-lib": "24.6.3",
"builder-util": "24.5.0",
"builder-util-runtime": "9.2.1",
"dmg-license": "^1.0.11",
"fs-extra": "^10.1.0",
"iconv-lite": "^0.6.2",
"js-yaml": "^4.1.0"
@ -8678,6 +8692,7 @@
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
"dev": true,
"dependencies": {
"graceful-fs": "^4.1.6",
"universalify": "^2.0.0"
},
"optionalDependencies": {
@ -9010,6 +9025,7 @@
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
"dev": true,
"dependencies": {
"graceful-fs": "^4.1.6",
"universalify": "^2.0.0"
},
"optionalDependencies": {
@ -9137,6 +9153,7 @@
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
"dev": true,
"dependencies": {
"graceful-fs": "^4.1.6",
"universalify": "^2.0.0"
},
"optionalDependencies": {
@ -9200,6 +9217,7 @@
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
"dev": true,
"dependencies": {
"graceful-fs": "^4.1.6",
"universalify": "^2.0.0"
},
"optionalDependencies": {
@ -9276,6 +9294,7 @@
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
"dependencies": {
"graceful-fs": "^4.1.6",
"universalify": "^2.0.0"
},
"optionalDependencies": {
@ -9581,7 +9600,8 @@
"esprima": "^4.0.1",
"estraverse": "^5.2.0",
"esutils": "^2.0.2",
"optionator": "^0.8.1"
"optionator": "^0.8.1",
"source-map": "~0.6.1"
},
"bin": {
"escodegen": "bin/escodegen.js",
@ -10752,6 +10772,7 @@
"integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==",
"dev": true,
"dependencies": {
"@types/yauzl": "^2.9.1",
"debug": "^4.1.1",
"get-stream": "^5.1.0",
"yauzl": "^2.10.0"
@ -11106,6 +11127,7 @@
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-10.13.0.tgz",
"integrity": "sha512-xKhw9VCizmwEHbopOfluaoVunGHSZyMztGbTvsgOYqCjaKu6qtlwWY1J+6GhL41NY1P157JgEikjDm67XCFnvQ==",
"dependencies": {
"@emotion/is-prop-valid": "^0.8.2",
"tslib": "^2.4.0"
},
"optionalDependencies": {
@ -12247,6 +12269,7 @@
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
"dev": true,
"dependencies": {
"graceful-fs": "^4.1.6",
"universalify": "^2.0.0"
},
"optionalDependencies": {
@ -13396,6 +13419,7 @@
"@types/node": "*",
"anymatch": "^3.0.3",
"fb-watchman": "^2.0.0",
"fsevents": "^2.3.2",
"graceful-fs": "^4.2.9",
"jest-regex-util": "^27.5.1",
"jest-serializer": "^27.5.1",
@ -13942,6 +13966,9 @@
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
"dev": true,
"dependencies": {
"graceful-fs": "^4.1.6"
},
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
@ -15229,6 +15256,11 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/nosleep.js": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/nosleep.js/-/nosleep.js-0.12.0.tgz",
"integrity": "sha512-9d1HbpKLh3sdWlhXMhU6MMH+wQzKkrgfRkYV0EBdvt99YJfj0ilCJrWRDYG2130Tm4GXbEoTCx5b34JSaP+HhA=="
},
"node_modules/now-and-later": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-2.0.1.tgz",
@ -18875,6 +18907,9 @@
"resolved": "https://registry.npmjs.org/stylelint-config-css-modules/-/stylelint-config-css-modules-4.3.0.tgz",
"integrity": "sha512-KvIvhzzjpcjHKkGSPkQCueoZJHrb6sZ6GCtrQb/J45HQTBVwJAeNYXaSZZK6ZQOC7NxJ4v5kLxpQLDiCK6zzgw==",
"dev": true,
"dependencies": {
"stylelint-scss": "^5.0.0 || ^6.0.0"
},
"optionalDependencies": {
"stylelint-scss": "^5.0.0 || ^6.0.0"
},
@ -19432,6 +19467,7 @@
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
"dev": true,
"dependencies": {
"graceful-fs": "^4.1.6",
"universalify": "^2.0.0"
},
"optionalDependencies": {
@ -32716,6 +32752,11 @@
"integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==",
"dev": true
},
"nosleep.js": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/nosleep.js/-/nosleep.js-0.12.0.tgz",
"integrity": "sha512-9d1HbpKLh3sdWlhXMhU6MMH+wQzKkrgfRkYV0EBdvt99YJfj0ilCJrWRDYG2130Tm4GXbEoTCx5b34JSaP+HhA=="
},
"now-and-later": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-2.0.1.tgz",

View File

@ -331,6 +331,7 @@
"nanoid": "^3.3.3",
"net": "^1.0.2",
"node-mpv": "github:jeffvli/Node-MPV",
"nosleep.js": "^0.12.0",
"overlayscrollbars": "^2.2.1",
"overlayscrollbars-react": "^0.5.1",
"react": "^18.2.0",

View File

@ -3,6 +3,7 @@ import { MantineProvider } from '@mantine/core';
import './styles/global.scss';
import { useIsDark, useReconnect } from '/@/remote/store';
import { Shell } from '/@/remote/components/shell';
import { AppTheme } from '/@/renderer/themes/types';
export const App = () => {
const isDark = useIsDark();
@ -12,6 +13,11 @@ export const App = () => {
reconnect();
}, [reconnect]);
useEffect(() => {
const targetTheme: AppTheme = isDark ? AppTheme.DEFAULT_DARK : AppTheme.DEFAULT_LIGHT;
document.body.setAttribute('data-theme', targetTheme);
}, [isDark]);
return (
<MantineProvider
withGlobalStyles

View File

@ -8,10 +8,7 @@ export const ImageButton = () => {
return (
<RemoteButton
mr={5}
size="xl"
tooltip={showImage ? 'Hide Image' : 'Show Image'}
variant="default"
onClick={() => toggleImage()}
>
{showImage ? <CiImageOff size={30} /> : <CiImageOn size={30} />}

View File

@ -9,10 +9,7 @@ export const ReconnectButton = () => {
return (
<RemoteButton
$active={!connected}
mr={5}
size="xl"
tooltip={connected ? 'Reconnect' : 'Not connected. Reconnect.'}
variant="default"
onClick={() => reconnect()}
>
<RiRestartLine size={30} />

View File

@ -12,7 +12,7 @@ interface StyledButtonProps extends MantineButtonProps {
}
export interface ButtonProps extends StyledButtonProps {
tooltip: string;
tooltip?: string;
}
const StyledButton = styled(Button)<StyledButtonProps>`
@ -37,19 +37,29 @@ const StyledButton = styled(Button)<StyledButtonProps>`
export const RemoteButton = forwardRef<HTMLButtonElement, ButtonProps>(
({ children, tooltip, ...props }: ButtonProps, ref) => {
return (
<Tooltip
withinPortal
label={tooltip}
const button = (
<StyledButton
fullWidth
size="xl"
variant="default"
{...props}
ref={ref}
>
<StyledButton
{...props}
ref={ref}
>
{children}
</StyledButton>
</Tooltip>
{children}
</StyledButton>
);
if (tooltip) {
return (
<Tooltip
withinPortal
events={{ focus: true, hover: true, touch: true }}
label={tooltip}
>
{button}
</Tooltip>
);
}
return button;
},
);

View File

@ -0,0 +1,18 @@
import { RemoteButton } from '/@/remote/components/buttons/remote-button';
import { LuMonitor, LuMonitorOff } from 'react-icons/lu';
import { useNoSleepContext } from '/@/remote/context/nosleep-context';
import { useToggleNoSleep } from '/@/remote/hooks/use-toggle-no-sleep';
export const SleepButton = () => {
const { enabled } = useNoSleepContext();
const toggleNoSleep = useToggleNoSleep();
return (
<RemoteButton
tooltip={enabled ? 'Enable screen lock' : 'Disable screen lock'}
onClick={toggleNoSleep}
>
{enabled ? <LuMonitorOff size={30} /> : <LuMonitor size={30} />}
</RemoteButton>
);
};

View File

@ -1,24 +1,14 @@
import { useIsDark, useToggleDark } from '/@/remote/store';
import { RiMoonLine, RiSunLine } from 'react-icons/ri';
import { RemoteButton } from '/@/remote/components/buttons/remote-button';
import { AppTheme } from '/@/renderer/themes/types';
import { useEffect } from 'react';
export const ThemeButton = () => {
const isDark = useIsDark();
const toggleDark = useToggleDark();
useEffect(() => {
const targetTheme: AppTheme = isDark ? AppTheme.DEFAULT_DARK : AppTheme.DEFAULT_LIGHT;
document.body.setAttribute('data-theme', targetTheme);
}, [isDark]);
return (
<RemoteButton
mr={5}
size="xl"
tooltip="Toggle Theme"
variant="default"
onClick={() => toggleDark()}
>
{isDark ? <RiSunLine size={30} /> : <RiMoonLine size={30} />}

View File

@ -0,0 +1,49 @@
import { CiImageOff, CiImageOn } from 'react-icons/ci';
import { LuMonitor, LuMonitorOff } from 'react-icons/lu';
import { RiMenuFill, RiMoonLine, RiSunLine } from 'react-icons/ri';
import { RemoteButton } from '/@/remote/components/buttons/remote-button';
import { DropdownMenu } from '/@/renderer/components/dropdown-menu';
import { useIsDark, useShowImage, useToggleDark, useToggleShowImage } from '/@/remote/store';
import { useNoSleepContext } from '/@/remote/context/nosleep-context';
import { useToggleNoSleep } from '/@/remote/hooks/use-toggle-no-sleep';
export const ResponsiveMenu = () => {
const showImage = useShowImage();
const toggleImage = useToggleShowImage();
const isDark = useIsDark();
const toggleDark = useToggleDark();
const { enabled } = useNoSleepContext();
const toggleNoSleep = useToggleNoSleep();
return (
<DropdownMenu closeOnItemClick={false}>
<DropdownMenu.Target>
<RemoteButton>
<RiMenuFill size={30} />
</RemoteButton>
</DropdownMenu.Target>
<DropdownMenu.Dropdown>
<DropdownMenu.Item
icon={showImage ? <CiImageOff size={30} /> : <CiImageOn size={30} />}
onClick={toggleImage}
>
{showImage ? 'Hide Image' : 'Show Image'}
</DropdownMenu.Item>
<DropdownMenu.Item
icon={isDark ? <RiSunLine size={30} /> : <RiMoonLine size={30} />}
onClick={toggleDark}
>
Toggle Theme
</DropdownMenu.Item>
<DropdownMenu.Item
icon={enabled ? <LuMonitorOff size={30} /> : <LuMonitor size={30} />}
onClick={toggleNoSleep}
>
{enabled ? 'Enable screen lock' : 'Disable screen lock'}
</DropdownMenu.Item>
</DropdownMenu.Dropdown>
</DropdownMenu>
);
};

View File

@ -1,9 +1,8 @@
import { useCallback } from 'react';
import { Group, Image, Text, Title } from '@mantine/core';
import { Center, Grid, Group, Image, MediaQuery, Text, Title } from '@mantine/core';
import { useInfo, useSend, useShowImage } from '/@/remote/store';
import { RemoteButton } from '/@/remote/components/buttons/remote-button';
import formatDuration from 'format-duration';
import debounce from 'lodash/debounce';
import {
RiHeartLine,
RiPauseFill,
@ -17,7 +16,6 @@ import {
} from 'react-icons/ri';
import { PlayerRepeat, PlayerStatus } from '/@/renderer/types';
import { WrapperSlider } from '/@/remote/components/wrapped-slider';
import { Tooltip } from '/@/renderer/components/tooltip';
import { Rating } from '/@/renderer/components';
export const RemoteContainer = () => {
@ -34,8 +32,6 @@ export const RemoteContainer = () => {
[send, id],
);
const debouncedSetRating = debounce(setRating, 400);
return (
<>
{song && (
@ -56,101 +52,125 @@ export const RemoteContainer = () => {
</Group>
</>
)}
<Group
<Grid
grow
spacing={0}
align="center"
gutter={0}
>
<RemoteButton
tooltip="Previous track"
variant="default"
onClick={() => send({ event: 'previous' })}
>
<RiSkipBackFill size={25} />
</RemoteButton>
<RemoteButton
tooltip={status === PlayerStatus.PLAYING ? 'Pause' : 'Play'}
variant="default"
onClick={() => {
if (status === PlayerStatus.PLAYING) {
send({ event: 'pause' });
} else if (status === PlayerStatus.PAUSED) {
send({ event: 'play' });
}
}}
>
{status === PlayerStatus.PLAYING ? (
<RiPauseFill size={25} />
) : (
<RiPlayFill size={25} />
)}
</RemoteButton>
<RemoteButton
tooltip="Next track"
variant="default"
onClick={() => send({ event: 'next' })}
>
<RiSkipForwardFill size={25} />
</RemoteButton>
</Group>
<Group
grow
spacing={0}
>
<RemoteButton
$active={shuffle || false}
tooltip={shuffle ? 'Shuffle tracks' : 'Shuffle disabled'}
variant="default"
onClick={() => send({ event: 'shuffle' })}
>
<RiShuffleFill size={25} />
</RemoteButton>
<RemoteButton
$active={repeat !== undefined && repeat !== PlayerRepeat.NONE}
tooltip={`Repeat ${
repeat === PlayerRepeat.ONE
? 'One'
: repeat === PlayerRepeat.ALL
? 'all'
: 'none'
}`}
variant="default"
onClick={() => send({ event: 'repeat' })}
>
{repeat === undefined || repeat === PlayerRepeat.ONE ? (
<RiRepeatOneLine size={25} />
) : (
<RiRepeat2Line size={25} />
)}
</RemoteButton>
<RemoteButton
$active={song?.userFavorite}
disabled={!song}
tooltip={song?.userFavorite ? 'Unfavorite' : 'Favorite'}
variant="default"
onClick={() => {
if (!id) return;
<Grid.Col span={4}>
<RemoteButton
tooltip="Previous track"
onClick={() => send({ event: 'previous' })}
>
<RiSkipBackFill size={25} />
</RemoteButton>
</Grid.Col>
<Grid.Col span={4}>
<RemoteButton
tooltip={status === PlayerStatus.PLAYING ? 'Pause' : 'Play'}
onClick={() => {
if (status === PlayerStatus.PLAYING) {
send({ event: 'pause' });
} else if (status === PlayerStatus.PAUSED) {
send({ event: 'play' });
}
}}
>
{status === PlayerStatus.PLAYING ? (
<RiPauseFill size={25} />
) : (
<RiPlayFill size={25} />
)}
</RemoteButton>
</Grid.Col>
send({ event: 'favorite', favorite: !song.userFavorite, id });
}}
<Grid.Col span={4}>
<RemoteButton
tooltip="Next track"
onClick={() => send({ event: 'next' })}
>
<RiSkipForwardFill size={25} />
</RemoteButton>
</Grid.Col>
</Grid>
<Grid
grow
align="center"
gutter={0}
>
<Grid.Col
md={3}
span={4}
>
<RiHeartLine size={25} />
</RemoteButton>
<RemoteButton
$active={shuffle || false}
tooltip={shuffle ? 'Shuffle tracks' : 'Shuffle disabled'}
onClick={() => send({ event: 'shuffle' })}
>
<RiShuffleFill size={25} />
</RemoteButton>
</Grid.Col>
<Grid.Col
md={3}
span={4}
>
<RemoteButton
$active={repeat !== undefined && repeat !== PlayerRepeat.NONE}
tooltip={`Repeat ${
repeat === PlayerRepeat.ONE
? 'One'
: repeat === PlayerRepeat.ALL
? 'all'
: 'none'
}`}
onClick={() => send({ event: 'repeat' })}
>
{repeat === undefined || repeat === PlayerRepeat.ONE ? (
<RiRepeatOneLine size={25} />
) : (
<RiRepeat2Line size={25} />
)}
</RemoteButton>
</Grid.Col>
<Grid.Col
md={3}
span={4}
>
<RemoteButton
$active={song?.userFavorite}
disabled={!song}
tooltip={song?.userFavorite ? 'Unfavorite' : 'Favorite'}
onClick={() => {
if (!id) return;
send({ event: 'favorite', favorite: !song.userFavorite, id });
}}
>
<RiHeartLine size={25} />
</RemoteButton>
</Grid.Col>
{(song?.serverType === 'navidrome' || song?.serverType === 'subsonic') && (
<div style={{ margin: 'auto' }}>
<Tooltip
label="Double click to clear"
openDelay={1000}
<MediaQuery
smallerThan="md"
styles={{ marginTop: 10 }}
>
<Grid.Col
md={3}
span={4}
>
<Rating
sx={{ margin: 'auto' }}
value={song.userRating ?? 0}
onChange={debouncedSetRating}
onDoubleClick={() => debouncedSetRating(0)}
/>
</Tooltip>
</div>
<Center>
<Rating
size="xl"
value={song.userRating ?? 0}
onChange={setRating}
/>
</Center>
</Grid.Col>
</MediaQuery>
)}
</Group>
</Grid>
<WrapperSlider
leftLabel={<RiVolumeUpFill size={20} />}
max={100}

View File

@ -14,62 +14,97 @@ import { ImageButton } from '/@/remote/components/buttons/image-button';
import { RemoteContainer } from '/@/remote/components/remote-container';
import { ReconnectButton } from '/@/remote/components/buttons/reconnect-button';
import { useConnected } from '/@/remote/store';
import { NoSleepContext } from '/@/remote/context/nosleep-context';
import NoSleep from 'nosleep.js';
import { useMemo, useState } from 'react';
import { SleepButton } from '/@/remote/components/buttons/sleep-button';
import { ResponsiveMenu } from '/@/remote/components/menu';
export const Shell = () => {
const connected = useConnected();
const noSleep = useMemo(() => {
return new NoSleep();
}, []);
const [blockSleep, setBlockSleep] = useState(false);
const noSleepValue = useMemo(() => {
return { enabled: blockSleep, noSleep, setEnabled: setBlockSleep };
}, [blockSleep, noSleep]);
return (
<AppShell
header={
<Header height={60}>
<Grid>
<Grid.Col span="auto">
<div>
<Image
fit="contain"
height={60}
src="/favicon.ico"
width={60}
/>
</div>
</Grid.Col>
<MediaQuery
smallerThan="sm"
styles={{ display: 'none' }}
>
<Grid.Col
sm={6}
xs={0}
>
<Title ta="center">Feishin Remote</Title>
<NoSleepContext.Provider value={noSleepValue}>
<AppShell
header={
<Header height={60}>
<Grid>
<Grid.Col span="auto">
<div>
<Image
fit="contain"
height={60}
src="/favicon.ico"
width={60}
/>
</div>
</Grid.Col>
</MediaQuery>
<Grid.Col span="auto">
<Flex
direction="row"
justify="right"
<MediaQuery
smallerThan="sm"
styles={{ display: 'none' }}
>
<ReconnectButton />
<ImageButton />
<ThemeButton />
</Flex>
</Grid.Col>
</Grid>
</Header>
}
padding="md"
>
<Container>
{connected ? (
<RemoteContainer />
) : (
<Skeleton
height={300}
width="100%"
/>
)}
</Container>
</AppShell>
<Grid.Col
sm={6}
xs={0}
>
<Title ta="center">Feishin Remote</Title>
</Grid.Col>
</MediaQuery>
<MediaQuery
largerThan="md"
styles={{ display: 'none' }}
>
<Grid.Col span="auto">
<Flex
direction="row"
justify="right"
>
<ReconnectButton />
<ResponsiveMenu />
</Flex>
</Grid.Col>
</MediaQuery>
<MediaQuery
smallerThan="md"
styles={{ display: 'none' }}
>
<Grid.Col span="auto">
<Flex
direction="row"
justify="right"
>
<ReconnectButton />
<ImageButton />
<ThemeButton />
<SleepButton />
</Flex>
</Grid.Col>
</MediaQuery>
</Grid>
</Header>
}
padding="md"
>
<Container>
{connected ? (
<RemoteContainer />
) : (
<Skeleton
height={300}
width="100%"
/>
)}
</Container>
</AppShell>
</NoSleepContext.Provider>
);
};

View File

@ -0,0 +1,15 @@
import { createContext, useContext } from 'react';
import NoSleep from 'nosleep.js';
export const NoSleepContext = createContext<{
enabled: boolean;
noSleep?: NoSleep;
setEnabled?: (val: boolean) => void;
}>({
enabled: false,
});
export const useNoSleepContext = () => {
const ctxValue = useContext(NoSleepContext);
return ctxValue;
};

View File

@ -0,0 +1,28 @@
import { useCallback } from 'react';
import { useNoSleepContext } from '/@/remote/context/nosleep-context';
import { toast } from '/@/renderer/components';
export const useToggleNoSleep = () => {
const { noSleep, enabled, setEnabled } = useNoSleepContext();
const toggle = useCallback(async () => {
if (!noSleep) return;
if (enabled) {
noSleep.disable();
setEnabled!(false);
} else {
try {
await noSleep.enable();
setEnabled!(true);
} catch (error) {
toast.error({
message: (error as Error).message,
title: 'Failed to disable screen lock',
});
}
}
}, [enabled, noSleep, setEnabled]);
return toggle;
};