Loading apps/demo/src/components/hex-viewer.tsx +102 −6 Original line number Diff line number Diff line import { makeStyles } from "@griffel/react"; import { makeStyles, mergeClasses } from "@griffel/react"; import { ReactNode, useMemo } from "react"; import { withDisplayName } from "../utils"; const useClasses = makeStyles({ root: { width: '100%', height: '100%', overflowY: 'auto', }, flex: { display: 'flex', }, cell: { fontFamily: '"Cascadia Code", Consolas, monospace', }, lineNumber: { textAlign: 'right', }, hex: { marginLeft: '40px', }, }); export interface HexViewer { const PRINTABLE_CHARACTERS: [number, number][] = [ [33, 126], [161, 172], [174, 255], ]; export function isPrintableCharacter(code: number) { return PRINTABLE_CHARACTERS.some( ([start, end]) => code >= start && code <= end ); } export function toCharacter(code: number) { if (isPrintableCharacter(code)) return String.fromCharCode(code); return '.'; } export function toText(data: Uint8Array) { let result = ''; for (const code of data) { result += toCharacter(code); } return result; } const PER_ROW = 16; export interface HexViewerProps { className?: string; data: Uint8Array; } export const HexViewer = withDisplayName('HexViewer')(({ }) => { className, data }: HexViewerProps) => { const classes = useClasses(); // Because ADB packets are usually small, // so don't add virtualization now. return ( const children = useMemo(() => { const lineNumbers: ReactNode[] = []; const hexRows: ReactNode[] = []; const textRows: ReactNode[] = []; for (let i = 0; i < data.length; i += PER_ROW) { lineNumbers.push( <div> {i.toString(16)} </div> ); let hex = ''; for (let j = i; j < i + PER_ROW && j < data.length; j++) { hex += data[j].toString(16).padStart(2, '0') + ' '; } hexRows.push( <div> {hex} </div> ); textRows.push( <div> {toText(data.slice(i, i + PER_ROW))} </div> ); } return { lineNumbers, hexRows, textRows, }; }, [data]); return ( <div className={mergeClasses(classes.root, className)}> <div className={classes.flex}> <div className={mergeClasses(classes.cell, classes.lineNumber)}> {children.lineNumbers} </div> <div className={mergeClasses(classes.cell, classes.hex)}> {children.hexRows} </div> <div className={mergeClasses(classes.cell, classes.hex)}> {children.textRows} </div> </div> </div> ); }); apps/demo/src/components/index.ts +1 −0 Original line number Diff line number Diff line Loading @@ -5,5 +5,6 @@ export * from './device-view'; export * from './error-dialog'; export * from './external-link'; export * from './grid'; export * from './hex-viewer'; export * from './log-view'; export * from './resize-observer'; apps/demo/src/pages/packet-log.tsx +72 −72 Original line number Diff line number Diff line import { ICommandBarItemProps, Stack, StackItem } from "@fluentui/react"; import { makeStyles, mergeClasses, shorthands } from "@griffel/react"; import { AdbCommand, decodeUtf8 } from "@yume-chan/adb"; import { makeAutoObservable } from "mobx"; import { autorun, makeAutoObservable, observable, runInAction } from "mobx"; import { observer } from "mobx-react-lite"; import { NextPage } from "next"; import Head from "next/head"; import { useMemo, useState } from "react"; import { CommandBar, Grid, GridCellProps, GridColumn, GridHeaderProps, GridRowProps } from "../components"; import { useMemo } from "react"; import { CommandBar, Grid, GridCellProps, GridColumn, GridHeaderProps, GridRowProps, HexViewer, toText } from "../components"; import { globalState, PacketLogItem } from "../state"; import { Icons, RouteStackProps, useCallbackRef, withDisplayName } from "../utils"; Loading @@ -19,40 +19,12 @@ const ADB_COMMAND_NAME = { [AdbCommand.Write]: 'WRTE', }; interface Column<T> extends GridColumn { interface Column extends GridColumn { title: string; } const LINE_HEIGHT = 32; const PRINTABLE_CHARACTERS: [number, number][] = [ [33, 126], [161, 172], [174, 255], ]; function isPrintableCharacter(code: number) { return PRINTABLE_CHARACTERS.some( ([start, end]) => code >= start && code <= end ); } function toCharacter(code: number) { if (isPrintableCharacter(code)) return String.fromCharCode(code); return '.'; } function toText(data: Uint8Array) { let result = ''; for (const code of data) { result += toCharacter(code); } return result; } const state = new class { get commandBarItems(): ICommandBarItemProps[] { return [ Loading @@ -66,12 +38,28 @@ const state = new class { ]; } selectedPacket: PacketLogItem | undefined = undefined; constructor() { makeAutoObservable(this); makeAutoObservable( this, { selectedPacket: observable.ref, } ); autorun(() => { if (globalState.logs.length === 0) { this.selectedPacket = undefined; } }); } }; const useClasses = makeStyles({ grow: { height: 0, }, grid: { height: '100%', }, Loading @@ -95,12 +83,16 @@ const useClasses = makeStyles({ cursor: 'default', ...shorthands.overflow('hidden'), }, hexViewer: { ...shorthands.padding('12px'), ...shorthands.borderTop('1px', 'solid', 'rgb(243, 242, 241)'), }, }); const PacketLog: NextPage = () => { const classes = useClasses(); const columns: Column<PacketLogItem>[] = useMemo(() => [ const columns: Column[] = useMemo(() => [ { key: 'direction', title: 'Direction', Loading Loading @@ -207,24 +199,32 @@ const PacketLog: NextPage = () => { }, ], [classes.code]); const [selectedRowIndex, setSelectedRowIndex] = useState(-1); const Header = useMemo(() => withDisplayName('Header')(({ className, columnIndex, ...rest }: GridHeaderProps) => { const Header = useMemo( () => withDisplayName('Header')(({ className, columnIndex, ...rest }: GridHeaderProps) => { return ( <div className={mergeClasses(className, classes.header)} {...rest}> {columns[columnIndex].title} </div> ); }), [classes.header, columns]); }), [classes.header, columns] ); const Row = useMemo(() => withDisplayName('Row')(({ const Row = useMemo( () => observer(function Row({ className, rowIndex, ...rest }: GridRowProps) => { }: GridRowProps) { /* eslint-disable-next-line */ const handleClick = useCallbackRef(() => { setSelectedRowIndex(rowIndex); runInAction(() => { state.selectedPacket = globalState.logs[rowIndex]; }); }); return ( Loading @@ -232,13 +232,15 @@ const PacketLog: NextPage = () => { className={mergeClasses( className, classes.row, selectedRowIndex === rowIndex && classes.selected state.selectedPacket === globalState.logs[rowIndex] && classes.selected )} onClick={handleClick} {...rest} /> ); }), [classes, selectedRowIndex]); }), [classes] ); return ( <Stack {...RouteStackProps} tokens={{}}> Loading @@ -248,7 +250,7 @@ const PacketLog: NextPage = () => { <CommandBar items={state.commandBarItems} /> <StackItem basis={0} grow> <StackItem className={classes.grow} grow> <Grid className={classes.grid} rowCount={globalState.logs.length} Loading @@ -259,13 +261,11 @@ const PacketLog: NextPage = () => { /> </StackItem> <StackItem grow> {selectedRowIndex !== -1 && ( <div> </div> )} {state.selectedPacket && state.selectedPacket.payload.length > 0 && ( <StackItem className={classes.grow} grow> <HexViewer className={classes.hexViewer} data={state.selectedPacket.payload} /> </StackItem> )} </Stack> ); }; Loading Loading
apps/demo/src/components/hex-viewer.tsx +102 −6 Original line number Diff line number Diff line import { makeStyles } from "@griffel/react"; import { makeStyles, mergeClasses } from "@griffel/react"; import { ReactNode, useMemo } from "react"; import { withDisplayName } from "../utils"; const useClasses = makeStyles({ root: { width: '100%', height: '100%', overflowY: 'auto', }, flex: { display: 'flex', }, cell: { fontFamily: '"Cascadia Code", Consolas, monospace', }, lineNumber: { textAlign: 'right', }, hex: { marginLeft: '40px', }, }); export interface HexViewer { const PRINTABLE_CHARACTERS: [number, number][] = [ [33, 126], [161, 172], [174, 255], ]; export function isPrintableCharacter(code: number) { return PRINTABLE_CHARACTERS.some( ([start, end]) => code >= start && code <= end ); } export function toCharacter(code: number) { if (isPrintableCharacter(code)) return String.fromCharCode(code); return '.'; } export function toText(data: Uint8Array) { let result = ''; for (const code of data) { result += toCharacter(code); } return result; } const PER_ROW = 16; export interface HexViewerProps { className?: string; data: Uint8Array; } export const HexViewer = withDisplayName('HexViewer')(({ }) => { className, data }: HexViewerProps) => { const classes = useClasses(); // Because ADB packets are usually small, // so don't add virtualization now. return ( const children = useMemo(() => { const lineNumbers: ReactNode[] = []; const hexRows: ReactNode[] = []; const textRows: ReactNode[] = []; for (let i = 0; i < data.length; i += PER_ROW) { lineNumbers.push( <div> {i.toString(16)} </div> ); let hex = ''; for (let j = i; j < i + PER_ROW && j < data.length; j++) { hex += data[j].toString(16).padStart(2, '0') + ' '; } hexRows.push( <div> {hex} </div> ); textRows.push( <div> {toText(data.slice(i, i + PER_ROW))} </div> ); } return { lineNumbers, hexRows, textRows, }; }, [data]); return ( <div className={mergeClasses(classes.root, className)}> <div className={classes.flex}> <div className={mergeClasses(classes.cell, classes.lineNumber)}> {children.lineNumbers} </div> <div className={mergeClasses(classes.cell, classes.hex)}> {children.hexRows} </div> <div className={mergeClasses(classes.cell, classes.hex)}> {children.textRows} </div> </div> </div> ); });
apps/demo/src/components/index.ts +1 −0 Original line number Diff line number Diff line Loading @@ -5,5 +5,6 @@ export * from './device-view'; export * from './error-dialog'; export * from './external-link'; export * from './grid'; export * from './hex-viewer'; export * from './log-view'; export * from './resize-observer';
apps/demo/src/pages/packet-log.tsx +72 −72 Original line number Diff line number Diff line import { ICommandBarItemProps, Stack, StackItem } from "@fluentui/react"; import { makeStyles, mergeClasses, shorthands } from "@griffel/react"; import { AdbCommand, decodeUtf8 } from "@yume-chan/adb"; import { makeAutoObservable } from "mobx"; import { autorun, makeAutoObservable, observable, runInAction } from "mobx"; import { observer } from "mobx-react-lite"; import { NextPage } from "next"; import Head from "next/head"; import { useMemo, useState } from "react"; import { CommandBar, Grid, GridCellProps, GridColumn, GridHeaderProps, GridRowProps } from "../components"; import { useMemo } from "react"; import { CommandBar, Grid, GridCellProps, GridColumn, GridHeaderProps, GridRowProps, HexViewer, toText } from "../components"; import { globalState, PacketLogItem } from "../state"; import { Icons, RouteStackProps, useCallbackRef, withDisplayName } from "../utils"; Loading @@ -19,40 +19,12 @@ const ADB_COMMAND_NAME = { [AdbCommand.Write]: 'WRTE', }; interface Column<T> extends GridColumn { interface Column extends GridColumn { title: string; } const LINE_HEIGHT = 32; const PRINTABLE_CHARACTERS: [number, number][] = [ [33, 126], [161, 172], [174, 255], ]; function isPrintableCharacter(code: number) { return PRINTABLE_CHARACTERS.some( ([start, end]) => code >= start && code <= end ); } function toCharacter(code: number) { if (isPrintableCharacter(code)) return String.fromCharCode(code); return '.'; } function toText(data: Uint8Array) { let result = ''; for (const code of data) { result += toCharacter(code); } return result; } const state = new class { get commandBarItems(): ICommandBarItemProps[] { return [ Loading @@ -66,12 +38,28 @@ const state = new class { ]; } selectedPacket: PacketLogItem | undefined = undefined; constructor() { makeAutoObservable(this); makeAutoObservable( this, { selectedPacket: observable.ref, } ); autorun(() => { if (globalState.logs.length === 0) { this.selectedPacket = undefined; } }); } }; const useClasses = makeStyles({ grow: { height: 0, }, grid: { height: '100%', }, Loading @@ -95,12 +83,16 @@ const useClasses = makeStyles({ cursor: 'default', ...shorthands.overflow('hidden'), }, hexViewer: { ...shorthands.padding('12px'), ...shorthands.borderTop('1px', 'solid', 'rgb(243, 242, 241)'), }, }); const PacketLog: NextPage = () => { const classes = useClasses(); const columns: Column<PacketLogItem>[] = useMemo(() => [ const columns: Column[] = useMemo(() => [ { key: 'direction', title: 'Direction', Loading Loading @@ -207,24 +199,32 @@ const PacketLog: NextPage = () => { }, ], [classes.code]); const [selectedRowIndex, setSelectedRowIndex] = useState(-1); const Header = useMemo(() => withDisplayName('Header')(({ className, columnIndex, ...rest }: GridHeaderProps) => { const Header = useMemo( () => withDisplayName('Header')(({ className, columnIndex, ...rest }: GridHeaderProps) => { return ( <div className={mergeClasses(className, classes.header)} {...rest}> {columns[columnIndex].title} </div> ); }), [classes.header, columns]); }), [classes.header, columns] ); const Row = useMemo(() => withDisplayName('Row')(({ const Row = useMemo( () => observer(function Row({ className, rowIndex, ...rest }: GridRowProps) => { }: GridRowProps) { /* eslint-disable-next-line */ const handleClick = useCallbackRef(() => { setSelectedRowIndex(rowIndex); runInAction(() => { state.selectedPacket = globalState.logs[rowIndex]; }); }); return ( Loading @@ -232,13 +232,15 @@ const PacketLog: NextPage = () => { className={mergeClasses( className, classes.row, selectedRowIndex === rowIndex && classes.selected state.selectedPacket === globalState.logs[rowIndex] && classes.selected )} onClick={handleClick} {...rest} /> ); }), [classes, selectedRowIndex]); }), [classes] ); return ( <Stack {...RouteStackProps} tokens={{}}> Loading @@ -248,7 +250,7 @@ const PacketLog: NextPage = () => { <CommandBar items={state.commandBarItems} /> <StackItem basis={0} grow> <StackItem className={classes.grow} grow> <Grid className={classes.grid} rowCount={globalState.logs.length} Loading @@ -259,13 +261,11 @@ const PacketLog: NextPage = () => { /> </StackItem> <StackItem grow> {selectedRowIndex !== -1 && ( <div> </div> )} {state.selectedPacket && state.selectedPacket.payload.length > 0 && ( <StackItem className={classes.grow} grow> <HexViewer className={classes.hexViewer} data={state.selectedPacket.payload} /> </StackItem> )} </Stack> ); }; Loading