styles: 更改一些样式

This commit is contained in:
2024-12-10 11:25:55 +08:00
parent 142a8cd3eb
commit a279a262b7
23 changed files with 879 additions and 262 deletions

View File

@@ -5,4 +5,5 @@
background-color: white;
border-radius: 5px;
padding: 16px;
overflow: scroll;
}

View File

@@ -3,11 +3,207 @@ import { Flex } from "antd";
import React, { useContext, useEffect, useRef, useState } from "react";
import type { GetRef, InputRef, TableProps } from "antd";
import { Form, Input, Table } from "antd";
import { invoke } from "@tauri-apps/api/core";
import { cloneDeep } from "lodash-es";
type FormInstance<T> = GetRef<typeof Form<T>>;
const EditableContext = React.createContext<FormInstance<any> | null>(null);
interface DosHeaderFieldProps {
name: string;
value: number | [number];
size: number;
offset: number;
field_type: string;
}
interface DosHeaderResponse {
image_dos_header: DosHeaderFieldResponse;
base_offset: number;
}
interface DosHeaderFieldResponse {
e_magic: number;
e_cblp: number;
e_cp: number;
e_crlc: number;
e_cparhdr: number;
e_minalloc: number;
e_maxalloc: number;
e_ss: number;
e_sp: number;
e_csum: number;
e_ip: number;
e_cs: number;
e_lfarlc: number;
e_ovno: number;
e_res: number;
e_oemid: number;
e_oeminfo: number;
e_res2: number;
e_lfanew: number;
}
// 定义DOS Header的数据结构
interface IDosHeader {
e_magic: DosHeaderFieldProps;
e_cblp: DosHeaderFieldProps;
e_cp: DosHeaderFieldProps;
e_crlc: DosHeaderFieldProps;
e_cparhdr: DosHeaderFieldProps;
e_minalloc: DosHeaderFieldProps;
e_maxalloc: DosHeaderFieldProps;
e_ss: DosHeaderFieldProps;
e_sp: DosHeaderFieldProps;
e_csum: DosHeaderFieldProps;
e_ip: DosHeaderFieldProps;
e_cs: DosHeaderFieldProps;
e_lfarlc: DosHeaderFieldProps;
e_ovno: DosHeaderFieldProps;
e_res: DosHeaderFieldProps;
e_oemid: DosHeaderFieldProps;
e_oeminfo: DosHeaderFieldProps;
e_res2: DosHeaderFieldProps;
e_lfanew: DosHeaderFieldProps;
}
const templateData: IDosHeader = {
e_magic: {
name: "e_magic",
value: 0,
size: 2,
offset: 0,
field_type: "WORD",
},
e_cblp: {
name: "e_cblp",
value: 0,
size: 2,
offset: 2,
field_type: "WORD",
},
e_cp: {
name: "e_cp",
value: 0,
size: 2,
offset: 4,
field_type: "WORD",
},
e_crlc: {
name: "e_crlc",
value: 0,
size: 2,
offset: 6,
field_type: "WORD",
},
e_cparhdr: {
name: "e_cparhdr",
value: 0,
size: 2,
offset: 8,
field_type: "WORD",
},
e_minalloc: {
name: "e_minalloc",
value: 0,
size: 2,
offset: 10,
field_type: "WORD",
},
e_maxalloc: {
name: "e_maxalloc",
value: 0,
size: 2,
offset: 12,
field_type: "WORD",
},
e_ss: {
name: "e_ss",
value: 0,
size: 2,
offset: 14,
field_type: "WORD",
},
e_sp: {
name: "e_sp",
value: 0,
size: 2,
offset: 16,
field_type: "WORD",
},
e_csum: {
name: "e_csum",
value: 0,
size: 2,
offset: 18,
field_type: "WORD",
},
e_ip: {
name: "e_ip",
value: 0,
size: 2,
offset: 20,
field_type: "WORD",
},
e_cs: {
name: "e_cs",
value: 0,
size: 2,
offset: 22,
field_type: "WORD",
},
e_lfarlc: {
name: "e_lfarlc",
value: 0,
size: 2,
offset: 24,
field_type: "WORD",
},
e_ovno: {
name: "e_ovno",
value: 0,
size: 2,
offset: 26,
field_type: "WORD",
},
e_res: {
name: "e_res",
value: null,
size: 8,
offset: 28,
field_type: "[WORD; 4]",
},
e_oemid: {
name: "e_oemid",
value: 0,
size: 2,
offset: 36,
field_type: "WORD",
},
e_oeminfo: {
name: "e_oeminfo",
value: 0,
size: 2,
offset: 38,
field_type: "WORD",
},
e_res2: {
name: "e_res2",
value: 0,
size: 20,
offset: 40,
field_type: "[WORD; 10]",
},
e_lfanew: {
name: "e_lfanew",
value: 0,
size: 4,
offset: 60,
field_type: "DWORD",
},
};
interface Item {
name: string; // 字段名称
offset: string; // 偏移
@@ -109,16 +305,47 @@ interface DataType {
type ColumnTypes = Exclude<TableProps<DataType>["columns"], undefined>;
const App = ({ defaultData = [] }) => {
const [dataSource, setDataSource] = useState<DataType[]>(defaultData);
useEffect(()=>{
setDataSource(defaultData)
}, [defaultData])
const App = () => {
const [offsetHex, setOffsetHex] = useState(false);
const [dataSource, setDataSource] = useState<DataType[]>([]);
const defaultColumns: (ColumnTypes[number] & {
editable?: boolean;
dataIndex: string;
})[] = [
useEffect(() => {
invoke("command_get_pe_data_dos_header").then(
(resData: DosHeaderResponse) => {
console.log("resData", resData);
// 格式化数据
let data = cloneDeep(templateData); // 先clone一个模板
let base_offset = resData.base_offset;
Object.keys(resData.image_dos_header).forEach((key) => {
let value = resData.image_dos_header[key];
data[key].offset = base_offset + data[key].offset;
if (Array.isArray(value)) {
// 如果是数组
data[key].children = value.map((item, index) => {
let child_size = data[key].size / value.length;
const extractWord = (input) => {
const match = input.match(/\[([\w]+);/);
return match ? match[1] : null; // 如果匹配到,返回结果;否则返回 null
};
return {
name: `${key}[${index}]`,
offset: index * child_size + data[key].offset,
size: child_size,
value: "0x" + item.toString(16).padStart(4, "0").toUpperCase(),
field_type: extractWord(data[key].field_type),
};
});
} else {
data[key].value =
"0x" + value.toString(16).padStart(4, "0").toUpperCase();
}
});
setDataSource(Object.values(data));
}
);
}, []);
const defaultColumns = [
{
title: "字段名称",
dataIndex: "name",
@@ -127,6 +354,15 @@ const App = ({ defaultData = [] }) => {
{
title: "偏移量",
dataIndex: "offset",
defaultSortOrder: "ascend",
render: (text) => {
return offsetHex ? `0x${text.toString(16).toUpperCase()}` : text;
},
onHeaderCell: (column) => ({
onClick: () => {
setOffsetHex(!offsetHex);
}
})
},
{
title: "字段类型",
@@ -202,15 +438,16 @@ const App = ({ defaultData = [] }) => {
columns={columns as ColumnTypes}
pagination={false}
size="small"
sticky={{ offsetHeader: -16 }}
/>
);
};
export default function DosHeader({ defaultData }) {
console.log("DosHeader defaultData:", defaultData);
export default function DosHeader() {
return (
<Flex className={styles.root}>
<App defaultData={defaultData} />
<App />
</Flex>
);
}

View File

@@ -0,0 +1,9 @@
.root {
margin: 8px;
margin-top: 0;
height: 100%;
background-color: white;
border-radius: 5px;
padding: 16px;
overflow: scroll;
}

View File

@@ -0,0 +1,247 @@
import { Flex, Form, FormInstance, Input, Table } from "antd";
import styles from "./FileHeader.module.scss";
import { useContext, useEffect, useRef, useState } from "react";
import { invoke } from "@tauri-apps/api/core";
import React from "react";
import { cloneDeep } from "lodash-es";
const EditableContext = React.createContext<FormInstance<any> | null>(null);
const templateData = {
machine: {
name: "Machine",
offset: 0,
size: 2,
value: null,
field_type: "WORD",
},
number_of_sections: {
name: "Number of Sections",
offset: 2,
size: 2,
value: null,
field_type: "WORD",
},
time_date_stamp: {
name: "Time Date Stamp",
offset: 4,
size: 4,
value: null,
field_type: "DWORD",
},
pointer_to_symbol_table: {
name: "Pointer to Symbol Table",
offset: 8,
size: 4,
value: null,
field_type: "DWORD",
},
number_of_symbols: {
name: "Number of Symbols",
offset: 12,
size: 4,
value: null,
field_type: "DWORD",
},
size_of_optional_header: {
name: "Size of Optional Header",
offset: 16,
size: 2,
value: null,
field_type: "WORD",
},
characteristics: {
name: "Characteristics",
offset: 18,
size: 2,
value: null,
field_type: "WORD",
},
};
const EditableRow = ({ index, ...props }) => {
const [form] = Form.useForm();
return (
<Form form={form} component={false}>
<EditableContext.Provider value={form}>
<tr {...props} />
</EditableContext.Provider>
</Form>
);
};
const EditableCell = ({
title,
editable,
children,
dataIndex,
record,
handleSave,
...restProps
}) => {
const [editing, setEditing] = useState(false);
const inputRef = useRef(null);
const form = useContext(EditableContext)!;
useEffect(() => {
if (editing) {
inputRef.current?.focus();
}
}, [editing]);
const toggleEdit = () => {
setEditing(!editing);
form.setFieldsValue({ [dataIndex]: record[dataIndex] });
};
const save = async () => {
try {
const values = await form.validateFields();
toggleEdit();
handleSave({ ...record, ...values });
} catch (errInfo) {
console.log("Save failed:", errInfo);
}
};
let childNode = children;
if (editable) {
childNode = editing ? (
<Form.Item
style={{ margin: 0 }}
name={dataIndex}
rules={[{ required: true, message: `${title} is required.` }]}
>
<Input size="small" ref={inputRef} onPressEnter={save} onBlur={save} />
</Form.Item>
) : (
<div
className="editable-cell-value-wrap"
style={{ paddingInlineEnd: 24 }}
onClick={toggleEdit}
>
{children}
</div>
);
}
return <td {...restProps}>{childNode}</td>;
};
const App = () => {
const [offsetHex, setOffsetHex] = useState(false);
const [dataSource, setDataSource] = useState([]);
useEffect(() => {
invoke("command_get_pe_data_file_header").then((resData: any) => {
console.log("resData", resData);
let data = cloneDeep(templateData);
let base_offset = resData.base_offset;
Object.keys(data).forEach((key) => {
data[key].value = resData.file_header[key];
data[key].offset = base_offset + data[key].offset;
});
setDataSource(Object.values(data));
});
}, []);
const defaultColumns = [
{
title: "字段名称",
dataIndex: "name",
width: "30%",
},
{
title: "偏移量",
dataIndex: "offset",
defaultSortOrder: "ascend",
render: (text) => {
return offsetHex ? `0x${text.toString(16).toUpperCase()}` : text;
},
onHeaderCell: (column) => ({
onClick: () => {
setOffsetHex(!offsetHex);
},
}),
},
{
title: "字段类型",
dataIndex: "field_type",
},
{
title: "字段大小",
dataIndex: "size",
},
{
title: "字段值",
dataIndex: "value",
editable: true,
},
];
const replaceNodeByName = (data, name, newNode) => {
return data.map((item) => {
if (item.name === name) {
return newNode;
}
if (item.children) {
return {
...item,
children: replaceNodeByName(item.children, name, newNode),
};
}
return item;
});
};
const handleSave = (row) => {
let newData = [...dataSource];
newData = replaceNodeByName(newData, row.name, row);
setDataSource(newData);
};
const components = {
body: {
row: EditableRow,
cell: EditableCell,
},
};
const columns = defaultColumns.map((col) => {
if (!col.editable) {
return col;
}
return {
...col,
onCell: (record) => ({
record,
editable: col.editable,
dataIndex: col.dataIndex,
title: col.title,
handleSave,
}),
};
});
return (
<Table
components={components}
rowClassName={() => "editable-row"}
bordered
rowKey={(record) => record.name}
style={{ width: "100%" }}
dataSource={dataSource}
columns={columns as any}
pagination={false}
size="small"
sticky={{ offsetHeader: -16 }}
/>
);
};
export default function FileHeader() {
return (
<Flex className={styles.root}>
<App />
</Flex>
);
}

View File

@@ -0,0 +1,8 @@
.root {
margin: 8px;
margin-top: 0;
height: 100%;
background-color: white;
border-radius: 5px;
padding: 16px;
}

View File

@@ -0,0 +1,205 @@
import { Flex, Form, FormInstance, Input, Table } from "antd";
import styles from "./NTHeader.module.scss";
import { useContext, useEffect, useRef, useState } from "react";
import { invoke } from "@tauri-apps/api/core";
import React from "react";
import { cloneDeep } from "lodash-es";
const EditableContext = React.createContext<FormInstance<any> | null>(null);
const templateData = {
signature: {
name: "Signature",
offset: 0,
size: 4,
value: null,
field_type: "DWORD",
},
};
const EditableRow = ({ index, ...props }) => {
const [form] = Form.useForm();
return (
<Form form={form} component={false}>
<EditableContext.Provider value={form}>
<tr {...props} />
</EditableContext.Provider>
</Form>
);
};
const EditableCell = ({
title,
editable,
children,
dataIndex,
record,
handleSave,
...restProps
}) => {
const [editing, setEditing] = useState(false);
const inputRef = useRef(null);
const form = useContext(EditableContext)!;
useEffect(() => {
if (editing) {
inputRef.current?.focus();
}
}, [editing]);
const toggleEdit = () => {
setEditing(!editing);
form.setFieldsValue({ [dataIndex]: record[dataIndex] });
};
const save = async () => {
try {
const values = await form.validateFields();
toggleEdit();
handleSave({ ...record, ...values });
} catch (errInfo) {
console.log("Save failed:", errInfo);
}
};
let childNode = children;
if (editable) {
childNode = editing ? (
<Form.Item
style={{ margin: 0 }}
name={dataIndex}
rules={[{ required: true, message: `${title} is required.` }]}
>
<Input size="small" ref={inputRef} onPressEnter={save} onBlur={save} />
</Form.Item>
) : (
<div
className="editable-cell-value-wrap"
style={{ paddingInlineEnd: 24 }}
onClick={toggleEdit}
>
{children}
</div>
);
}
return <td {...restProps}>{childNode}</td>;
};
const App = () => {
const [offsetHex, setOffsetHex] = useState(false);
const [dataSource, setDataSource] = useState([]);
useEffect(() => {
invoke("command_get_pe_data_nt_header").then((resData: any) => {
console.log("resData", resData);
// 格式化数据
let data = cloneDeep(templateData);
let base_offset = resData.base_offset;
data.signature.offset = base_offset;
data.signature.value = "0x" + resData.image_nt_header.signature.toString(16).toUpperCase();
console.log("data", data);
setDataSource(Object.values(data));
});
}, []);
const defaultColumns = [
{
title: "字段名称",
dataIndex: "name",
width: "30%",
},
{
title: "偏移量",
dataIndex: "offset",
defaultSortOrder: "ascend",
render: (text) => {
return offsetHex ? `0x${text.toString(16).toUpperCase()}` : text;
},
onHeaderCell: (column) => ({
onClick: () => {
setOffsetHex(!offsetHex);
},
}),
},
{
title: "字段类型",
dataIndex: "field_type",
},
{
title: "字段大小",
dataIndex: "size",
},
{
title: "字段值",
dataIndex: "value",
editable: true,
},
];
const replaceNodeByName = (data, name, newNode) => {
return data.map((item) => {
if (item.name === name) {
return newNode;
}
if (item.children) {
return {
...item,
children: replaceNodeByName(item.children, name, newNode),
};
}
return item;
});
};
const handleSave = (row) => {
let newData = [...dataSource];
newData = replaceNodeByName(newData, row.name, row);
setDataSource(newData);
};
const components = {
body: {
row: EditableRow,
cell: EditableCell,
},
};
const columns = defaultColumns.map((col) => {
if (!col.editable) {
return col;
}
return {
...col,
onCell: (record) => ({
record,
editable: col.editable,
dataIndex: col.dataIndex,
title: col.title,
handleSave,
}),
};
});
return (
<Table
components={components}
rowClassName={() => "editable-row"}
bordered
rowKey={(record) => record.name}
style={{ width: "100%" }}
dataSource={dataSource}
columns={columns as any}
pagination={false}
size="small"
sticky={{ offsetHeader: -16 }}
/>
);
};
export default function NTHeader() {
return (
<Flex className={styles.root}>
<App />
</Flex>
);
}

View File

@@ -1,6 +1,5 @@
.root {
background-color: white;
min-height: 500px;
width: 100%;
margin: 8px;
margin-top: 0;

View File

@@ -17,8 +17,8 @@ export default function SiderTree({
const [selectedKey, setSelectedKey] = useState<string>(defaultSelectedKey);
const onSelect: TreeProps["onSelect"] = (selectedKeys, info) => {
console.log("selected", selectedKeys, info);
let key = info.node.key as string;
console.log("onSelect", key);
setSelectedKey(key);
onTreeSelect(key);
};
@@ -43,8 +43,6 @@ export default function SiderTree({
// 设置默认选中的节点
setSelectedKey(defaultSelectedKey);
}, [treeData]);
console.log("selectedKey", selectedKey);
return (
<Flex className={styles.root}>
<Tree

View File

@@ -1,13 +1,15 @@
.mainLayout {
display: flex;
height: 100vh;
overflow: hidden;
}
.sider {
width: 250px;
height: 100%;
flex-shrink: 0;
width: 260px;
height: calc(100% - 64px);
margin: 8px;
margin-top: 0;
padding: 8px;
@@ -22,4 +24,8 @@
height: 55px;
// 圆角
border-radius: 5px;
}
.content{
height: calc(100% - 64px);
}

View File

@@ -7,24 +7,26 @@ const { Content } = Layout;
import { SaveOutlined, FolderOpenFilled } from "@ant-design/icons";
import SiderTree from "../components/side_tree/SideTree";
import DosHeader from "../components/DosHeader/DosHeader";
import NtHeader from "../components/NTHeader/NTHeader";
import FileHeader from "../components/FileHeader/FileHeader";
import { open } from "@tauri-apps/plugin-dialog";
const SelectNodeMap = {
"dos_header": DosHeader,
dos_header: <DosHeader />,
nt_header: <NtHeader />,
file_header: <FileHeader />,
};
export default function MainPage() {
const [treeData, setTreeData] = useState([]);
const [filePath, setFilePath] = useState("");
const [selectedKey, setSelectedKey] = useState("");
const [nodeData, setNodeData] = useState([]);
const openFile = async () => {
const file = await open({
multiple: false,
directory: false,
});
console.log("file:", file);
if (file) {
setFilePath(file);
}
@@ -33,26 +35,12 @@ export default function MainPage() {
const changeFile = (filePath: string) => {
invoke("command_open_file", { filePath }).then(() => {
invoke("command_get_file_pe_node_tree_data").then((res) => {
console.log("tree nodes:", res);
setTreeData(res as any);
});
});
};
const onSelect = (newSelectedKey) => {
console.log("newSelectedKey", newSelectedKey);
// TODO: 获取节点数据
newSelectedKey &&
invoke(`command_get_pe_data_${newSelectedKey}`).then((resData: any) => {
console.log("resData", resData);
// 格式化
let ary = [];
for (let key in resData) {
ary.push(resData[key]);
}
console.log("ary", ary);
setNodeData(ary);
});
setSelectedKey(newSelectedKey);
};
@@ -61,7 +49,6 @@ export default function MainPage() {
"tauri://drag-drop",
(event) => {
const filePath = event.payload.paths[0];
console.log("file path:", filePath);
setFilePath(filePath);
}
);
@@ -76,8 +63,6 @@ export default function MainPage() {
}
}, [filePath]);
console.log("selectedKey", selectedKey);
return (
<Layout className={styles.mainLayout}>
<Flex className={styles.header}>
@@ -99,6 +84,7 @@ export default function MainPage() {
style={{
backgroundColor: "#e5e5e5",
paddingBottom: "16px",
height: "100%"
}}
>
<Flex className={styles.sider}>
@@ -108,11 +94,9 @@ export default function MainPage() {
onSelect={onSelect}
/>
</Flex>
<Content>
<Content className={styles.content}>
{selectedKey && SelectNodeMap[selectedKey] ? (
SelectNodeMap[selectedKey]({
defaultData: nodeData,
})
SelectNodeMap[selectedKey]
) : (
<Empty description={"请选择一个节点"} />
)}