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 { makeStyles, shorthands } from "@griffel/react";
|
||||
import { WebCodecsDecoder } from "@yume-chan/scrcpy-decoder-webcodecs";
|
||||
|
@ -17,6 +23,7 @@ import {
|
|||
SettingItem,
|
||||
VideoContainer,
|
||||
} from "../components/scrcpy";
|
||||
import { useLocalStorage } from "../hooks";
|
||||
import { GLOBAL_STATE } from "../state";
|
||||
import { CommonStackTokens, RouteStackProps, formatSpeed } from "../utils";
|
||||
|
||||
|
@ -176,6 +183,11 @@ const Scrcpy: NextPage = () => {
|
|||
};
|
||||
}, []);
|
||||
|
||||
const [hintHidden, setHintHidden] = useLocalStorage<`${boolean}`>(
|
||||
"scrcpy-hint-hidden",
|
||||
"false"
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener("blur", handleBlur);
|
||||
|
||||
|
@ -200,14 +212,22 @@ const Scrcpy: NextPage = () => {
|
|||
onKeyDown={handleKeyEvent}
|
||||
onKeyUp={handleKeyEvent}
|
||||
>
|
||||
{keyboardLockEnabled && STATE.isFullScreen && (
|
||||
{keyboardLockEnabled &&
|
||||
hintHidden !== "true" &&
|
||||
STATE.isFullScreen && (
|
||||
<div className={classes.fullScreenStatusBar}>
|
||||
<div>{GLOBAL_STATE.backend?.serial}</div>
|
||||
<div>FPS: {STATE.fps}</div>
|
||||
|
||||
<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>
|
||||
)}
|
||||
<DeviceView
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue