feat(demo): packet logger done

fix #397
This commit is contained in:
Simon Chan 2022-04-23 13:44:55 +08:00
parent 5f1fa1192d
commit f46ff36830
No known key found for this signature in database
GPG key ID: A8B69F750B9BCEDD
3 changed files with 175 additions and 78 deletions

View file

@ -1,24 +1,120 @@
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 (
<div>
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>
);
});

View file

@ -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';

View file

@ -1,12 +1,12 @@
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";
@ -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 [
@ -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%',
},
@ -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',
@ -207,38 +199,48 @@ const PacketLog: NextPage = () => {
},
], [classes.code]);
const [selectedRowIndex, setSelectedRowIndex] = useState(-1);
const Header = useMemo(
() => withDisplayName('Header')(({
className,
columnIndex,
...rest
}: GridHeaderProps) => {
return (
<div className={mergeClasses(className, classes.header)} {...rest}>
{columns[columnIndex].title}
</div>
);
}),
[classes.header, columns]
);
const Header = useMemo(() => withDisplayName('Header')(({ className, columnIndex, ...rest }: GridHeaderProps) => {
return (
<div className={mergeClasses(className, classes.header)} {...rest}>
{columns[columnIndex].title}
</div>
);
}), [classes.header, columns]);
const Row = useMemo(
() => observer(function Row({
className,
rowIndex,
...rest
}: GridRowProps) {
/* eslint-disable-next-line */
const handleClick = useCallbackRef(() => {
runInAction(() => {
state.selectedPacket = globalState.logs[rowIndex];
});
});
const Row = useMemo(() => withDisplayName('Row')(({
className,
rowIndex,
...rest
}: GridRowProps) => {
/* eslint-disable-next-line */
const handleClick = useCallbackRef(() => {
setSelectedRowIndex(rowIndex);
});
return (
<div
className={mergeClasses(
className,
classes.row,
selectedRowIndex === rowIndex && classes.selected
)}
onClick={handleClick}
{...rest}
/>
);
}), [classes, selectedRowIndex]);
return (
<div
className={mergeClasses(
className,
classes.row,
state.selectedPacket === globalState.logs[rowIndex] && classes.selected
)}
onClick={handleClick}
{...rest}
/>
);
}),
[classes]
);
return (
<Stack {...RouteStackProps} tokens={{}}>
@ -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}
@ -259,13 +261,11 @@ const PacketLog: NextPage = () => {
/>
</StackItem>
<StackItem grow>
{selectedRowIndex !== -1 && (
<div>
</div>
)}
</StackItem>
{state.selectedPacket && state.selectedPacket.payload.length > 0 && (
<StackItem className={classes.grow} grow>
<HexViewer className={classes.hexViewer} data={state.selectedPacket.payload} />
</StackItem>
)}
</Stack>
);
};