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

View File

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

View File

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

View File

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

View File

@ -12,7 +12,7 @@ interface StyledButtonProps extends MantineButtonProps {
} }
export interface ButtonProps extends StyledButtonProps { export interface ButtonProps extends StyledButtonProps {
tooltip: string; tooltip?: string;
} }
const StyledButton = styled(Button)<StyledButtonProps>` const StyledButton = styled(Button)<StyledButtonProps>`
@ -37,19 +37,29 @@ const StyledButton = styled(Button)<StyledButtonProps>`
export const RemoteButton = forwardRef<HTMLButtonElement, ButtonProps>( export const RemoteButton = forwardRef<HTMLButtonElement, ButtonProps>(
({ children, tooltip, ...props }: ButtonProps, ref) => { ({ children, tooltip, ...props }: ButtonProps, ref) => {
return ( const button = (
<Tooltip
withinPortal
label={tooltip}
>
<StyledButton <StyledButton
fullWidth
size="xl"
variant="default"
{...props} {...props}
ref={ref} ref={ref}
> >
{children} {children}
</StyledButton> </StyledButton>
);
if (tooltip) {
return (
<Tooltip
withinPortal
events={{ focus: true, hover: true, touch: true }}
label={tooltip}
>
{button}
</Tooltip> </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 { useIsDark, useToggleDark } from '/@/remote/store';
import { RiMoonLine, RiSunLine } from 'react-icons/ri'; import { RiMoonLine, RiSunLine } from 'react-icons/ri';
import { RemoteButton } from '/@/remote/components/buttons/remote-button'; import { RemoteButton } from '/@/remote/components/buttons/remote-button';
import { AppTheme } from '/@/renderer/themes/types';
import { useEffect } from 'react';
export const ThemeButton = () => { export const ThemeButton = () => {
const isDark = useIsDark(); const isDark = useIsDark();
const toggleDark = useToggleDark(); const toggleDark = useToggleDark();
useEffect(() => {
const targetTheme: AppTheme = isDark ? AppTheme.DEFAULT_DARK : AppTheme.DEFAULT_LIGHT;
document.body.setAttribute('data-theme', targetTheme);
}, [isDark]);
return ( return (
<RemoteButton <RemoteButton
mr={5}
size="xl"
tooltip="Toggle Theme" tooltip="Toggle Theme"
variant="default"
onClick={() => toggleDark()} onClick={() => toggleDark()}
> >
{isDark ? <RiSunLine size={30} /> : <RiMoonLine size={30} />} {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 { 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 { useInfo, useSend, useShowImage } from '/@/remote/store';
import { RemoteButton } from '/@/remote/components/buttons/remote-button'; import { RemoteButton } from '/@/remote/components/buttons/remote-button';
import formatDuration from 'format-duration'; import formatDuration from 'format-duration';
import debounce from 'lodash/debounce';
import { import {
RiHeartLine, RiHeartLine,
RiPauseFill, RiPauseFill,
@ -17,7 +16,6 @@ import {
} from 'react-icons/ri'; } from 'react-icons/ri';
import { PlayerRepeat, PlayerStatus } from '/@/renderer/types'; import { PlayerRepeat, PlayerStatus } from '/@/renderer/types';
import { WrapperSlider } from '/@/remote/components/wrapped-slider'; import { WrapperSlider } from '/@/remote/components/wrapped-slider';
import { Tooltip } from '/@/renderer/components/tooltip';
import { Rating } from '/@/renderer/components'; import { Rating } from '/@/renderer/components';
export const RemoteContainer = () => { export const RemoteContainer = () => {
@ -34,8 +32,6 @@ export const RemoteContainer = () => {
[send, id], [send, id],
); );
const debouncedSetRating = debounce(setRating, 400);
return ( return (
<> <>
{song && ( {song && (
@ -56,20 +52,22 @@ export const RemoteContainer = () => {
</Group> </Group>
</> </>
)} )}
<Group <Grid
grow grow
spacing={0} align="center"
gutter={0}
> >
<Grid.Col span={4}>
<RemoteButton <RemoteButton
tooltip="Previous track" tooltip="Previous track"
variant="default"
onClick={() => send({ event: 'previous' })} onClick={() => send({ event: 'previous' })}
> >
<RiSkipBackFill size={25} /> <RiSkipBackFill size={25} />
</RemoteButton> </RemoteButton>
</Grid.Col>
<Grid.Col span={4}>
<RemoteButton <RemoteButton
tooltip={status === PlayerStatus.PLAYING ? 'Pause' : 'Play'} tooltip={status === PlayerStatus.PLAYING ? 'Pause' : 'Play'}
variant="default"
onClick={() => { onClick={() => {
if (status === PlayerStatus.PLAYING) { if (status === PlayerStatus.PLAYING) {
send({ event: 'pause' }); send({ event: 'pause' });
@ -84,26 +82,38 @@ export const RemoteContainer = () => {
<RiPlayFill size={25} /> <RiPlayFill size={25} />
)} )}
</RemoteButton> </RemoteButton>
</Grid.Col>
<Grid.Col span={4}>
<RemoteButton <RemoteButton
tooltip="Next track" tooltip="Next track"
variant="default"
onClick={() => send({ event: 'next' })} onClick={() => send({ event: 'next' })}
> >
<RiSkipForwardFill size={25} /> <RiSkipForwardFill size={25} />
</RemoteButton> </RemoteButton>
</Group> </Grid.Col>
<Group </Grid>
<Grid
grow grow
spacing={0} align="center"
gutter={0}
>
<Grid.Col
md={3}
span={4}
> >
<RemoteButton <RemoteButton
$active={shuffle || false} $active={shuffle || false}
tooltip={shuffle ? 'Shuffle tracks' : 'Shuffle disabled'} tooltip={shuffle ? 'Shuffle tracks' : 'Shuffle disabled'}
variant="default"
onClick={() => send({ event: 'shuffle' })} onClick={() => send({ event: 'shuffle' })}
> >
<RiShuffleFill size={25} /> <RiShuffleFill size={25} />
</RemoteButton> </RemoteButton>
</Grid.Col>
<Grid.Col
md={3}
span={4}
>
<RemoteButton <RemoteButton
$active={repeat !== undefined && repeat !== PlayerRepeat.NONE} $active={repeat !== undefined && repeat !== PlayerRepeat.NONE}
tooltip={`Repeat ${ tooltip={`Repeat ${
@ -113,7 +123,6 @@ export const RemoteContainer = () => {
? 'all' ? 'all'
: 'none' : 'none'
}`} }`}
variant="default"
onClick={() => send({ event: 'repeat' })} onClick={() => send({ event: 'repeat' })}
> >
{repeat === undefined || repeat === PlayerRepeat.ONE ? ( {repeat === undefined || repeat === PlayerRepeat.ONE ? (
@ -122,11 +131,16 @@ export const RemoteContainer = () => {
<RiRepeat2Line size={25} /> <RiRepeat2Line size={25} />
)} )}
</RemoteButton> </RemoteButton>
</Grid.Col>
<Grid.Col
md={3}
span={4}
>
<RemoteButton <RemoteButton
$active={song?.userFavorite} $active={song?.userFavorite}
disabled={!song} disabled={!song}
tooltip={song?.userFavorite ? 'Unfavorite' : 'Favorite'} tooltip={song?.userFavorite ? 'Unfavorite' : 'Favorite'}
variant="default"
onClick={() => { onClick={() => {
if (!id) return; if (!id) return;
@ -135,22 +149,28 @@ export const RemoteContainer = () => {
> >
<RiHeartLine size={25} /> <RiHeartLine size={25} />
</RemoteButton> </RemoteButton>
</Grid.Col>
{(song?.serverType === 'navidrome' || song?.serverType === 'subsonic') && ( {(song?.serverType === 'navidrome' || song?.serverType === 'subsonic') && (
<div style={{ margin: 'auto' }}> <MediaQuery
<Tooltip smallerThan="md"
label="Double click to clear" styles={{ marginTop: 10 }}
openDelay={1000}
> >
<Grid.Col
md={3}
span={4}
>
<Center>
<Rating <Rating
sx={{ margin: 'auto' }} size="xl"
value={song.userRating ?? 0} value={song.userRating ?? 0}
onChange={debouncedSetRating} onChange={setRating}
onDoubleClick={() => debouncedSetRating(0)}
/> />
</Tooltip> </Center>
</div> </Grid.Col>
</MediaQuery>
)} )}
</Group> </Grid>
<WrapperSlider <WrapperSlider
leftLabel={<RiVolumeUpFill size={20} />} leftLabel={<RiVolumeUpFill size={20} />}
max={100} max={100}

View File

@ -14,11 +14,26 @@ import { ImageButton } from '/@/remote/components/buttons/image-button';
import { RemoteContainer } from '/@/remote/components/remote-container'; import { RemoteContainer } from '/@/remote/components/remote-container';
import { ReconnectButton } from '/@/remote/components/buttons/reconnect-button'; import { ReconnectButton } from '/@/remote/components/buttons/reconnect-button';
import { useConnected } from '/@/remote/store'; 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 = () => { export const Shell = () => {
const connected = useConnected(); 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 ( return (
<NoSleepContext.Provider value={noSleepValue}>
<AppShell <AppShell
header={ header={
<Header height={60}> <Header height={60}>
@ -44,7 +59,24 @@ export const Shell = () => {
<Title ta="center">Feishin Remote</Title> <Title ta="center">Feishin Remote</Title>
</Grid.Col> </Grid.Col>
</MediaQuery> </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"> <Grid.Col span="auto">
<Flex <Flex
direction="row" direction="row"
@ -53,8 +85,10 @@ export const Shell = () => {
<ReconnectButton /> <ReconnectButton />
<ImageButton /> <ImageButton />
<ThemeButton /> <ThemeButton />
<SleepButton />
</Flex> </Flex>
</Grid.Col> </Grid.Col>
</MediaQuery>
</Grid> </Grid>
</Header> </Header>
} }
@ -71,5 +105,6 @@ export const Shell = () => {
)} )}
</Container> </Container>
</AppShell> </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;
};