Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Unverified Commit aecc8883 authored by Simon Chan's avatar Simon Chan
Browse files

refactor(demo): separate scrcpy setting state

parent 73013bdf
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -11,7 +11,7 @@
        }
    ],
    "isolated_storage": true,
    "name": "WebADB Demo",
    "name": "Android Web Toolbox",
    "short_name": "WebADB",
    "start_url": "/",
    "scope": "/"
+158 −115
Original line number Diff line number Diff line
import { DefaultButton, Dialog, Dropdown, IDropdownOption, PrimaryButton, ProgressIndicator, Stack, StackItem } from '@fluentui/react';
import { Adb, AdbBackend, AdbPacketData, AdbPacketInit } from '@yume-chan/adb';
import {
    DefaultButton,
    Dialog,
    Dropdown,
    IDropdownOption,
    PrimaryButton,
    ProgressIndicator,
    Stack,
    StackItem,
} from "@fluentui/react";
import { Adb, AdbBackend, AdbPacketData, AdbPacketInit } from "@yume-chan/adb";
import AdbDirectSocketsBackend from "@yume-chan/adb-backend-direct-sockets";
import AdbWebUsbBackend, { AdbWebUsbBackendWatcher } from '@yume-chan/adb-backend-webusb';
import AdbWsBackend from '@yume-chan/adb-backend-ws';
import AdbWebCredentialStore from '@yume-chan/adb-credential-web';
import { InspectStream, pipeFrom, ReadableStream, WritableStream } from '@yume-chan/stream-extra';
import { observer } from 'mobx-react-lite';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { GlobalState } from '../state';
import { CommonStackTokens, Icons } from '../utils';

const DropdownStyles = { dropdown: { width: '100%' } };
import AdbWebUsbBackend, {
    AdbWebUsbBackendWatcher,
} from "@yume-chan/adb-backend-webusb";
import AdbWsBackend from "@yume-chan/adb-backend-ws";
import AdbWebCredentialStore from "@yume-chan/adb-credential-web";
import {
    InspectStream,
    ReadableStream,
    WritableStream,
    pipeFrom,
} from "@yume-chan/stream-extra";
import { observer } from "mobx-react-lite";
import { useCallback, useEffect, useMemo, useState } from "react";
import { GLOBAL_STATE } from "../state";
import { CommonStackTokens, Icons } from "../utils";

const DropdownStyles = { dropdown: { width: "100%" } };

const CredentialStore = new AdbWebCredentialStore();

function _Connect(): JSX.Element | null {
    const [supported, setSupported] = useState(true);

    const [selectedBackend, setSelectedBackend] = useState<AdbBackend | undefined>();
    const [selectedBackend, setSelectedBackend] = useState<
        AdbBackend | undefined
    >();
    const [connecting, setConnecting] = useState(false);

    const [usbBackendList, setUsbBackendList] = useState<AdbBackend[]>([]);
@@ -34,20 +52,26 @@ function _Connect(): JSX.Element | null {
            setSupported(supported);

            if (!supported) {
                GlobalState.showErrorDialog('Your browser does not support WebUSB standard, which is required for this site to work.\n\nLatest version of Google Chrome, Microsoft Edge, or other Chromium-based browsers are required.');
                GLOBAL_STATE.showErrorDialog(
                    "Your browser does not support WebUSB standard, which is required for this site to work.\n\nLatest version of Google Chrome, Microsoft Edge, or other Chromium-based browsers are required."
                );
                return;
            }

            updateUsbBackendList();

            const watcher = new AdbWebUsbBackendWatcher(async (serial?: string) => {
            const watcher = new AdbWebUsbBackendWatcher(
                async (serial?: string) => {
                    const list = await updateUsbBackendList();

                    if (serial) {
                    setSelectedBackend(list.find(backend => backend.serial === serial));
                        setSelectedBackend(
                            list.find((backend) => backend.serial === serial)
                        );
                        return;
                    }
            });
                }
            );

            return () => watcher.dispose();
        },
@@ -57,68 +81,76 @@ function _Connect(): JSX.Element | null {

    const [wsBackendList, setWsBackendList] = useState<AdbWsBackend[]>([]);
    useEffect(() => {
        const savedList = localStorage.getItem('ws-backend-list');
        const savedList = localStorage.getItem("ws-backend-list");
        if (!savedList) {
            return;
        }

        const parsed = JSON.parse(savedList) as { address: string; }[];
        setWsBackendList(parsed.map(x => new AdbWsBackend(x.address)));
        const parsed = JSON.parse(savedList) as { address: string }[];
        setWsBackendList(parsed.map((x) => new AdbWsBackend(x.address)));
    }, []);

    const addWsBackend = useCallback(() => {
        const address = window.prompt('Enter the address of WebSockify server');
        const address = window.prompt("Enter the address of WebSockify server");
        if (!address) {
            return;
        }
        setWsBackendList(list => {
        setWsBackendList((list) => {
            const copy = list.slice();
            copy.push(new AdbWsBackend(address));
            window.localStorage.setItem('ws-backend-list', JSON.stringify(copy.map(x => ({ address: x.serial }))));
            window.localStorage.setItem(
                "ws-backend-list",
                JSON.stringify(copy.map((x) => ({ address: x.serial })))
            );
            return copy;
        });
    }, []);

    const [tcpBackendList, setTcpBackendList] = useState<AdbDirectSocketsBackend[]>([]);
    const [tcpBackendList, setTcpBackendList] = useState<
        AdbDirectSocketsBackend[]
    >([]);
    useEffect(() => {
        if (!AdbDirectSocketsBackend.isSupported()) {
            return;
        }

        const savedList = localStorage.getItem('tcp-backend-list');
        const savedList = localStorage.getItem("tcp-backend-list");
        if (!savedList) {
            return;
        }

        const parsed = JSON.parse(savedList) as { address: string; port: number; }[];
        setTcpBackendList(parsed.map(x => new AdbDirectSocketsBackend(x.address, x.port)));
        const parsed = JSON.parse(savedList) as {
            address: string;
            port: number;
        }[];
        setTcpBackendList(
            parsed.map((x) => new AdbDirectSocketsBackend(x.address, x.port))
        );
    }, []);

    const addTcpBackend = useCallback(() => {
        const host = window.prompt('Enter the address of device');
        const host = window.prompt("Enter the address of device");
        if (!host) {
            return;
        }

        const port = window.prompt('Enter the port of device', '5555');
        const port = window.prompt("Enter the port of device", "5555");
        if (!port) {
            return;
        }

        const portNumber = Number.parseInt(port, 10);

        setTcpBackendList(list => {
        setTcpBackendList((list) => {
            const copy = list.slice();
            copy.push(new AdbDirectSocketsBackend(host, portNumber));
            window.localStorage.setItem(
                'tcp-backend-list',
                "tcp-backend-list",
                JSON.stringify(
                    copy.map(
                        x => ({
                    copy.map((x) => ({
                        address: x.host,
                            port: x.port
                        })
                    )
                        port: x.port,
                    }))
                )
            );
            return copy;
@@ -127,7 +159,7 @@ function _Connect(): JSX.Element | null {

    const handleSelectedBackendChange = (
        e: React.FormEvent<HTMLDivElement>,
        option?: IDropdownOption,
        option?: IDropdownOption
    ) => {
        setSelectedBackend(option?.data as AdbBackend);
    };
@@ -151,21 +183,20 @@ function _Connect(): JSX.Element | null {
            const streams = await selectedBackend.connect();

            // Use `InspectStream`s to intercept and log packets
            readable = streams.readable
                .pipeThrough(
                    new InspectStream(packet => {
                        GlobalState.appendLog('in', packet);
            readable = streams.readable.pipeThrough(
                new InspectStream((packet) => {
                    GLOBAL_STATE.appendLog("in", packet);
                })
            );

            writable = pipeFrom(
                streams.writable,
                new InspectStream((packet: AdbPacketInit) => {
                    GlobalState.appendLog('out', packet);
                    GLOBAL_STATE.appendLog("out", packet);
                })
            );
        } catch (e: any) {
            GlobalState.showErrorDialog(e);
            GLOBAL_STATE.showErrorDialog(e);
            setConnecting(false);
            return;
        }
@@ -173,9 +204,13 @@ function _Connect(): JSX.Element | null {
        async function dispose() {
            // Adb won't close the streams,
            // so manually close them.
            try { readable.cancel(); } catch { }
            try { await writable.close(); } catch { }
            GlobalState.setDevice(undefined, undefined);
            try {
                readable.cancel();
            } catch {}
            try {
                await writable.close();
            } catch {}
            GLOBAL_STATE.setDevice(undefined, undefined);
        }

        try {
@@ -185,16 +220,19 @@ function _Connect(): JSX.Element | null {
                undefined
            );

            device.disconnected.then(async () => {
            device.disconnected.then(
                async () => {
                    await dispose();
            }, async (e) => {
                GlobalState.showErrorDialog(e);
                },
                async (e) => {
                    GLOBAL_STATE.showErrorDialog(e);
                    await dispose();
            });
                }
            );

            GlobalState.setDevice(selectedBackend, device);
            GLOBAL_STATE.setDevice(selectedBackend, device);
        } catch (e: any) {
            GlobalState.showErrorDialog(e);
            GLOBAL_STATE.showErrorDialog(e);
            await dispose();
        } finally {
            setConnecting(false);
@@ -203,29 +241,38 @@ function _Connect(): JSX.Element | null {

    const disconnect = useCallback(async () => {
        try {
            await GlobalState.device!.close();
            await GLOBAL_STATE.device!.close();
        } catch (e: any) {
            GlobalState.showErrorDialog(e);
            GLOBAL_STATE.showErrorDialog(e);
        }
    }, []);

    const backendList = useMemo(
        () => ([] as AdbBackend[]).concat(usbBackendList, wsBackendList, tcpBackendList),
        () =>
            ([] as AdbBackend[]).concat(
                usbBackendList,
                wsBackendList,
                tcpBackendList
            ),
        [usbBackendList, wsBackendList, tcpBackendList]
    );

    const backendOptions = useMemo(() => {
        return backendList.map(backend => ({
        return backendList.map((backend) => ({
            key: backend.serial,
            text: `${backend.serial} ${backend.name ? `(${backend.name})` : ''}`,
            text: `${backend.serial} ${
                backend.name ? `(${backend.name})` : ""
            }`,
            data: backend,
        }));
    }, [backendList]);

    useEffect(() => {
        setSelectedBackend(old => {
        setSelectedBackend((old) => {
            if (old) {
                const current = backendList.find(backend => backend.serial === old.serial);
                const current = backendList.find(
                    (backend) => backend.serial === old.serial
                );
                if (current) {
                    return current;
                }
@@ -239,21 +286,21 @@ function _Connect(): JSX.Element | null {
        const items = [];

        items.push({
            key: 'usb',
            text: 'USB',
            key: "usb",
            text: "USB",
            onClick: addUsbBackend,
        });

        items.push({
            key: 'websocket',
            text: 'WebSocket',
            key: "websocket",
            text: "WebSocket",
            onClick: addWsBackend,
        });

        if (AdbDirectSocketsBackend.isSupported()) {
            items.push({
                key: 'direct-sockets',
                text: 'Direct Sockets TCP',
                key: "direct-sockets",
                text: "Direct Sockets TCP",
                onClick: addTcpBackend,
            });
        }
@@ -264,11 +311,9 @@ function _Connect(): JSX.Element | null {
    }, [addUsbBackend, addWsBackend, addTcpBackend]);

    return (
        <Stack
            tokens={{ childrenGap: 8, padding: '0 0 8px 8px' }}
        >
        <Stack tokens={{ childrenGap: 8, padding: "0 0 8px 8px" }}>
            <Dropdown
                disabled={!!GlobalState.device || backendOptions.length === 0}
                disabled={!!GLOBAL_STATE.device || backendOptions.length === 0}
                label="Available devices"
                placeholder="No available devices"
                options={backendOptions}
@@ -278,8 +323,7 @@ function _Connect(): JSX.Element | null {
                onChange={handleSelectedBackendChange}
            />

            {!GlobalState.device
                ? (
            {!GLOBAL_STATE.device ? (
                <Stack horizontal tokens={CommonStackTokens}>
                    <StackItem grow shrink>
                        <PrimaryButton
@@ -287,7 +331,7 @@ function _Connect(): JSX.Element | null {
                            text="Connect"
                            disabled={!selectedBackend}
                            primary={!!selectedBackend}
                                styles={{ root: { width: '100%' } }}
                            styles={{ root: { width: "100%" } }}
                            onClick={connect}
                        />
                    </StackItem>
@@ -300,13 +344,12 @@ function _Connect(): JSX.Element | null {
                            menuProps={addMenuProps}
                            disabled={!supported}
                            primary={!selectedBackend}
                                styles={{ root: { width: '100%' } }}
                            styles={{ root: { width: "100%" } }}
                            onClick={addUsbBackend}
                        />
                    </StackItem>
                </Stack>
                )
                : (
            ) : (
                <DefaultButton
                    iconProps={{ iconName: Icons.PlugDisconnected }}
                    text="Disconnect"
@@ -317,14 +360,14 @@ function _Connect(): JSX.Element | null {
            <Dialog
                hidden={!connecting}
                dialogContentProps={{
                    title: 'Connecting...',
                    subText: 'Please authorize the connection on your device'
                    title: "Connecting...",
                    subText: "Please authorize the connection on your device",
                }}
            >
                <ProgressIndicator />
            </Dialog>
        </Stack>
    );
};
}

export const Connect = observer(_Connect);
+210 −161

File changed.

Preview size limit exceeded, changes collapsed.

+15 −7
Original line number Diff line number Diff line
import { Dialog, DialogFooter, DialogType, PrimaryButton } from '@fluentui/react';
import {
    Dialog,
    DialogFooter,
    DialogType,
    PrimaryButton,
} from "@fluentui/react";
import { observer } from "mobx-react-lite";
import { PropsWithChildren } from 'react';
import { GlobalState } from '../state';
import { PropsWithChildren } from "react";
import { GLOBAL_STATE } from "../state";

export const ErrorDialogProvider = observer((props: PropsWithChildren<{}>) => {
    return (
@@ -9,15 +14,18 @@ export const ErrorDialogProvider = observer((props: PropsWithChildren<{}>) => {
            {props.children}

            <Dialog
                hidden={!GlobalState.errorDialogVisible}
                hidden={!GLOBAL_STATE.errorDialogVisible}
                dialogContentProps={{
                    type: DialogType.normal,
                    title: 'Error',
                    subText: GlobalState.errorDialogMessage,
                    title: "Error",
                    subText: GLOBAL_STATE.errorDialogMessage,
                }}
            >
                <DialogFooter>
                    <PrimaryButton text="OK" onClick={GlobalState.hideErrorDialog} />
                    <PrimaryButton
                        text="OK"
                        onClick={GLOBAL_STATE.hideErrorDialog}
                    />
                </DialogFooter>
            </Dialog>
        </>
+5 −4
Original line number Diff line number Diff line
@@ -10,12 +10,13 @@ import {
} from "@yume-chan/scrcpy";
import { action, computed } from "mobx";
import { observer } from "mobx-react-lite";
import { GlobalState } from "../../state";
import { GLOBAL_STATE } from "../../state";
import { Icons } from "../../utils";
import { ExternalLink } from "../external-link";
import { CommandBarSpacerItem } from "./command-bar-spacer-item";
import { RECORD_STATE } from "./recorder";
import { STATE } from "./state";
import { SETTING_STATE } from "./settings";

const ITEMS = computed(() => {
    const result: ICommandBarItemProps[] = [];
@@ -23,7 +24,7 @@ const ITEMS = computed(() => {
    if (!STATE.running) {
        result.push({
            key: "start",
            disabled: !GlobalState.device,
            disabled: !GLOBAL_STATE.device,
            iconProps: { iconName: Icons.Play },
            text: "Start",
            onClick: STATE.start as VoidFunction,
@@ -262,11 +263,11 @@ const ITEMS = computed(() => {
            key: "Settings",
            iconProps: { iconName: Icons.Settings },
            canCheck: true,
            checked: STATE.settingsVisible,
            checked: SETTING_STATE.settingsVisible,
            text: "Settings",
            iconOnly: true,
            onClick: action(() => {
                STATE.settingsVisible = !STATE.settingsVisible;
                SETTING_STATE.settingsVisible = !SETTING_STATE.settingsVisible;
            }),
        },
        {
Loading