mirror of
https://github.com/yume-chan/ya-webadb.git
synced 2025-10-03 09:49:24 +02:00
parent
4524f90aab
commit
59c470a76c
6 changed files with 146 additions and 9 deletions
40
apps/demo/src/hooks/add-event-listener.ts
Normal file
40
apps/demo/src/hooks/add-event-listener.ts
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
|
type CommonEventMaps<T> = T extends typeof globalThis
|
||||||
|
? WindowEventMap
|
||||||
|
: T extends Window
|
||||||
|
? WindowEventMap
|
||||||
|
: T extends Document
|
||||||
|
? DocumentEventMap
|
||||||
|
: T extends HTMLElement
|
||||||
|
? HTMLElementEventMap
|
||||||
|
: T extends SVGElement
|
||||||
|
? SVGElementEventMap
|
||||||
|
: { [type: string]: unknown };
|
||||||
|
|
||||||
|
const useClientAddEventListener = <
|
||||||
|
T extends EventTarget,
|
||||||
|
U extends keyof CommonEventMaps<T>
|
||||||
|
>(
|
||||||
|
target: T | (() => T),
|
||||||
|
type: U,
|
||||||
|
listener: (this: T, ev: CommonEventMaps<T>[U]) => any,
|
||||||
|
options?: AddEventListenerOptions,
|
||||||
|
deps?: readonly unknown[]
|
||||||
|
) => {
|
||||||
|
useEffect(() => {
|
||||||
|
const targetValue = typeof target === "function" ? target() : target;
|
||||||
|
targetValue.addEventListener(type as any, listener as any, options);
|
||||||
|
|
||||||
|
return () =>
|
||||||
|
targetValue.removeEventListener(
|
||||||
|
type as any,
|
||||||
|
listener as any,
|
||||||
|
options
|
||||||
|
);
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, deps);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useAddEventListener =
|
||||||
|
typeof window !== "undefined" ? useClientAddEventListener : () => {};
|
4
apps/demo/src/hooks/index.ts
Normal file
4
apps/demo/src/hooks/index.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
export * from "./add-event-listener";
|
||||||
|
export * from "./layout-effect";
|
||||||
|
export * from "./local-storage";
|
||||||
|
export * from "./stable-callback";
|
4
apps/demo/src/hooks/layout-effect.ts
Normal file
4
apps/demo/src/hooks/layout-effect.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
import { useLayoutEffect as useReactLayoutEffect } from "react";
|
||||||
|
|
||||||
|
export const useLayoutEffect =
|
||||||
|
typeof window !== "undefined" ? useReactLayoutEffect : () => {};
|
34
apps/demo/src/hooks/local-storage.ts
Normal file
34
apps/demo/src/hooks/local-storage.ts
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
import { useAddEventListener } from "./add-event-listener";
|
||||||
|
import { useStableCallback } from "./stable-callback";
|
||||||
|
|
||||||
|
function useClientLocalStorage<T extends string = string>(
|
||||||
|
key: string,
|
||||||
|
fallbackValue: T
|
||||||
|
) {
|
||||||
|
const [value, setValue] = useState<T>(
|
||||||
|
() => (localStorage.getItem(key) as T) || fallbackValue
|
||||||
|
);
|
||||||
|
|
||||||
|
useAddEventListener(
|
||||||
|
globalThis,
|
||||||
|
"storage",
|
||||||
|
() =>
|
||||||
|
setValue((localStorage.getItem(key) as T | null) ?? fallbackValue),
|
||||||
|
{ passive: true },
|
||||||
|
[key, fallbackValue]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleChange = useStableCallback((value: T) => {
|
||||||
|
setValue(value);
|
||||||
|
localStorage.setItem(key, value);
|
||||||
|
});
|
||||||
|
|
||||||
|
return [value, handleChange] as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useLocalStorage: typeof useClientLocalStorage =
|
||||||
|
typeof localStorage !== "undefined"
|
||||||
|
? useClientLocalStorage
|
||||||
|
: (key, fallbackValue) => [fallbackValue, () => {}];
|
35
apps/demo/src/hooks/stable-callback.ts
Normal file
35
apps/demo/src/hooks/stable-callback.ts
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import { MutableRefObject, useRef } from "react";
|
||||||
|
|
||||||
|
import { useLayoutEffect } from "./layout-effect";
|
||||||
|
|
||||||
|
const UNINITIALIZED = Symbol("UNINITIALIZED");
|
||||||
|
|
||||||
|
export function useConstLazy<T>(initializer: () => T): T {
|
||||||
|
const ref = useRef<T | typeof UNINITIALIZED>(UNINITIALIZED);
|
||||||
|
if (ref.current === UNINITIALIZED) {
|
||||||
|
ref.current = initializer();
|
||||||
|
}
|
||||||
|
return ref.current;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useConst<T>(value: T): T {
|
||||||
|
const ref = useRef(value);
|
||||||
|
return ref.current;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useLatestRef<T>(value: T): MutableRefObject<T> {
|
||||||
|
const ref = useRef(value);
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
ref.current = value;
|
||||||
|
}, [value]);
|
||||||
|
return ref;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useStableCallback<T extends (...args: any[]) => void>(
|
||||||
|
callback: T
|
||||||
|
): T {
|
||||||
|
const callbackRef = useLatestRef(callback);
|
||||||
|
return useConst(function (...args) {
|
||||||
|
return callbackRef.current(...args);
|
||||||
|
} as T);
|
||||||
|
}
|
|
@ -1,4 +1,10 @@
|
||||||
import { Dialog, LayerHost, ProgressIndicator, Stack } from "@fluentui/react";
|
import {
|
||||||
|
Dialog,
|
||||||
|
LayerHost,
|
||||||
|
Link,
|
||||||
|
ProgressIndicator,
|
||||||
|
Stack,
|
||||||
|
} from "@fluentui/react";
|
||||||
import { useId } from "@fluentui/react-hooks";
|
import { useId } from "@fluentui/react-hooks";
|
||||||
import { makeStyles, shorthands } from "@griffel/react";
|
import { makeStyles, shorthands } from "@griffel/react";
|
||||||
import { WebCodecsDecoder } from "@yume-chan/scrcpy-decoder-webcodecs";
|
import { WebCodecsDecoder } from "@yume-chan/scrcpy-decoder-webcodecs";
|
||||||
|
@ -17,6 +23,7 @@ import {
|
||||||
SettingItem,
|
SettingItem,
|
||||||
VideoContainer,
|
VideoContainer,
|
||||||
} from "../components/scrcpy";
|
} from "../components/scrcpy";
|
||||||
|
import { useLocalStorage } from "../hooks";
|
||||||
import { GLOBAL_STATE } from "../state";
|
import { GLOBAL_STATE } from "../state";
|
||||||
import { CommonStackTokens, RouteStackProps, formatSpeed } from "../utils";
|
import { CommonStackTokens, RouteStackProps, formatSpeed } from "../utils";
|
||||||
|
|
||||||
|
@ -176,6 +183,11 @@ const Scrcpy: NextPage = () => {
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const [hintHidden, setHintHidden] = useLocalStorage<`${boolean}`>(
|
||||||
|
"scrcpy-hint-hidden",
|
||||||
|
"false"
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.addEventListener("blur", handleBlur);
|
window.addEventListener("blur", handleBlur);
|
||||||
|
|
||||||
|
@ -200,14 +212,22 @@ const Scrcpy: NextPage = () => {
|
||||||
onKeyDown={handleKeyEvent}
|
onKeyDown={handleKeyEvent}
|
||||||
onKeyUp={handleKeyEvent}
|
onKeyUp={handleKeyEvent}
|
||||||
>
|
>
|
||||||
{keyboardLockEnabled && STATE.isFullScreen && (
|
{keyboardLockEnabled &&
|
||||||
|
hintHidden !== "true" &&
|
||||||
|
STATE.isFullScreen && (
|
||||||
<div className={classes.fullScreenStatusBar}>
|
<div className={classes.fullScreenStatusBar}>
|
||||||
<div>{GLOBAL_STATE.backend?.serial}</div>
|
<div>{GLOBAL_STATE.backend?.serial}</div>
|
||||||
<div>FPS: {STATE.fps}</div>
|
<div>FPS: {STATE.fps}</div>
|
||||||
|
|
||||||
<div className={classes.spacer} />
|
<div className={classes.spacer} />
|
||||||
|
|
||||||
<div>Press and hold ESC to exit full screen</div>
|
<div>
|
||||||
|
Press and hold ESC to exit full screen
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Link onClick={() => setHintHidden("true")}>
|
||||||
|
{`Don't show again`}
|
||||||
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<DeviceView
|
<DeviceView
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue