mirror of
https://github.com/movie-web/movie-web.git
synced 2024-12-28 09:26:05 +00:00
Fix sticky sidebar + new design for app information + gorgegous new dropdown + bunch of small bug fixes + fix encryption not supporting utf8
Co-authored-by: Jip Frijlink <JipFr@users.noreply.github.com>
This commit is contained in:
parent
8cdedbfca6
commit
7bc3bb1416
|
@ -32,7 +32,7 @@
|
||||||
"react-helmet-async": "^1.3.0",
|
"react-helmet-async": "^1.3.0",
|
||||||
"react-i18next": "^12.1.1",
|
"react-i18next": "^12.1.1",
|
||||||
"react-router-dom": "^5.2.0",
|
"react-router-dom": "^5.2.0",
|
||||||
"react-stickynode": "^4.1.0",
|
"react-sticky-el": "^2.1.0",
|
||||||
"react-use": "^17.4.0",
|
"react-use": "^17.4.0",
|
||||||
"slugify": "^1.6.6",
|
"slugify": "^1.6.6",
|
||||||
"subsrt-ts": "^2.1.1",
|
"subsrt-ts": "^2.1.1",
|
||||||
|
|
|
@ -95,9 +95,9 @@ dependencies:
|
||||||
react-router-dom:
|
react-router-dom:
|
||||||
specifier: ^5.2.0
|
specifier: ^5.2.0
|
||||||
version: 5.3.4(react@17.0.2)
|
version: 5.3.4(react@17.0.2)
|
||||||
react-stickynode:
|
react-sticky-el:
|
||||||
specifier: ^4.1.0
|
specifier: ^2.1.0
|
||||||
version: 4.1.0(react-dom@17.0.2)(react@17.0.2)
|
version: 2.1.0(react-dom@17.0.2)(react@17.0.2)
|
||||||
react-use:
|
react-use:
|
||||||
specifier: ^17.4.0
|
specifier: ^17.4.0
|
||||||
version: 17.4.0(react-dom@17.0.2)(react@17.0.2)
|
version: 17.4.0(react-dom@17.0.2)(react@17.0.2)
|
||||||
|
@ -3657,10 +3657,6 @@ packages:
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/eventemitter3@3.1.2:
|
|
||||||
resolution: {integrity: sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/fast-deep-equal@3.1.3:
|
/fast-deep-equal@3.1.3:
|
||||||
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
|
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
|
||||||
|
|
||||||
|
@ -4595,6 +4591,7 @@ packages:
|
||||||
|
|
||||||
/lodash@4.17.21:
|
/lodash@4.17.21:
|
||||||
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
|
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/loose-envify@1.4.0:
|
/loose-envify@1.4.0:
|
||||||
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
|
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
|
||||||
|
@ -4994,10 +4991,6 @@ packages:
|
||||||
resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==}
|
resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/performance-now@2.1.0:
|
|
||||||
resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/picocolors@1.0.0:
|
/picocolors@1.0.0:
|
||||||
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
|
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -5165,12 +5158,6 @@ packages:
|
||||||
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/raf@3.4.1:
|
|
||||||
resolution: {integrity: sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==}
|
|
||||||
dependencies:
|
|
||||||
performance-now: 2.1.0
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/randombytes@2.1.0:
|
/randombytes@2.1.0:
|
||||||
resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
|
resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -5286,19 +5273,14 @@ packages:
|
||||||
tiny-warning: 1.0.3
|
tiny-warning: 1.0.3
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/react-stickynode@4.1.0(react-dom@17.0.2)(react@17.0.2):
|
/react-sticky-el@2.1.0(react-dom@17.0.2)(react@17.0.2):
|
||||||
resolution: {integrity: sha512-zylWgfad75jLfh/gYIayDcDWIDwO4weZrsZqDpjZ/axhF06zRjdCWFBgUr33Pvv2+htKWqPSFksWTyB6aMQ1ZQ==}
|
resolution: {integrity: sha512-oo+a2GedF4QMfCfm20e9gD+RuuQp/ngvwGMUXAXpST+h4WnmKhuv7x6MQ4X/e3AHiLYgE0zDyJo1Pzo8m51KpA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^0.14.2 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0
|
react: '>=16.3.0'
|
||||||
react-dom: ^0.14.2 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0
|
react-dom: '>=16.3.0'
|
||||||
dependencies:
|
dependencies:
|
||||||
classnames: 2.3.2
|
|
||||||
core-js: 3.32.1
|
|
||||||
prop-types: 15.8.1
|
|
||||||
react: 17.0.2
|
react: 17.0.2
|
||||||
react-dom: 17.0.2(react@17.0.2)
|
react-dom: 17.0.2(react@17.0.2)
|
||||||
shallowequal: 1.1.0
|
|
||||||
subscribe-ui-event: 2.0.7
|
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/react-universal-interface@0.6.2(react@17.0.2)(tslib@2.6.2):
|
/react-universal-interface@0.6.2(react@17.0.2)(tslib@2.6.2):
|
||||||
|
@ -5796,14 +5778,6 @@ packages:
|
||||||
resolution: {integrity: sha512-E87pIogpwUsUwXw7dNyU4QDjdgVMy52m+XEOPEKUn161cCzWjjhPSQhByfd1CcNvrOLnXQ6OnnZDwnJrz/Z4YQ==}
|
resolution: {integrity: sha512-E87pIogpwUsUwXw7dNyU4QDjdgVMy52m+XEOPEKUn161cCzWjjhPSQhByfd1CcNvrOLnXQ6OnnZDwnJrz/Z4YQ==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/subscribe-ui-event@2.0.7:
|
|
||||||
resolution: {integrity: sha512-Acrtf9XXl6lpyHAWYeRD1xTPUQHDERfL4GHeNuYAtZMc4Z8Us2iDBP0Fn3xiRvkQ1FO+hx+qRLmPEwiZxp7FDQ==}
|
|
||||||
dependencies:
|
|
||||||
eventemitter3: 3.1.2
|
|
||||||
lodash: 4.17.21
|
|
||||||
raf: 3.4.1
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/subsrt-ts@2.1.1:
|
/subsrt-ts@2.1.1:
|
||||||
resolution: {integrity: sha512-E+GiLNG4L82yRDswd4ys34OUfJLNN6ZBdtefE7ftn/WJchjvyJ9dNXuXYviNglrqiCqNyayGGUZE3v9aL7zIYg==}
|
resolution: {integrity: sha512-E+GiLNG4L82yRDswd4ys34OUfJLNN6ZBdtefE7ftn/WJchjvyJ9dNXuXYviNglrqiCqNyayGGUZE3v9aL7zIYg==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
|
@ -70,7 +70,7 @@ export function base64ToBuffer(data: string) {
|
||||||
return forge.util.binary.base64.decode(data);
|
return forge.util.binary.base64.decode(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function base64ToStringBugger(data: string) {
|
export function base64ToStringBuffer(data: string) {
|
||||||
return forge.util.createBuffer(base64ToBuffer(data));
|
return forge.util.createBuffer(base64ToBuffer(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,7 +97,7 @@ export async function encryptData(data: string, secret: Uint8Array) {
|
||||||
iv,
|
iv,
|
||||||
tagLength: 128,
|
tagLength: 128,
|
||||||
});
|
});
|
||||||
cipher.update(forge.util.createBuffer(data));
|
cipher.update(forge.util.createBuffer(data, "utf8"));
|
||||||
cipher.finish();
|
cipher.finish();
|
||||||
|
|
||||||
const encryptedData = cipher.output;
|
const encryptedData = cipher.output;
|
||||||
|
@ -118,11 +118,11 @@ export function decryptData(data: string, secret: Uint8Array) {
|
||||||
forge.util.createBuffer(secret)
|
forge.util.createBuffer(secret)
|
||||||
);
|
);
|
||||||
decipher.start({
|
decipher.start({
|
||||||
iv: base64ToStringBugger(iv),
|
iv: base64ToStringBuffer(iv),
|
||||||
tag: base64ToStringBugger(tag),
|
tag: base64ToStringBuffer(tag),
|
||||||
tagLength: 128,
|
tagLength: 128,
|
||||||
});
|
});
|
||||||
decipher.update(base64ToStringBugger(encryptedData));
|
decipher.update(base64ToStringBuffer(encryptedData));
|
||||||
const pass = decipher.finish();
|
const pass = decipher.finish();
|
||||||
|
|
||||||
if (!pass) throw new Error("Error decrypting data");
|
if (!pass) throw new Error("Error decrypting data");
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
|
||||||
|
import { base64ToBuffer, decryptData } from "@/backend/accounts/crypto";
|
||||||
import { Icon, Icons } from "@/components/Icon";
|
import { Icon, Icons } from "@/components/Icon";
|
||||||
import { UserIcon } from "@/components/UserIcon";
|
import { UserIcon } from "@/components/UserIcon";
|
||||||
import { AccountProfile } from "@/pages/parts/auth/AccountCreatePart";
|
import { AccountProfile } from "@/pages/parts/auth/AccountCreatePart";
|
||||||
|
@ -42,16 +44,40 @@ export function UserAvatar(props: {
|
||||||
sizeClass?: string;
|
sizeClass?: string;
|
||||||
iconClass?: string;
|
iconClass?: string;
|
||||||
bottom?: React.ReactNode;
|
bottom?: React.ReactNode;
|
||||||
|
withName?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const auth = useAuthStore();
|
const auth = useAuthStore();
|
||||||
if (!auth.account) return null;
|
|
||||||
|
const bufferSeed = useMemo(
|
||||||
|
() =>
|
||||||
|
auth.account && auth.account.seed
|
||||||
|
? base64ToBuffer(auth.account.seed)
|
||||||
|
: null,
|
||||||
|
[auth]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!auth.account || auth.account === null) return null;
|
||||||
|
|
||||||
|
const deviceName = bufferSeed
|
||||||
|
? decryptData(auth.account.deviceName, bufferSeed)
|
||||||
|
: "...";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Avatar
|
<>
|
||||||
profile={auth.account.profile}
|
<Avatar
|
||||||
sizeClass={props.sizeClass ?? "w-[2rem] h-[2rem]"}
|
profile={auth.account.profile}
|
||||||
iconClass={props.iconClass}
|
sizeClass={props.sizeClass ?? "w-[2rem] h-[2rem]"}
|
||||||
bottom={props.bottom}
|
iconClass={props.iconClass}
|
||||||
/>
|
bottom={props.bottom}
|
||||||
|
/>
|
||||||
|
{props.withName && bufferSeed ? (
|
||||||
|
<span>
|
||||||
|
{deviceName.length >= 20
|
||||||
|
? `${deviceName.slice(0, 20 - 1)}…`
|
||||||
|
: deviceName}
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -107,12 +107,19 @@ export function LinksDropdown(props: { children: React.ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<div className="relative is-dropdown">
|
<div className="relative is-dropdown">
|
||||||
<div
|
<div
|
||||||
className="cursor-pointer tabbable rounded-full"
|
className="cursor-pointer tabbable rounded-full flex gap-2 text-white items-center py-2 px-3 bg-pill-background bg-opacity-50"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onClick={toggleOpen}
|
onClick={toggleOpen}
|
||||||
onKeyUp={(evt) => evt.key === "Enter" && toggleOpen()}
|
onKeyUp={(evt) => evt.key === "Enter" && toggleOpen()}
|
||||||
>
|
>
|
||||||
{props.children}
|
{props.children}
|
||||||
|
<Icon
|
||||||
|
className={classNames(
|
||||||
|
"text-xl transition-transform duration-100",
|
||||||
|
open ? "rotate-180" : ""
|
||||||
|
)}
|
||||||
|
icon={Icons.CHEVRON_DOWN}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Transition animation="slide-down" show={open}>
|
<Transition animation="slide-down" show={open}>
|
||||||
<div className="rounded-lg absolute w-64 bg-dropdown-altBackground top-full mt-3 right-0">
|
<div className="rounded-lg absolute w-64 bg-dropdown-altBackground top-full mt-3 right-0">
|
||||||
|
|
|
@ -101,7 +101,7 @@ export function Navigation(props: NavigationProps) {
|
||||||
</div>
|
</div>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<LinksDropdown>
|
<LinksDropdown>
|
||||||
{loggedIn ? <UserAvatar /> : <NoUserAvatar />}
|
{loggedIn ? <UserAvatar withName /> : <NoUserAvatar />}
|
||||||
</LinksDropdown>
|
</LinksDropdown>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -10,7 +10,7 @@ export function Heading1(props: TextProps) {
|
||||||
return (
|
return (
|
||||||
<h1
|
<h1
|
||||||
className={[
|
className={[
|
||||||
"text-5xl font-bold text-white mb-9",
|
"text-3xl lg:text-5xl font-bold text-white mb-9",
|
||||||
props.border ? borderClass : null,
|
props.border ? borderClass : null,
|
||||||
props.className ?? "",
|
props.className ?? "",
|
||||||
].join(" ")}
|
].join(" ")}
|
||||||
|
@ -24,7 +24,7 @@ export function Heading2(props: TextProps) {
|
||||||
return (
|
return (
|
||||||
<h2
|
<h2
|
||||||
className={[
|
className={[
|
||||||
"text-3xl font-bold text-white mt-20 mb-9",
|
"text-xl lg:text-3xl font-bold text-white mt-20 mb-9",
|
||||||
props.border ? borderClass : null,
|
props.border ? borderClass : null,
|
||||||
props.className ?? "",
|
props.className ?? "",
|
||||||
].join(" ")}
|
].join(" ")}
|
||||||
|
@ -38,7 +38,7 @@ export function Heading3(props: TextProps) {
|
||||||
return (
|
return (
|
||||||
<h2
|
<h2
|
||||||
className={[
|
className={[
|
||||||
"text-xl font-bold text-white mb-3",
|
"text-lg lg:text-xl font-bold text-white mb-3",
|
||||||
props.border ? borderClass : null,
|
props.border ? borderClass : null,
|
||||||
props.className ?? "",
|
props.className ?? "",
|
||||||
].join(" ")}
|
].join(" ")}
|
||||||
|
|
|
@ -164,7 +164,8 @@ export function useAuth() {
|
||||||
const anyError: any = err;
|
const anyError: any = err;
|
||||||
if (
|
if (
|
||||||
anyError?.response?.status === 401 ||
|
anyError?.response?.status === 401 ||
|
||||||
anyError?.response?.status === 403
|
anyError?.response?.status === 403 ||
|
||||||
|
anyError?.response?.status === 400
|
||||||
) {
|
) {
|
||||||
await logout();
|
await logout();
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -87,7 +87,9 @@ function AuthWrapper() {
|
||||||
if (status.error)
|
if (status.error)
|
||||||
return (
|
return (
|
||||||
<ErrorScreen showResetButton={backendUrl !== userBackendUrl}>
|
<ErrorScreen showResetButton={backendUrl !== userBackendUrl}>
|
||||||
Failed to fetch user data. Try resetting the backend URL.
|
{backendUrl !== userBackendUrl
|
||||||
|
? "Failed to fetch user data. Try resetting the backend URL"
|
||||||
|
: "Failed to fetch user data."}
|
||||||
</ErrorScreen>
|
</ErrorScreen>
|
||||||
);
|
);
|
||||||
return <App />;
|
return <App />;
|
||||||
|
|
|
@ -42,7 +42,7 @@ function SettingsLayout(props: { children: React.ReactNode }) {
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"grid gap-12",
|
"grid gap-12",
|
||||||
isMobile ? "grid-cols-1" : "lg:grid-cols-[310px,1fr]"
|
isMobile ? "grid-cols-1" : "lg:grid-cols-[280px,1fr]"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<SidebarPart />
|
<SidebarPart />
|
||||||
|
@ -240,16 +240,24 @@ export function SettingsPage() {
|
||||||
</div>
|
</div>
|
||||||
</SettingsLayout>
|
</SettingsLayout>
|
||||||
<div
|
<div
|
||||||
className={`bg-settings-saveBar-background border-t border-settings-card-border/50 py-4 transition-opacity w-full fixed bottom-0 flex justify-between px-8 items-center ${
|
className={`bg-settings-saveBar-background border-t border-settings-card-border/50 py-4 transition-opacity w-full fixed bottom-0 flex justify-between flex-col md:flex-row px-8 items-start md:items-center gap-3 ${
|
||||||
state.changed ? "opacity-100" : "opacity-0"
|
state.changed ? "opacity-100" : "opacity-0"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<p className="text-type-danger">You have unsaved changes</p>
|
<p className="text-type-danger">You have unsaved changes</p>
|
||||||
<div className="space-x-6">
|
<div className="space-x-3 w-full md:w-auto flex">
|
||||||
<Button theme="secondary" onClick={state.reset}>
|
<Button
|
||||||
|
className="w-full md:w-auto"
|
||||||
|
theme="secondary"
|
||||||
|
onClick={state.reset}
|
||||||
|
>
|
||||||
Reset
|
Reset
|
||||||
</Button>
|
</Button>
|
||||||
<Button theme="purple" onClick={saveChanges}>
|
<Button
|
||||||
|
className="w-full md:w-auto"
|
||||||
|
theme="purple"
|
||||||
|
onClick={saveChanges}
|
||||||
|
>
|
||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { useCallback, useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
import Sticky from "react-stickynode";
|
import Sticky from "react-sticky-el";
|
||||||
|
|
||||||
import { ThinContainer } from "@/components/layout/ThinContainer";
|
import { ThinContainer } from "@/components/layout/ThinContainer";
|
||||||
import { SearchBarInput } from "@/components/SearchBar";
|
import { SearchBarInput } from "@/components/SearchBar";
|
||||||
|
@ -19,10 +19,9 @@ export function HeroPart({ setIsSticky, searchParams }: HeroPartProps) {
|
||||||
const [, setShowBg] = useState(false);
|
const [, setShowBg] = useState(false);
|
||||||
const bannerSize = useBannerSize();
|
const bannerSize = useBannerSize();
|
||||||
const stickStateChanged = useCallback(
|
const stickStateChanged = useCallback(
|
||||||
({ status }: Sticky.Status) => {
|
(isFixed) => {
|
||||||
const val = status === Sticky.STATUS_FIXED;
|
setShowBg(isFixed);
|
||||||
setShowBg(val);
|
setIsSticky(isFixed);
|
||||||
setIsSticky(val);
|
|
||||||
},
|
},
|
||||||
[setShowBg, setIsSticky]
|
[setShowBg, setIsSticky]
|
||||||
);
|
);
|
||||||
|
@ -40,11 +39,13 @@ export function HeroPart({ setIsSticky, searchParams }: HeroPartProps) {
|
||||||
<div className="relative z-10 mb-16">
|
<div className="relative z-10 mb-16">
|
||||||
<HeroTitle className="mx-auto max-w-xs">{title}</HeroTitle>
|
<HeroTitle className="mx-auto max-w-xs">{title}</HeroTitle>
|
||||||
</div>
|
</div>
|
||||||
<div className="relative z-30">
|
<div className="relative h-20 z-30">
|
||||||
<Sticky
|
<Sticky
|
||||||
enabled
|
topOffset={-16 + bannerSize}
|
||||||
top={16 + bannerSize}
|
stickyStyle={{
|
||||||
onStateChange={stickStateChanged}
|
paddingTop: `${16 + bannerSize}px`,
|
||||||
|
}}
|
||||||
|
onFixedToggle={stickStateChanged}
|
||||||
>
|
>
|
||||||
<SearchBarInput
|
<SearchBarInput
|
||||||
onChange={setSearch}
|
onChange={setSearch}
|
||||||
|
|
|
@ -11,7 +11,7 @@ import { Menu } from "@/components/player/internals/ContextMenu";
|
||||||
import { CaptionCue } from "@/components/player/Player";
|
import { CaptionCue } from "@/components/player/Player";
|
||||||
import { Transition } from "@/components/Transition";
|
import { Transition } from "@/components/Transition";
|
||||||
import { Heading1 } from "@/components/utils/Text";
|
import { Heading1 } from "@/components/utils/Text";
|
||||||
import { SubtitleStyling, useSubtitleStore } from "@/stores/subtitles";
|
import { SubtitleStyling } from "@/stores/subtitles";
|
||||||
|
|
||||||
export function CaptionPreview(props: {
|
export function CaptionPreview(props: {
|
||||||
fullscreen?: boolean;
|
fullscreen?: boolean;
|
||||||
|
@ -24,7 +24,7 @@ export function CaptionPreview(props: {
|
||||||
className={classNames({
|
className={classNames({
|
||||||
"pointer-events-none overflow-hidden w-full rounded": true,
|
"pointer-events-none overflow-hidden w-full rounded": true,
|
||||||
"aspect-video relative": !props.fullscreen,
|
"aspect-video relative": !props.fullscreen,
|
||||||
"fixed inset-0 z-50": props.fullscreen,
|
"fixed inset-0 z-[60]": props.fullscreen,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<Transition animation="fade" show={props.show}>
|
<Transition animation="fade" show={props.show}>
|
||||||
|
@ -71,7 +71,7 @@ export function CaptionsPart(props: {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Heading1 border>Captions</Heading1>
|
<Heading1 border>Captions</Heading1>
|
||||||
<div className="grid grid-cols-[1fr,356px] gap-8">
|
<div className="grid md:grid-cols-[1fr,356px] gap-8">
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<CaptionSetting
|
<CaptionSetting
|
||||||
label="Background opacity"
|
label="Background opacity"
|
||||||
|
|
|
@ -44,7 +44,7 @@ function ProxyEdit({ proxyUrls, setProxyUrls }: ProxyEditProps) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingsCard>
|
<SettingsCard>
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center gap-4">
|
||||||
<div className="my-3">
|
<div className="my-3">
|
||||||
<p className="text-white font-bold mb-3">Use custom proxy workers</p>
|
<p className="text-white font-bold mb-3">Use custom proxy workers</p>
|
||||||
<p className="max-w-[20rem] font-medium">
|
<p className="max-w-[20rem] font-medium">
|
||||||
|
@ -103,7 +103,7 @@ function ProxyEdit({ proxyUrls, setProxyUrls }: ProxyEditProps) {
|
||||||
function BackendEdit({ backendUrl, setBackendUrl }: BackendEditProps) {
|
function BackendEdit({ backendUrl, setBackendUrl }: BackendEditProps) {
|
||||||
return (
|
return (
|
||||||
<SettingsCard>
|
<SettingsCard>
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center gap-4">
|
||||||
<div className="my-3">
|
<div className="my-3">
|
||||||
<p className="text-white font-bold mb-3">Custom server</p>
|
<p className="text-white font-bold mb-3">Custom server</p>
|
||||||
<p className="max-w-[20rem] font-medium">
|
<p className="max-w-[20rem] font-medium">
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import classNames from "classnames";
|
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import Sticky from "react-stickynode";
|
import Sticky from "react-sticky-el";
|
||||||
import { useAsync } from "react-use";
|
import { useAsync } from "react-use";
|
||||||
|
|
||||||
import { getBackendMeta } from "@/backend/accounts/meta";
|
import { getBackendMeta } from "@/backend/accounts/meta";
|
||||||
|
@ -10,34 +9,25 @@ import { Divider } from "@/components/utils/Divider";
|
||||||
import { useBackendUrl } from "@/hooks/auth/useBackendUrl";
|
import { useBackendUrl } from "@/hooks/auth/useBackendUrl";
|
||||||
import { useIsMobile } from "@/hooks/useIsMobile";
|
import { useIsMobile } from "@/hooks/useIsMobile";
|
||||||
import { conf } from "@/setup/config";
|
import { conf } from "@/setup/config";
|
||||||
|
import { useAuthStore } from "@/stores/auth";
|
||||||
|
|
||||||
function BackendUrl(props: { url: string }) {
|
const rem = 16;
|
||||||
const url = props.url.replace(/https?:\/\//, "");
|
|
||||||
|
function SecureBadge(props: { url: string }) {
|
||||||
const secure = props.url.startsWith("https://");
|
const secure = props.url.startsWith("https://");
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-1 -mx-1 ml-3 px-1 rounded bg-largeCard-background font-bold">
|
||||||
<div
|
<Icon icon={secure ? Icons.LOCK : Icons.UNLOCK} />
|
||||||
title={secure ? "Secure" : "Insecure"}
|
Secure
|
||||||
className={classNames(
|
|
||||||
"w-5 min-w-[1.25rem] h-5 rounded flex justify-center items-center",
|
|
||||||
secure ? "bg-emerald-200/30 text-white" : "bg-red-600/20 text-white"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
className="opacity-50"
|
|
||||||
icon={secure ? Icons.LOCK : Icons.UNLOCK}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{url}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SidebarPart() {
|
export function SidebarPart() {
|
||||||
const { isMobile } = useIsMobile();
|
const { isMobile } = useIsMobile();
|
||||||
|
const { account } = useAuthStore();
|
||||||
// eslint-disable-next-line no-restricted-globals
|
// eslint-disable-next-line no-restricted-globals
|
||||||
const hostname = location.hostname;
|
const hostname = location.hostname;
|
||||||
const rem = 16;
|
|
||||||
const [activeLink, setActiveLink] = useState("");
|
const [activeLink, setActiveLink] = useState("");
|
||||||
|
|
||||||
const settingLinks = [
|
const settingLinks = [
|
||||||
|
@ -54,7 +44,6 @@ export function SidebarPart() {
|
||||||
return getBackendMeta(backendUrl);
|
return getBackendMeta(backendUrl);
|
||||||
}, [backendUrl]);
|
}, [backendUrl]);
|
||||||
|
|
||||||
// TODO loading/error state for backend
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function recheck() {
|
function recheck() {
|
||||||
const windowHeight =
|
const windowHeight =
|
||||||
|
@ -97,11 +86,13 @@ export function SidebarPart() {
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="text-settings-sidebar-type-inactive sidebar-boundary">
|
||||||
<Sticky
|
<Sticky
|
||||||
enabled={!isMobile}
|
topOffset={-6 * rem}
|
||||||
top={10 * rem} // 10rem
|
stickyClassName="pt-[6rem]"
|
||||||
className="text-settings-sidebar-type-inactive"
|
disabled={isMobile}
|
||||||
|
hideOnBoundaryHit={false}
|
||||||
|
boundaryElement=".sidebar-boundary"
|
||||||
>
|
>
|
||||||
<div className="hidden lg:block">
|
<div className="hidden lg:block">
|
||||||
<SidebarSection title="Settings">
|
<SidebarSection title="Settings">
|
||||||
|
@ -119,28 +110,56 @@ export function SidebarPart() {
|
||||||
<Divider />
|
<Divider />
|
||||||
</div>
|
</div>
|
||||||
<SidebarSection className="text-sm" title="App information">
|
<SidebarSection className="text-sm" title="App information">
|
||||||
<div className="flex justify-between items-center space-x-3">
|
<div className="px-3 py-3.5 rounded-lg bg-largeCard-background bg-opacity-50 grid grid-cols-2 gap-4">
|
||||||
<span>Version</span>
|
{/* Hostname */}
|
||||||
<span>{conf().APP_VERSION}</span>
|
<div className="col-span-2 space-y-1">
|
||||||
|
<p className="text-type-dimmed font-medium">Hostname</p>
|
||||||
|
<p className="text-white">{hostname}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Backend URL */}
|
||||||
|
<div className="col-span-2 space-y-1">
|
||||||
|
<p className="text-type-dimmed font-medium flex items-center">
|
||||||
|
Backend URL
|
||||||
|
<SecureBadge url={backendUrl} />
|
||||||
|
</p>
|
||||||
|
<p className="text-white">
|
||||||
|
{backendUrl.replace(/https?:\/\//, "")}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* User ID */}
|
||||||
|
<div className="col-span-2 space-y-1">
|
||||||
|
<p className="text-type-dimmed font-medium">User ID</p>
|
||||||
|
<p className="text-white">{account?.userId ?? "Not logged in"}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* App version */}
|
||||||
|
<div className="col-span-1 space-y-1">
|
||||||
|
<p className="text-type-dimmed font-medium">App version</p>
|
||||||
|
<p className="text-type-dimmed px-2 py-1 rounded bg-settings-sidebar-badge inline-block">
|
||||||
|
{conf().APP_VERSION}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Backend version */}
|
||||||
|
<div className="col-span-1 space-y-1">
|
||||||
|
<p className="text-type-dimmed font-medium">Backend version</p>
|
||||||
|
<p className="text-type-dimmed px-2 py-1 rounded bg-settings-sidebar-badge inline-flex items-center gap-1">
|
||||||
|
{backendMeta.error ? (
|
||||||
|
<Icon
|
||||||
|
icon={Icons.WARNING}
|
||||||
|
className="text-type-danger text-base"
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
{backendMeta.loading ? (
|
||||||
|
<div className="h-4 w-12 bg-type-dimmed/20 rounded" />
|
||||||
|
) : (
|
||||||
|
backendMeta?.value?.version || "Unknown"
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between items-center space-x-3">
|
|
||||||
<span>Domain</span>
|
|
||||||
<span className="text-right">{hostname}</span>
|
|
||||||
</div>
|
|
||||||
{backendMeta.value ? (
|
|
||||||
<>
|
|
||||||
<div className="flex justify-between items-center space-x-3">
|
|
||||||
<span>Backend Version</span>
|
|
||||||
<span>{backendMeta.value.version}</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between items-center space-x-3">
|
|
||||||
<span className="whitespace-nowrap">Backend URL</span>
|
|
||||||
<span className="text-right">
|
|
||||||
<BackendUrl url={backendUrl} />
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
) : null}
|
|
||||||
</SidebarSection>
|
</SidebarSection>
|
||||||
</Sticky>
|
</Sticky>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -120,7 +120,7 @@ export function ThemePart(props: {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Heading1 border>Appearance</Heading1>
|
<Heading1 border>Appearance</Heading1>
|
||||||
<div className="grid grid-cols-[repeat(auto-fill,minmax(200px,1fr))] gap-6 max-w-[700px]">
|
<div className="grid grid-cols-[repeat(auto-fill,minmax(160px,1fr))] gap-6 max-w-[700px]">
|
||||||
{/* default theme */}
|
{/* default theme */}
|
||||||
<ThemePreview
|
<ThemePreview
|
||||||
name="Default"
|
name="Default"
|
||||||
|
|
|
@ -120,6 +120,7 @@ export const defaultTheme = {
|
||||||
settings: {
|
settings: {
|
||||||
sidebar: {
|
sidebar: {
|
||||||
activeLink: "#171728",
|
activeLink: "#171728",
|
||||||
|
badge: "#0A0A12",
|
||||||
|
|
||||||
type: {
|
type: {
|
||||||
secondary: "#4B395F",
|
secondary: "#4B395F",
|
||||||
|
|
Loading…
Reference in a new issue