Loading apps/demo/src/components/grid.tsx +1 −1 Original line number Diff line number Diff line Loading @@ -175,7 +175,7 @@ export const Grid = withDisplayName('Grid')(({ if (scrollTop < bodyRef.scrollHeight - bodyRef.clientHeight && bodyRef.scrollTop < scrollTop) { setAutoScroll(false); } } else if (bodyRef.scrollTop + bodyRef.offsetHeight >= bodyRef.scrollHeight - 50) { } else if (bodyRef.scrollTop + bodyRef.offsetHeight >= bodyRef.scrollHeight - 10) { setAutoScroll(true); } Loading apps/demo/src/pages/logcat.tsx +56 −4 Original line number Diff line number Diff line import { ICommandBarItemProps, Stack, StackItem } from "@fluentui/react"; import { Checkbox, ICommandBarItemProps, Stack, StackItem } from "@fluentui/react"; import { makeStyles, mergeClasses, shorthands } from "@griffel/react"; import { AndroidLogEntry, AndroidLogPriority, Logcat } from '@yume-chan/android-bin'; import { AndroidLogEntry, AndroidLogPriority, formatAndroidLogEntry, Logcat, LogcatFormat } from '@yume-chan/android-bin'; import { BTree } from '@yume-chan/b-tree'; import { AbortController, ReadableStream, WritableStream } from '@yume-chan/stream-extra'; import { action, autorun, makeAutoObservable, observable, runInAction } from "mobx"; import { observer } from "mobx-react-lite"; import { NextPage } from "next"; import Head from "next/head"; import { FormEvent, useEffect, useState } from 'react'; import { CommandBar, Grid, GridColumn, GridHeaderProps, GridRowProps } from "../components"; import { GlobalState } from "../state"; Loading Loading @@ -55,6 +57,7 @@ const state = makeAutoObservable({ buffer: [] as LogRow[], flushRequested: false, list: [] as LogRow[], selection: new BTree(6), count: 0, stream: undefined as ReadableStream<AndroidLogEntry> | undefined, stopSignal: undefined as AbortController | undefined, Loading Loading @@ -98,6 +101,7 @@ const state = makeAutoObservable({ }, clear() { this.list = []; this.selection.clear(); this.selectedCount = 0; }, get empty() { Loading Loading @@ -130,7 +134,16 @@ const state = makeAutoObservable({ disabled: this.selectedCount === 0, iconProps: { iconName: Icons.Copy }, onClick: () => { let text = ''; for (const index of this.selection) { text += formatAndroidLogEntry( this.list[index], LogcatFormat.Brief ) + '\n'; } // Chrome on Windows can't copy null characters text = text.replace(/\u0000/g, ''); navigator.clipboard.writeText(text); } }, { Loading @@ -139,13 +152,52 @@ const state = makeAutoObservable({ disabled: this.selectedCount === 0, iconProps: { iconName: Icons.Copy }, onClick: () => { let text = ''; for (const index of this.selection) { text += this.list[index].message + '\n'; } // Chrome on Windows can't copy null characters text = text.replace(/\u0000/g, ''); navigator.clipboard.writeText(text); } } ]; }, get columns(): Column[] { return [ { width: 40, title: '', CellComponent: ({ rowIndex, columnIndex, className, ...rest }) => { const [checked, setChecked] = useState(false); useEffect(() => { setChecked(this.selection.has(rowIndex)); }, [rowIndex]); const handleChange = useStableCallback((e?: FormEvent<EventTarget>, checked?: boolean) => { if (checked === undefined) { return; } if (checked) { this.selection.add(rowIndex); setChecked(true); } else { this.selection.delete(rowIndex); setChecked(false); } runInAction(() => { // Trigger mobx this.selectedCount = this.selection.size; }); }); return ( <Stack className={className} verticalAlign='center' horizontalAlign='center' {...rest}> <Checkbox checked={checked} onChange={handleChange} /> </Stack> ); } }, { width: 200, title: 'Time', Loading apps/demo/src/utils/icons.tsx +1 −0 Original line number Diff line number Diff line Loading @@ -50,6 +50,7 @@ export function register() { WindowConsole: <WindowConsoleRegular style={STYLE} />, // Required by @fluentui/react Checkmark: <CheckmarkRegular style={STYLE} />, StatusCircleCheckmark: <CheckmarkRegular style={STYLE} />, ChevronUpSmall: <ChevronUpRegular style={STYLE} />, ChevronDownSmall: <ChevronDownRegular style={STYLE} />, Loading libraries/android-bin/src/logcat.ts +52 −1 Original line number Diff line number Diff line Loading @@ -25,6 +25,7 @@ export enum AndroidLogPriority { Unknown, Default, Verbose, Debug, Info, Warn, Error, Loading @@ -32,6 +33,41 @@ export enum AndroidLogPriority { Silent, } // https://cs.android.com/android/platform/superproject/+/master:system/logging/liblog/logprint.cpp;l=140;drc=8dbf3b2bb6b6d1652d9797e477b9abd03278bb79 export const AndroidLogPriorityToCharacter: Record<AndroidLogPriority, string> = { [AndroidLogPriority.Unknown]: '?', [AndroidLogPriority.Default]: '?', [AndroidLogPriority.Verbose]: 'V', [AndroidLogPriority.Debug]: 'D', [AndroidLogPriority.Info]: 'I', [AndroidLogPriority.Warn]: 'W', [AndroidLogPriority.Error]: 'E', [AndroidLogPriority.Fatal]: 'F', [AndroidLogPriority.Silent]: 'S', }; export enum LogcatFormat { Brief, Process, Tag, Thread, Raw, Time, ThreadTime, Long } export interface LogcatFormatModifiers { usec?: boolean; printable?: boolean; year?: boolean; zone?: boolean; epoch?: boolean; monotonic?: boolean; uid?: boolean; descriptive?: boolean; } export interface LogcatOptions { pid?: number; ids?: LogId[]; Loading Loading @@ -65,6 +101,21 @@ export interface AndroidLogEntry extends LoggerEntry { message: string; } // https://cs.android.com/android/platform/superproject/+/master:system/logging/liblog/logprint.cpp;l=1415;drc=8dbf3b2bb6b6d1652d9797e477b9abd03278bb79 export function formatAndroidLogEntry( entry: AndroidLogEntry, format: LogcatFormat = LogcatFormat.Brief, modifier?: LogcatFormatModifiers ) { const uid = modifier?.uid ? `${entry.uid.toString().padStart(5)}:` : ''; switch (format) { // TODO: implement other formats default: return `${AndroidLogPriorityToCharacter[entry.priority]}/${entry.tag.padEnd(8)}(${uid}${entry.pid.toString().padStart(5)}): ${entry.message}`; } } function findTagEnd(payload: Uint8Array) { for (const separator of [0, ' '.charCodeAt(0), ':'.charCodeAt(0)]) { const index = payload.indexOf(separator); Loading Loading @@ -200,6 +251,6 @@ export class Logcat extends AdbCommandBase { return stdout; }).pipeThrough(new BufferedTransformStream(stream => { return deserializeAndroidLogEntry(stream); })) })); } } libraries/b-tree/src/index.spec.ts +4 −4 Original line number Diff line number Diff line Loading @@ -55,13 +55,13 @@ describe('BTree', () => { const values = Array.from({ length: LENGTH }, (_, i) => i - LENGTH / 2); for (let value of values) { tree.insert(value); tree.add(value); validateTree(tree); expect(tree.has(value)).toBe(true); } for (let value of values) { tree.remove(value); tree.delete(value); validateTree(tree); expect(tree.has(value)).toBe(false); } Loading @@ -73,14 +73,14 @@ describe('BTree', () => { const values = Array.from({ length: LENGTH }, (_, i) => i - LENGTH / 2); shuffle(values); for (const value of values) { tree.insert(value); tree.add(value); validateTree(tree); expect(tree.has(value)).toBe(true); } shuffle(values); for (const value of values) { tree.remove(value); tree.delete(value); validateTree(tree); expect(tree.has(value)).toBe(false); } Loading Loading
apps/demo/src/components/grid.tsx +1 −1 Original line number Diff line number Diff line Loading @@ -175,7 +175,7 @@ export const Grid = withDisplayName('Grid')(({ if (scrollTop < bodyRef.scrollHeight - bodyRef.clientHeight && bodyRef.scrollTop < scrollTop) { setAutoScroll(false); } } else if (bodyRef.scrollTop + bodyRef.offsetHeight >= bodyRef.scrollHeight - 50) { } else if (bodyRef.scrollTop + bodyRef.offsetHeight >= bodyRef.scrollHeight - 10) { setAutoScroll(true); } Loading
apps/demo/src/pages/logcat.tsx +56 −4 Original line number Diff line number Diff line import { ICommandBarItemProps, Stack, StackItem } from "@fluentui/react"; import { Checkbox, ICommandBarItemProps, Stack, StackItem } from "@fluentui/react"; import { makeStyles, mergeClasses, shorthands } from "@griffel/react"; import { AndroidLogEntry, AndroidLogPriority, Logcat } from '@yume-chan/android-bin'; import { AndroidLogEntry, AndroidLogPriority, formatAndroidLogEntry, Logcat, LogcatFormat } from '@yume-chan/android-bin'; import { BTree } from '@yume-chan/b-tree'; import { AbortController, ReadableStream, WritableStream } from '@yume-chan/stream-extra'; import { action, autorun, makeAutoObservable, observable, runInAction } from "mobx"; import { observer } from "mobx-react-lite"; import { NextPage } from "next"; import Head from "next/head"; import { FormEvent, useEffect, useState } from 'react'; import { CommandBar, Grid, GridColumn, GridHeaderProps, GridRowProps } from "../components"; import { GlobalState } from "../state"; Loading Loading @@ -55,6 +57,7 @@ const state = makeAutoObservable({ buffer: [] as LogRow[], flushRequested: false, list: [] as LogRow[], selection: new BTree(6), count: 0, stream: undefined as ReadableStream<AndroidLogEntry> | undefined, stopSignal: undefined as AbortController | undefined, Loading Loading @@ -98,6 +101,7 @@ const state = makeAutoObservable({ }, clear() { this.list = []; this.selection.clear(); this.selectedCount = 0; }, get empty() { Loading Loading @@ -130,7 +134,16 @@ const state = makeAutoObservable({ disabled: this.selectedCount === 0, iconProps: { iconName: Icons.Copy }, onClick: () => { let text = ''; for (const index of this.selection) { text += formatAndroidLogEntry( this.list[index], LogcatFormat.Brief ) + '\n'; } // Chrome on Windows can't copy null characters text = text.replace(/\u0000/g, ''); navigator.clipboard.writeText(text); } }, { Loading @@ -139,13 +152,52 @@ const state = makeAutoObservable({ disabled: this.selectedCount === 0, iconProps: { iconName: Icons.Copy }, onClick: () => { let text = ''; for (const index of this.selection) { text += this.list[index].message + '\n'; } // Chrome on Windows can't copy null characters text = text.replace(/\u0000/g, ''); navigator.clipboard.writeText(text); } } ]; }, get columns(): Column[] { return [ { width: 40, title: '', CellComponent: ({ rowIndex, columnIndex, className, ...rest }) => { const [checked, setChecked] = useState(false); useEffect(() => { setChecked(this.selection.has(rowIndex)); }, [rowIndex]); const handleChange = useStableCallback((e?: FormEvent<EventTarget>, checked?: boolean) => { if (checked === undefined) { return; } if (checked) { this.selection.add(rowIndex); setChecked(true); } else { this.selection.delete(rowIndex); setChecked(false); } runInAction(() => { // Trigger mobx this.selectedCount = this.selection.size; }); }); return ( <Stack className={className} verticalAlign='center' horizontalAlign='center' {...rest}> <Checkbox checked={checked} onChange={handleChange} /> </Stack> ); } }, { width: 200, title: 'Time', Loading
apps/demo/src/utils/icons.tsx +1 −0 Original line number Diff line number Diff line Loading @@ -50,6 +50,7 @@ export function register() { WindowConsole: <WindowConsoleRegular style={STYLE} />, // Required by @fluentui/react Checkmark: <CheckmarkRegular style={STYLE} />, StatusCircleCheckmark: <CheckmarkRegular style={STYLE} />, ChevronUpSmall: <ChevronUpRegular style={STYLE} />, ChevronDownSmall: <ChevronDownRegular style={STYLE} />, Loading
libraries/android-bin/src/logcat.ts +52 −1 Original line number Diff line number Diff line Loading @@ -25,6 +25,7 @@ export enum AndroidLogPriority { Unknown, Default, Verbose, Debug, Info, Warn, Error, Loading @@ -32,6 +33,41 @@ export enum AndroidLogPriority { Silent, } // https://cs.android.com/android/platform/superproject/+/master:system/logging/liblog/logprint.cpp;l=140;drc=8dbf3b2bb6b6d1652d9797e477b9abd03278bb79 export const AndroidLogPriorityToCharacter: Record<AndroidLogPriority, string> = { [AndroidLogPriority.Unknown]: '?', [AndroidLogPriority.Default]: '?', [AndroidLogPriority.Verbose]: 'V', [AndroidLogPriority.Debug]: 'D', [AndroidLogPriority.Info]: 'I', [AndroidLogPriority.Warn]: 'W', [AndroidLogPriority.Error]: 'E', [AndroidLogPriority.Fatal]: 'F', [AndroidLogPriority.Silent]: 'S', }; export enum LogcatFormat { Brief, Process, Tag, Thread, Raw, Time, ThreadTime, Long } export interface LogcatFormatModifiers { usec?: boolean; printable?: boolean; year?: boolean; zone?: boolean; epoch?: boolean; monotonic?: boolean; uid?: boolean; descriptive?: boolean; } export interface LogcatOptions { pid?: number; ids?: LogId[]; Loading Loading @@ -65,6 +101,21 @@ export interface AndroidLogEntry extends LoggerEntry { message: string; } // https://cs.android.com/android/platform/superproject/+/master:system/logging/liblog/logprint.cpp;l=1415;drc=8dbf3b2bb6b6d1652d9797e477b9abd03278bb79 export function formatAndroidLogEntry( entry: AndroidLogEntry, format: LogcatFormat = LogcatFormat.Brief, modifier?: LogcatFormatModifiers ) { const uid = modifier?.uid ? `${entry.uid.toString().padStart(5)}:` : ''; switch (format) { // TODO: implement other formats default: return `${AndroidLogPriorityToCharacter[entry.priority]}/${entry.tag.padEnd(8)}(${uid}${entry.pid.toString().padStart(5)}): ${entry.message}`; } } function findTagEnd(payload: Uint8Array) { for (const separator of [0, ' '.charCodeAt(0), ':'.charCodeAt(0)]) { const index = payload.indexOf(separator); Loading Loading @@ -200,6 +251,6 @@ export class Logcat extends AdbCommandBase { return stdout; }).pipeThrough(new BufferedTransformStream(stream => { return deserializeAndroidLogEntry(stream); })) })); } }
libraries/b-tree/src/index.spec.ts +4 −4 Original line number Diff line number Diff line Loading @@ -55,13 +55,13 @@ describe('BTree', () => { const values = Array.from({ length: LENGTH }, (_, i) => i - LENGTH / 2); for (let value of values) { tree.insert(value); tree.add(value); validateTree(tree); expect(tree.has(value)).toBe(true); } for (let value of values) { tree.remove(value); tree.delete(value); validateTree(tree); expect(tree.has(value)).toBe(false); } Loading @@ -73,14 +73,14 @@ describe('BTree', () => { const values = Array.from({ length: LENGTH }, (_, i) => i - LENGTH / 2); shuffle(values); for (const value of values) { tree.insert(value); tree.add(value); validateTree(tree); expect(tree.has(value)).toBe(true); } shuffle(values); for (const value of values) { tree.remove(value); tree.delete(value); validateTree(tree); expect(tree.has(value)).toBe(false); } Loading