From 14ddf846425e3de5ec44dbf2fb100a243adf249d Mon Sep 17 00:00:00 2001 From: Markus Rudolph Date: Sat, 15 Apr 2023 20:00:48 +0200 Subject: [PATCH] Hex & JSON view (#14) * Add hex view * Fix JSON --- gh-pages/src/components/Breadcrumb.tsx | 2 +- gh-pages/src/components/ChunkJsonViewer.tsx | 27 +++++ gh-pages/src/components/HexViewer.tsx | 100 ++++++++++++++++++ gh-pages/src/components/Inspector.tsx | 5 +- gh-pages/src/components/SearchBar.tsx | 10 +- .../src/components/iWorkArchiveInspector.tsx | 37 +++++-- .../src/components/statemachine/actions.ts | 9 +- .../src/components/statemachine/reducer.ts | 23 +++- .../src/components/statemachine/states.ts | 2 + gh-pages/tsconfig.json | 3 +- 10 files changed, 200 insertions(+), 18 deletions(-) create mode 100644 gh-pages/src/components/ChunkJsonViewer.tsx create mode 100644 gh-pages/src/components/HexViewer.tsx diff --git a/gh-pages/src/components/Breadcrumb.tsx b/gh-pages/src/components/Breadcrumb.tsx index d04a054..cfbe8a0 100644 --- a/gh-pages/src/components/Breadcrumb.tsx +++ b/gh-pages/src/components/Breadcrumb.tsx @@ -13,7 +13,7 @@ export function Breadcrumb({ filePath }: BreadcrumbProps) {
  • {index === 0 && } - {index !== 0 && } + {index !== 0 && } {p}
  • diff --git a/gh-pages/src/components/ChunkJsonViewer.tsx b/gh-pages/src/components/ChunkJsonViewer.tsx new file mode 100644 index 0000000..7d092a5 --- /dev/null +++ b/gh-pages/src/components/ChunkJsonViewer.tsx @@ -0,0 +1,27 @@ +import { asJson, KeynoteArchives, splitObjectsAs } from "keynote-archives"; +import { useEffect, useState } from "react"; + +export interface ChunkJsonViewerProps { + data: Uint8Array; +} + +export function ChunkJsonViewer({data}: ChunkJsonViewerProps) { + const [list, setList] = useState(() => []); + + useEffect(() => { + (async function() { + try{ + for await (const obj of splitObjectsAs(data, KeynoteArchives)) { + const jsons = obj.messages.map(v => asJson(v.data)); + setList(previous => [...previous, ...jsons]); + } + } catch(e) { + setList(['Error:'+(e as any['message'])]); + } + })(); + }, []); + + return
    + {list.map((v, i) =>
    {v}
    )} +
    ; +} \ No newline at end of file diff --git a/gh-pages/src/components/HexViewer.tsx b/gh-pages/src/components/HexViewer.tsx new file mode 100644 index 0000000..0c39572 --- /dev/null +++ b/gh-pages/src/components/HexViewer.tsx @@ -0,0 +1,100 @@ +import clsx from "clsx"; +import { padStart } from "lodash"; +import { useMemo } from "react"; + +export interface HexRegion { + startAddress: number; + length: number; + color: string; +} + +export interface HexViewerProps { + startAddress: number; + data: Uint8Array; + regions: HexRegion[]; +} + +export interface HexRowProps { + address: number; + data: number[]; + from: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7; + to: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7; +} + +export function HexRow({ address, data, from, to }: HexRowProps) { + if(data.length !== 8) { + data = [...data, ...new Array(8 - data.length).fill(0)]; + } + return ( +
    + + 0x{address.toString(16).padStart(6, "0")}  + + {data.map((d, index) => { + return ( + to, + })} + > + {d.toString(16).padStart(2, "0")} +   + + ); + })} + {data.map((d, index) => { + return ( + to, + })} + > + {d < 32 || d > 126 ? "." : String.fromCharCode(d)} + + ); + })} +
    + ); +} + +export function HexViewer({ startAddress, data }: HexViewerProps) { + //Address (2+6, 0x123456) | Hex (8x2) | ASCII (8) + const rows = useMemo(() => { + const rows: HexRowProps[] = []; + const mod = startAddress % 8; + let address = startAddress - mod; + if (mod > 0) { + const rowData = new Array(8).fill(0); + for (let i = mod; i < 8; i++) { + rowData[i] = data[i - mod]; + } + rows.push({ + address, + data: rowData, + from: mod as any, + to: 7, + }); + address += 8; + } + for (let i = 0; i < data.length; i += 8) { + const slice = [...data.slice(i + mod, i + 8)]; + rows.push({ + address, + data: slice, + from: 0, + to: (slice.length-1) as any, + }); + address += 8; + } + return rows; + }, [startAddress, data]); + return ( +
    + {rows.map((row) => ( + + ))} +
    + ); +} diff --git a/gh-pages/src/components/Inspector.tsx b/gh-pages/src/components/Inspector.tsx index 47e36ed..bfeee9a 100644 --- a/gh-pages/src/components/Inspector.tsx +++ b/gh-pages/src/components/Inspector.tsx @@ -88,7 +88,10 @@ export function Inspector({ name, url, onUnload }: InspectorProps) { {f.type === "image" && ( )} - {f.type === "iwa" && } + {f.type === "iwa" && dispatch({ + type: "toggle-file", + path: f.path, + })} />} ))} diff --git a/gh-pages/src/components/SearchBar.tsx b/gh-pages/src/components/SearchBar.tsx index cecb58b..0511cc2 100644 --- a/gh-pages/src/components/SearchBar.tsx +++ b/gh-pages/src/components/SearchBar.tsx @@ -37,9 +37,9 @@ export function SearchBar({ category, onCategoryChange }: SearchBarProps) { xmlns="http://www.w3.org/2000/svg" > @@ -94,9 +94,9 @@ export function SearchBar({ category, onCategoryChange }: SearchBarProps) { xmlns="http://www.w3.org/2000/svg" > diff --git a/gh-pages/src/components/iWorkArchiveInspector.tsx b/gh-pages/src/components/iWorkArchiveInspector.tsx index 6edc142..70311d9 100644 --- a/gh-pages/src/components/iWorkArchiveInspector.tsx +++ b/gh-pages/src/components/iWorkArchiveInspector.tsx @@ -1,13 +1,38 @@ -import { Fragment} from 'react' +import { Fragment, useEffect } from "react"; +import { ChunkJsonViewer } from "./ChunkJsonViewer"; +import { HexViewer } from "./HexViewer"; import { IwaFileState } from "./statemachine/states"; export interface IWorkArchiveInspectorProps { file: IwaFileState; + onToggle: () => void; } -export function IWorkArchiveInspector({file}: IWorkArchiveInspectorProps) { +export function IWorkArchiveInspector({ file, onToggle }: IWorkArchiveInspectorProps) { const { chunks } = file; - return <> - {chunks.map(c =>
    0x{c.startAddress.toString(16)}:
    ).reduce((a,b) => {[a,
    , b]}
    )} - -} \ No newline at end of file + + return ( + <> + {!file.open && } + {file.open && chunks + .map((c, index) => ( + +
    + Chunk {index + 1} @ 0x{c.startAddress.toString(16)}: +
    +
    +
    + +
    +
    + +
    +
    +
    + )) + .reduce((a, b) => ( + {[a,
    , b]}
    + ))} + + ); +} diff --git a/gh-pages/src/components/statemachine/actions.ts b/gh-pages/src/components/statemachine/actions.ts index 1fcd9d3..d958533 100644 --- a/gh-pages/src/components/statemachine/actions.ts +++ b/gh-pages/src/components/statemachine/actions.ts @@ -1,4 +1,4 @@ -export type ActionType = 'add-file'|'add-chunk'; +export type ActionType = 'add-file'|'add-chunk'|'toggle-file'; export interface ActionBase { type: ActionType; @@ -17,4 +17,9 @@ export interface AddChunkAction extends ActionBase { data: Uint8Array; } -export type Action = AddFileAction | AddChunkAction; +export interface ToggleFileAction extends ActionBase { + type: 'toggle-file'; + path: string; +} + +export type Action = AddFileAction | AddChunkAction | ToggleFileAction; diff --git a/gh-pages/src/components/statemachine/reducer.ts b/gh-pages/src/components/statemachine/reducer.ts index de3cf6a..9dfe25e 100644 --- a/gh-pages/src/components/statemachine/reducer.ts +++ b/gh-pages/src/components/statemachine/reducer.ts @@ -37,7 +37,8 @@ export function reducer(state: InspectorState, action: Action): InspectorState { ...fileCommon, type: 'iwa', chunks: [], - buffer: action.data + buffer: action.data, + open: false }; break; default: @@ -59,7 +60,8 @@ export function reducer(state: InspectorState, action: Action): InspectorState { const iwaFile = state.files.find(f => f.path === action.path); if (iwaFile && iwaFile.type === 'iwa') { const chunk: ChunkState = { - startAddress: action.startAddress + startAddress: action.startAddress, + data: action.data }; return { ...state, @@ -76,6 +78,23 @@ export function reducer(state: InspectorState, action: Action): InspectorState { }; } return state; + case "toggle-file": + const fileToToggleIndex = state.files.findIndex(f => f.path === action.path); + const fileToToggle = state.files[fileToToggleIndex]; + if (fileToToggle && fileToToggle.type === 'iwa') { + return { + ...state, + files: [ + ...state.files.slice(0, fileToToggleIndex), + { + ...fileToToggle, + open: !fileToToggle.open + }, + ...state.files.slice(fileToToggleIndex + 1) + ] + }; + } + return state; default: return state; } diff --git a/gh-pages/src/components/statemachine/states.ts b/gh-pages/src/components/statemachine/states.ts index 6841ba2..a94e537 100644 --- a/gh-pages/src/components/statemachine/states.ts +++ b/gh-pages/src/components/statemachine/states.ts @@ -16,12 +16,14 @@ export interface FileStateBase { export interface IwaFileState extends FileStateBase { type: 'iwa'; + open: boolean; chunks: ChunkState[]; buffer: Uint8Array } export interface ChunkState { startAddress: number; + data: Uint8Array; } export interface XmlFileState extends FileStateBase { diff --git a/gh-pages/tsconfig.json b/gh-pages/tsconfig.json index 61c19ab..4700211 100644 --- a/gh-pages/tsconfig.json +++ b/gh-pages/tsconfig.json @@ -16,7 +16,8 @@ "incremental": true, "paths": { "@/*": ["./src/*"] - } + }, + "downlevelIteration": true }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], "exclude": ["node_modules"]