• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2022 The Pigweed Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License"); you may not
4// use this file except in compliance with the License. You may obtain a copy of
5// the License at
6//
7//     https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12// License for the specific language governing permissions and limitations under
13// the License.
14
15import {useEffect, useRef, useState} from "react";
16import {pw_tokenizer, Device} from "pigweedjs";
17import {AutoSizer, Table, Column} from 'react-virtualized';
18import {listenToDefaultLogService} from "../common/logService";
19import 'react-virtualized/styles.css';
20import styles from "../styles/log.module.css";
21
22type Detokenizer = pw_tokenizer.Detokenizer;
23
24interface LogProps {
25  device: Device | undefined,
26  tokenDB: string | undefined
27}
28
29interface LogEntry {
30  msg: string,
31  timestamp: number,
32  humanTime: string,
33  module: string,
34  file: string
35}
36
37function parseLogMsg(msg: string): LogEntry {
38  const pairs = msg.split("■").slice(1).map(pair => pair.split("♦"));
39
40  // Not a valid message, print as-is.
41  if (pairs.length === 0) {
42    return {
43      msg,
44      module: "",
45      file: "",
46      timestamp: Date.now(),
47      humanTime: new Date(Date.now()).toLocaleTimeString("en-US")
48    }
49  }
50
51  let map: any = {};
52  pairs.forEach(pair => map[pair[0]] = pair[1])
53  return {
54    msg: map.msg,
55    module: map.module,
56    file: map.file,
57    timestamp: Date.now(),
58    humanTime: new Date(Date.now()).toLocaleTimeString("en-US")
59  }
60}
61
62const keyToDisplayName: {[key: string]: string} = {
63  "msg": "Message",
64  "humanTime": "Time",
65  "module": "Module",
66  "file": "File"
67}
68
69export default function Log({device, tokenDB}: LogProps) {
70  const [logs, setLogs] = useState<LogEntry[]>([]);
71  const [detokenizer, setDetokenizer] = useState<Detokenizer | null>(null);
72  const logTable = useRef<Table | null>(null);
73  const _headerRenderer = ({dataKey, sortBy, sortDirection}: any) => {
74    return (
75      <div>
76        {keyToDisplayName[dataKey]}
77      </div>
78    );
79  }
80
81  const processFrame = (frame: Uint8Array) => {
82    if (detokenizer) {
83      const detokenized = detokenizer.detokenizeUint8Array(frame);
84      setLogs(oldLogs => [...oldLogs, parseLogMsg(detokenized)]);
85    }
86    else {
87      const decoded = new TextDecoder().decode(frame);
88      setLogs(oldLogs => [...oldLogs, parseLogMsg(decoded)]);
89    }
90    setTimeout(() => {
91      logTable.current!.scrollToRow(logs.length - 1);
92    }, 100);
93  }
94
95  useEffect(() => {
96    if (device) {
97      let cleanupFn: () => void;
98      listenToDefaultLogService(device, processFrame).then((unsub) => cleanupFn = unsub);
99      return () => {
100        if (cleanupFn) cleanupFn();
101      }
102    }
103  }, [device, detokenizer]);
104
105  useEffect(() => {
106    if (tokenDB && tokenDB.length > 0) {
107      const detokenizer = new pw_tokenizer.Detokenizer(tokenDB);
108      setDetokenizer(detokenizer);
109    }
110  }, [tokenDB])
111
112  return (
113    <>
114      {/* @ts-ignore */}
115      <AutoSizer>
116        {({height, width}) => (
117          <>
118            {/* @ts-ignore */}
119            <Table
120              className={styles.logsContainer}
121              headerHeight={40}
122              height={height}
123              rowCount={logs.length}
124              rowGetter={({index}) => logs[index]}
125              rowHeight={30}
126              ref={logTable}
127              width={width}
128            >
129              {/* @ts-ignore */}
130              <Column
131                dataKey="humanTime"
132                width={190}
133                headerRenderer={_headerRenderer}
134              />
135              {/* @ts-ignore */}
136              <Column
137                dataKey="msg"
138                flexGrow={1}
139                width={290}
140                headerRenderer={_headerRenderer}
141              />
142              {/* @ts-ignore */}
143              <Column
144                dataKey="module"
145                width={190}
146                headerRenderer={_headerRenderer}
147              />
148              {/* @ts-ignore */}
149              <Column
150                dataKey="file"
151                flexGrow={1}
152                width={190}
153                headerRenderer={_headerRenderer}
154              />
155            </Table>
156          </>
157        )}
158      </AutoSizer>
159    </>
160  )
161}
162