@forge-kit/plugin-source-map-prase 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -0
- package/dist/app.js +79 -0
- package/dist/icon.svg +3 -0
- package/dist/vite.svg +1 -0
- package/package.json +54 -0
- package/src/App.less +101 -0
- package/src/App.tsx +141 -0
- package/src/components/code-highlight/index.tsx +41 -0
- package/src/components/map-input-panel/index.less +95 -0
- package/src/components/map-input-panel/index.tsx +128 -0
- package/src/components/panel-card/index.tsx +25 -0
- package/src/components/trace-chain/index.less +128 -0
- package/src/components/trace-chain/index.tsx +151 -0
- package/src/main.tsx +29 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/source-map/base/constants.ts +2 -0
- package/src/utils/source-map/base/path.ts +17 -0
- package/src/utils/source-map/base/registry.ts +38 -0
- package/src/utils/source-map/base/types.ts +37 -0
- package/src/utils/source-map/domain/chain-slots.ts +59 -0
- package/src/utils/source-map/domain/source-content.ts +75 -0
- package/src/utils/source-map/domain/trace-resolver.ts +165 -0
- package/src/utils/source-map/domain/view-model.ts +20 -0
- package/src/utils/source-map/facade/source-map-utils.ts +212 -0
- package/src/utils/source-map/index.ts +18 -0
package/dist/icon.svg
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<svg width="204" height="204" viewBox="0 0 204 204" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path d="M178.275 203.725H25.475C11.4 203.725 0 192.325 0 178.25V25.475C0 11.4 11.4 0 25.475 0H178.275C192.35 0 203.75 11.4 203.75 25.475V178.275C203.725 192.35 192.35 203.725 178.275 203.725ZM191.15 24.3C191.15 17.85 185.45 12.6 178.4 12.6H25.35C18.3 12.6 12.6 17.85 12.6 24.3V38.325H191.15V24.3ZM191.15 50.95H12.6V178.175C12.6 185.35 18.3 191.15 25.35 191.15H178.4C185.45 191.15 191.15 185.35 191.15 178.175V50.95ZM149.275 154.4C147.125 156.525 143.675 156.525 141.525 154.4C140.5 153.4 139.925 152.025 139.925 150.575C139.925 149.15 140.5 147.775 141.525 146.75L169.725 123.725L141.525 100.7C140.5 99.7 139.925 98.325 139.925 96.875C139.925 95.425 140.5 94.05 141.525 93.05C143.675 90.95 147.15 90.95 149.275 93.05L181.825 119.625C182.975 120.75 183.45 122.225 183.375 123.7C183.45 125.175 182.975 126.675 181.825 127.775L149.275 154.4ZM94.125 170.7C92.625 173.25 90.225 173.7 86.7 172.65C83.825 171.8 83.2 167.875 84.725 165.3L111.9 73.925C111.9 73.925 116.725 70.475 119.3 71.975C120.55 72.675 121.45 73.85 121.825 75.225C122.2 76.6 122 78.075 121.3 79.3L94.125 170.7ZM62.225 154.4C60.075 156.525 56.625 156.525 54.475 154.4L21.925 127.85C20.775 126.725 20.3 125.225 20.375 123.775C20.3 122.3 20.775 120.8 21.925 119.7L54.475 93.125C56.625 91.025 60.075 91.025 62.225 93.125C63.25 94.125 63.85 95.5 63.85 96.95C63.85 98.4 63.275 99.775 62.225 100.775L34.025 123.8L62.225 146.825C63.25 147.825 63.85 149.2 63.85 150.65C63.85 152.1 63.25 153.4 62.225 154.4Z" fill=""/>
|
|
3
|
+
</svg>
|
package/dist/vite.svg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@forge-kit/plugin-source-map-prase",
|
|
3
|
+
"entry": "./dist/app.js",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public"
|
|
6
|
+
},
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"src"
|
|
10
|
+
],
|
|
11
|
+
"author": "yu.pan <panyupy@vip.qq.com>",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"version": "0.0.1",
|
|
14
|
+
"type": "module",
|
|
15
|
+
"scripts": {
|
|
16
|
+
"dev": "vite",
|
|
17
|
+
"build": "tsc -b && vite build",
|
|
18
|
+
"lint": "eslint .",
|
|
19
|
+
"preview": "vite preview"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@dnd-kit/core": "^6.3.1",
|
|
23
|
+
"@dnd-kit/sortable": "^10.0.0",
|
|
24
|
+
"@dnd-kit/utilities": "^3.2.2",
|
|
25
|
+
"@forge-kit/component": "workspace:*",
|
|
26
|
+
"@forge-kit/icons": "workspace:*",
|
|
27
|
+
"@forge-kit/types": "workspace:*",
|
|
28
|
+
"classnames": "^2.5.1",
|
|
29
|
+
"react": "^19.2.0",
|
|
30
|
+
"react-dom": "^19.2.0",
|
|
31
|
+
"react-syntax-highlighter": "^16.1.0",
|
|
32
|
+
"source-map": "^0.7.6",
|
|
33
|
+
"source-map-js": "^1.2.1"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@eslint/js": "^9.39.1",
|
|
37
|
+
"@forge-kit/vite-plugin-forge-kit": "workspace:*",
|
|
38
|
+
"@types/node": "^24.10.1",
|
|
39
|
+
"@types/react": "^19.2.5",
|
|
40
|
+
"@types/react-dom": "^19.2.3",
|
|
41
|
+
"@types/react-syntax-highlighter": "^15.5.13",
|
|
42
|
+
"@vitejs/plugin-react": "^5.1.1",
|
|
43
|
+
"eslint": "^9.39.1",
|
|
44
|
+
"eslint-plugin-react-hooks": "^7.0.1",
|
|
45
|
+
"eslint-plugin-react-refresh": "^0.4.24",
|
|
46
|
+
"globals": "^16.5.0",
|
|
47
|
+
"typescript": "~5.9.3",
|
|
48
|
+
"typescript-eslint": "^8.46.4",
|
|
49
|
+
"vite": "npm:rolldown-vite@7.2.5"
|
|
50
|
+
},
|
|
51
|
+
"overrides": {
|
|
52
|
+
"vite": "npm:rolldown-vite@7.2.5"
|
|
53
|
+
}
|
|
54
|
+
}
|
package/src/App.less
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
html, body, #root {
|
|
2
|
+
-webkit-app-region: drag;
|
|
3
|
+
background-color: transparent;
|
|
4
|
+
margin: 0;
|
|
5
|
+
height: 100%;
|
|
6
|
+
overflow: hidden;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.forge-kit-plugin-source-map-prase {
|
|
10
|
+
width: 100%;
|
|
11
|
+
height: 100%;
|
|
12
|
+
overflow: hidden;
|
|
13
|
+
|
|
14
|
+
&-content {
|
|
15
|
+
width: 100%;
|
|
16
|
+
height: 100%;
|
|
17
|
+
overflow: hidden;
|
|
18
|
+
min-height: 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.top-cards {
|
|
22
|
+
flex: 1;
|
|
23
|
+
min-height: 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.trace-panel-card,
|
|
27
|
+
.input-panel-card,
|
|
28
|
+
.result-panel-card {
|
|
29
|
+
|
|
30
|
+
-webkit-app-region: no-drag;
|
|
31
|
+
border: 1px solid color-mix(in srgb, var(--gray-6) 75%, transparent);
|
|
32
|
+
background:
|
|
33
|
+
linear-gradient(180deg, color-mix(in srgb, var(--gray-2) 88%, transparent), color-mix(in srgb, var(--gray-1) 86%, transparent));
|
|
34
|
+
box-shadow:
|
|
35
|
+
inset 0 1px 0 rgba(255, 255, 255, 0.04),
|
|
36
|
+
0 10px 24px rgba(0, 0, 0, 0.16);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.input-panel-card,
|
|
40
|
+
.trace-panel-card {
|
|
41
|
+
flex: 1;
|
|
42
|
+
min-height: 0;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.trace-panel-card {
|
|
46
|
+
display: flex;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.result-panel-card {
|
|
50
|
+
flex: 1;
|
|
51
|
+
min-height: 0;
|
|
52
|
+
display: flex;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.panel-shell {
|
|
56
|
+
flex: 1;
|
|
57
|
+
min-height: 0;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.panel-title {
|
|
61
|
+
display: block;
|
|
62
|
+
padding: 0 0 var(--space-3);
|
|
63
|
+
margin-bottom: var(--space-2);
|
|
64
|
+
border-bottom: solid 1px var(--gray-5);
|
|
65
|
+
font-size: 12px;
|
|
66
|
+
letter-spacing: 0.1em;
|
|
67
|
+
color: var(--gray-11);
|
|
68
|
+
text-transform: uppercase;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.trace-panel-content,
|
|
72
|
+
.result-panel-content {
|
|
73
|
+
padding-top: var(--space-3);
|
|
74
|
+
flex: 1;
|
|
75
|
+
min-height: 0;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.trace-panel-content {
|
|
79
|
+
overflow: hidden;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.result-panel-content {
|
|
83
|
+
overflow: auto;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.trace-chain-root {
|
|
87
|
+
height: 100%;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.output-meta {
|
|
91
|
+
display: inline-block;
|
|
92
|
+
margin-bottom: var(--space-2);
|
|
93
|
+
color: var(--gray-11);
|
|
94
|
+
font-size: 12px;
|
|
95
|
+
border: 1px solid var(--gray-6);
|
|
96
|
+
border-radius: 999px;
|
|
97
|
+
padding: 2px 10px;
|
|
98
|
+
background: color-mix(in srgb, var(--gray-3) 75%, transparent);
|
|
99
|
+
-webkit-app-region: no-drag;
|
|
100
|
+
}
|
|
101
|
+
}
|
package/src/App.tsx
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import {Box, Flex, Text, Theme} from "@forge-kit/component";
|
|
3
|
+
import {CodeHighlight} from "@/components/code-highlight";
|
|
4
|
+
import {MapInputPanel} from "@/components/map-input-panel";
|
|
5
|
+
import {PanelCard} from "@/components/panel-card";
|
|
6
|
+
import {TraceChain} from "@/components/trace-chain";
|
|
7
|
+
import {
|
|
8
|
+
SourceMapUtils,
|
|
9
|
+
type ChainMapSlot,
|
|
10
|
+
type SourceMapChainState,
|
|
11
|
+
type TraceOutputState,
|
|
12
|
+
} from "@/utils/source-map";
|
|
13
|
+
import '@forge-kit/component/style.css'
|
|
14
|
+
import './App.less'
|
|
15
|
+
|
|
16
|
+
interface AppProps {
|
|
17
|
+
themeProps?: React.ComponentProps<typeof Theme>
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface InputState {
|
|
21
|
+
entryLine: string
|
|
22
|
+
entryColumn: string
|
|
23
|
+
contextLineRadius: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const initialChainState: SourceMapChainState = {
|
|
27
|
+
sourceMaps: {},
|
|
28
|
+
sourceMapFileNames: [],
|
|
29
|
+
chainMapSlots: [],
|
|
30
|
+
entryFileName: "",
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const initialInputState: InputState = {
|
|
34
|
+
entryLine: "",
|
|
35
|
+
entryColumn: "",
|
|
36
|
+
contextLineRadius: String(SourceMapUtils.DEFAULT_CONTEXT_LINE_RADIUS),
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const App: React.FC<AppProps> = (props) => {
|
|
40
|
+
const {themeProps} = props
|
|
41
|
+
const [chainState, setChainState] = React.useState<SourceMapChainState>(initialChainState)
|
|
42
|
+
const [inputState, setInputState] = React.useState<InputState>(initialInputState)
|
|
43
|
+
const [outputState, setOutputState] = React.useState<TraceOutputState>(SourceMapUtils.createInitialOutputState())
|
|
44
|
+
|
|
45
|
+
const traceChainDisplayData = React.useMemo(() => {
|
|
46
|
+
return SourceMapUtils.mapTraceForDisplay(outputState.traceData)
|
|
47
|
+
}, [outputState.traceData])
|
|
48
|
+
|
|
49
|
+
const applyChainSlots =(slots: ChainMapSlot[]) => {
|
|
50
|
+
setChainState(SourceMapUtils.buildChainStateFromSlots(slots))
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const setOutputMessage = (message: string) => {
|
|
54
|
+
setOutputState((prev) => SourceMapUtils.patchOutputMessage(prev, message))
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const handleAppendChainFile = async (file: File) => {
|
|
58
|
+
const slotFromFile = await SourceMapUtils.createChainSlotFromFile(file)
|
|
59
|
+
applyChainSlots([...chainState.chainMapSlots, slotFromFile])
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const handleRemoveChainSlot = (slotId: string) => {
|
|
63
|
+
applyChainSlots(SourceMapUtils.removeChainSlot(chainState.chainMapSlots, slotId))
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const handleReorderChainSlots = (nextIds: string[]) => {
|
|
67
|
+
applyChainSlots(SourceMapUtils.reorderChainSlots(chainState.chainMapSlots, nextIds))
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const handleResolveTrace = async () => {
|
|
71
|
+
const validated = SourceMapUtils.validateResolveInput({
|
|
72
|
+
entryFileName: chainState.entryFileName,
|
|
73
|
+
entryLine: inputState.entryLine,
|
|
74
|
+
entryColumn: inputState.entryColumn,
|
|
75
|
+
contextLineRadius: inputState.contextLineRadius,
|
|
76
|
+
mapCount: chainState.sourceMapFileNames.length,
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
if (!validated.ok) return setOutputMessage(validated.message)
|
|
80
|
+
|
|
81
|
+
const nextOutput = await SourceMapUtils.resolveTraceOutput({
|
|
82
|
+
entry: validated.entry,
|
|
83
|
+
contextLineRadius: validated.contextLineRadius,
|
|
84
|
+
sourceMaps: chainState.sourceMaps,
|
|
85
|
+
sourceMapFileNames: chainState.sourceMapFileNames,
|
|
86
|
+
})
|
|
87
|
+
setOutputState(nextOutput)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<Theme className="forge-kit-plugin-source-map-prase" accentColor="gray" appearance="dark" grayColor="slate"
|
|
92
|
+
radius="full" panelBackground="translucent" {...themeProps}>
|
|
93
|
+
<Flex className="forge-kit-plugin-source-map-prase-content" direction="column" gap="3">
|
|
94
|
+
<Flex className="top-cards" gap="3" align="stretch">
|
|
95
|
+
<PanelCard title="TRACE CHAIN" cardClassName="trace-panel-card" contentClassName="trace-panel-content">
|
|
96
|
+
<TraceChain
|
|
97
|
+
slots={chainState.chainMapSlots.map((slot) => ({
|
|
98
|
+
id: slot.id,
|
|
99
|
+
mapFileName: slot.mapFileName,
|
|
100
|
+
error: slot.error,
|
|
101
|
+
}))}
|
|
102
|
+
data={traceChainDisplayData}
|
|
103
|
+
onReorder={handleReorderChainSlots}
|
|
104
|
+
onDelete={handleRemoveChainSlot}
|
|
105
|
+
className="trace-chain-root"
|
|
106
|
+
/>
|
|
107
|
+
</PanelCard>
|
|
108
|
+
|
|
109
|
+
<MapInputPanel
|
|
110
|
+
chainDepth={chainState.chainMapSlots.length}
|
|
111
|
+
entryFileName={chainState.entryFileName}
|
|
112
|
+
inputConfig={inputState}
|
|
113
|
+
onInputConfigChange={setInputState}
|
|
114
|
+
onResolve={handleResolveTrace}
|
|
115
|
+
onAppendMapFile={handleAppendChainFile}
|
|
116
|
+
/>
|
|
117
|
+
</Flex>
|
|
118
|
+
|
|
119
|
+
<PanelCard
|
|
120
|
+
title="TRACE OUTPUT"
|
|
121
|
+
cardClassName="result-panel-card"
|
|
122
|
+
contentClassName="result-panel-content"
|
|
123
|
+
contentProps={{direction: "column"}}
|
|
124
|
+
>
|
|
125
|
+
{outputState.resolvedSourceMeta && (
|
|
126
|
+
<Text className="output-meta">
|
|
127
|
+
{`目标位置: ${outputState.resolvedSourceMeta.sourceFile}:${outputState.resolvedSourceMeta.lineNumber}:${outputState.resolvedSourceMeta.columnNumber}`}
|
|
128
|
+
</Text>
|
|
129
|
+
)}
|
|
130
|
+
<Box>
|
|
131
|
+
<CodeHighlight
|
|
132
|
+
code={outputState.traceCode}
|
|
133
|
+
highlightLines={outputState.traceHighlightLines}
|
|
134
|
+
className="code-highlight-view"
|
|
135
|
+
/>
|
|
136
|
+
</Box>
|
|
137
|
+
</PanelCard>
|
|
138
|
+
</Flex>
|
|
139
|
+
</Theme>
|
|
140
|
+
)
|
|
141
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import {Prism as SyntaxHighlighter} from 'react-syntax-highlighter'
|
|
3
|
+
import {oneDark} from 'react-syntax-highlighter/dist/esm/styles/prism'
|
|
4
|
+
|
|
5
|
+
interface CodeHighlightProps {
|
|
6
|
+
code?: string
|
|
7
|
+
className?: string
|
|
8
|
+
highlightLines?: number[]
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const CodeHighlight: React.FC<CodeHighlightProps> = (props) => {
|
|
12
|
+
const {code = "", className, highlightLines = []} = props
|
|
13
|
+
|
|
14
|
+
const baseLineStyle = {
|
|
15
|
+
backgroundColor: 'var(--color-background)',
|
|
16
|
+
display: 'block',
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const highlightLineStyle = {
|
|
20
|
+
backgroundColor: 'color-mix(in srgb, var(--color-background) 55%, var(--gray-8) 45%)',
|
|
21
|
+
display: 'block',
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const generateLineProps = (lineNum: number) => {
|
|
25
|
+
return highlightLines.includes(lineNum) ? {style: highlightLineStyle} : {style: baseLineStyle}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<SyntaxHighlighter
|
|
30
|
+
wrapLines
|
|
31
|
+
showLineNumbers
|
|
32
|
+
style={oneDark}
|
|
33
|
+
language="javascript"
|
|
34
|
+
className={className}
|
|
35
|
+
lineProps={generateLineProps}
|
|
36
|
+
customStyle={{background: "var(--color-background)"}}
|
|
37
|
+
>
|
|
38
|
+
{code}
|
|
39
|
+
</SyntaxHighlighter>
|
|
40
|
+
)
|
|
41
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
.forge-kit-plugin-source-map-prase {
|
|
2
|
+
.input-panel-card {
|
|
3
|
+
flex: 1;
|
|
4
|
+
display: flex;
|
|
5
|
+
|
|
6
|
+
.input-panel-content {
|
|
7
|
+
flex: 1;
|
|
8
|
+
padding-top: var(--space-3);
|
|
9
|
+
overflow: auto;
|
|
10
|
+
gap: 10px;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.input-footer {
|
|
14
|
+
display: grid;
|
|
15
|
+
grid-template-rows: auto minmax(0, 1fr);
|
|
16
|
+
row-gap: 10px;
|
|
17
|
+
border-top: 1px solid var(--gray-5);
|
|
18
|
+
padding-top: 10px;
|
|
19
|
+
flex: 1;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.input-summary-grid {
|
|
24
|
+
display: grid;
|
|
25
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
26
|
+
gap: 8px;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.input-summary-item {
|
|
30
|
+
border: 1px solid var(--gray-5);
|
|
31
|
+
border-radius: var(--radius-3);
|
|
32
|
+
padding: 8px 10px;
|
|
33
|
+
background: var(--gray-2);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.input-summary-label {
|
|
37
|
+
display: block;
|
|
38
|
+
color: var(--gray-10);
|
|
39
|
+
font-size: 12px;
|
|
40
|
+
margin-bottom: 4px;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.input-summary-value {
|
|
44
|
+
display: block;
|
|
45
|
+
color: var(--gray-12);
|
|
46
|
+
font-size: 13px;
|
|
47
|
+
font-weight: 600;
|
|
48
|
+
word-break: break-all;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.input-hints {
|
|
52
|
+
border: 1px dashed var(--gray-6);
|
|
53
|
+
border-radius: var(--radius-3);
|
|
54
|
+
padding: 8px 10px;
|
|
55
|
+
background: color-mix(in srgb, var(--gray-2) 70%, transparent);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.input-hints-title {
|
|
59
|
+
display: block;
|
|
60
|
+
font-size: 12px;
|
|
61
|
+
font-weight: 600;
|
|
62
|
+
color: var(--gray-11);
|
|
63
|
+
margin-bottom: 6px;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.input-hints-item {
|
|
67
|
+
display: block;
|
|
68
|
+
font-size: 12px;
|
|
69
|
+
line-height: 1.6;
|
|
70
|
+
color: var(--gray-11);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.field-row-compact {
|
|
74
|
+
flex: 1;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.entry-actions {
|
|
78
|
+
display: flex;
|
|
79
|
+
flex: 1;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.entry-action-row {
|
|
83
|
+
width: auto;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.field-label {
|
|
87
|
+
color: var(--gray-11);
|
|
88
|
+
font-size: 12px;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.rt-TextFieldRoot {
|
|
92
|
+
border-radius: 6px;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import {Box, Button, Flex, Text, TextField} from "@forge-kit/component";
|
|
3
|
+
import {PanelCard} from "@/components/panel-card";
|
|
4
|
+
import {noop} from "@/utils";
|
|
5
|
+
import "./index.less";
|
|
6
|
+
|
|
7
|
+
interface MapInputConfig {
|
|
8
|
+
entryLine: string
|
|
9
|
+
entryColumn: string
|
|
10
|
+
contextLineRadius: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface MapInputPanelProps {
|
|
14
|
+
chainDepth: number
|
|
15
|
+
entryFileName: string
|
|
16
|
+
inputConfig: MapInputConfig
|
|
17
|
+
onInputConfigChange: (next: MapInputConfig) => void
|
|
18
|
+
onResolve: () => void
|
|
19
|
+
onAppendMapFile: (file: File) => void
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface MapInputFieldProps {
|
|
23
|
+
title?: string
|
|
24
|
+
value?: string
|
|
25
|
+
min?: number
|
|
26
|
+
placeholder?: string
|
|
27
|
+
onChange?: (value: string) => void
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const MapInputField: React.FC<MapInputFieldProps> = (props) => {
|
|
31
|
+
const {title, value, min, placeholder, onChange = noop} = props
|
|
32
|
+
return (
|
|
33
|
+
<Flex className="field-row-compact" direction="column" gap="1">
|
|
34
|
+
<Text className="field-label">{title}</Text>
|
|
35
|
+
<TextField.Root
|
|
36
|
+
type="number"
|
|
37
|
+
min={min}
|
|
38
|
+
value={value}
|
|
39
|
+
onChange={(event) => onChange(event.target.value)}
|
|
40
|
+
placeholder={placeholder}
|
|
41
|
+
/>
|
|
42
|
+
</Flex>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const MapInputPanel: React.FC<MapInputPanelProps> = (props) => {
|
|
47
|
+
const {
|
|
48
|
+
chainDepth,
|
|
49
|
+
entryFileName,
|
|
50
|
+
inputConfig,
|
|
51
|
+
onInputConfigChange,
|
|
52
|
+
onResolve,
|
|
53
|
+
onAppendMapFile,
|
|
54
|
+
} = props
|
|
55
|
+
|
|
56
|
+
const openFilePicker = () => {
|
|
57
|
+
const fileInput = document.createElement("input")
|
|
58
|
+
fileInput.type = "file"
|
|
59
|
+
fileInput.accept = ".map,application/json"
|
|
60
|
+
fileInput.onchange = () => {
|
|
61
|
+
const file = fileInput.files?.[0]
|
|
62
|
+
if (file) {
|
|
63
|
+
onAppendMapFile(file)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
fileInput.click()
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const handleInputConfigChange = (field: keyof MapInputConfig, value: string) => {
|
|
70
|
+
onInputConfigChange({...inputConfig, [field]: value})
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<PanelCard
|
|
75
|
+
title="MAP INPUT"
|
|
76
|
+
cardClassName="input-panel-card"
|
|
77
|
+
contentClassName="input-panel-content"
|
|
78
|
+
contentProps={{direction: "column", gap: "3"}}
|
|
79
|
+
>
|
|
80
|
+
<Box className="input-summary-grid">
|
|
81
|
+
<Box className="input-summary-item">
|
|
82
|
+
<Text className="input-summary-label">入口 Map</Text>
|
|
83
|
+
<Text className="input-summary-value">{entryFileName || "-"}</Text>
|
|
84
|
+
</Box>
|
|
85
|
+
<Box className="input-summary-item">
|
|
86
|
+
<Text className="input-summary-label">链路层数</Text>
|
|
87
|
+
<Text className="input-summary-value">{`${chainDepth} 层`}</Text>
|
|
88
|
+
</Box>
|
|
89
|
+
</Box>
|
|
90
|
+
|
|
91
|
+
<Box className="input-hints">
|
|
92
|
+
<Text className="input-hints-title">使用说明</Text>
|
|
93
|
+
<Text className="input-hints-item">1. 按解析链路顺序添加 map 文件。</Text>
|
|
94
|
+
<Text className="input-hints-item">2. 入口 Map 会自动取第一层的 output file。</Text>
|
|
95
|
+
<Text className="input-hints-item">3. TRACE CHAIN 支持拖动序号来切换 map 顺序。</Text>
|
|
96
|
+
</Box>
|
|
97
|
+
|
|
98
|
+
<Box className="input-footer">
|
|
99
|
+
<Flex gap="2" className="entry-row" align="end" wrap="wrap">
|
|
100
|
+
<MapInputField
|
|
101
|
+
title="入口行号"
|
|
102
|
+
min={1}
|
|
103
|
+
value={inputConfig.entryLine}
|
|
104
|
+
onChange={(value) => handleInputConfigChange("entryLine", value)}
|
|
105
|
+
/>
|
|
106
|
+
<MapInputField
|
|
107
|
+
title="入口列号"
|
|
108
|
+
min={0}
|
|
109
|
+
value={inputConfig.entryColumn}
|
|
110
|
+
onChange={(value) => handleInputConfigChange("entryColumn", value)}
|
|
111
|
+
/>
|
|
112
|
+
<MapInputField
|
|
113
|
+
title="上下文行数"
|
|
114
|
+
min={0}
|
|
115
|
+
value={inputConfig.contextLineRadius}
|
|
116
|
+
onChange={(value) => handleInputConfigChange("contextLineRadius", value)}
|
|
117
|
+
/>
|
|
118
|
+
</Flex>
|
|
119
|
+
<Flex className="entry-actions" direction="column" justify="center" align="center" gap="2">
|
|
120
|
+
<Flex className="entry-action-row" gap="2" wrap="wrap" justify="center" align="center">
|
|
121
|
+
<Button size="2" radius="large" onClick={onResolve}>解析链路</Button>
|
|
122
|
+
<Button size="2" radius="large" variant="outline" onClick={openFilePicker}>添加 map 文件</Button>
|
|
123
|
+
</Flex>
|
|
124
|
+
</Flex>
|
|
125
|
+
</Box>
|
|
126
|
+
</PanelCard>
|
|
127
|
+
)
|
|
128
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import {Card, Flex, Text} from "@forge-kit/component";
|
|
3
|
+
|
|
4
|
+
interface PanelCardProps {
|
|
5
|
+
title: string
|
|
6
|
+
cardClassName: string
|
|
7
|
+
contentClassName: string
|
|
8
|
+
contentProps?: Omit<React.ComponentProps<typeof Flex>, "className" | "children">
|
|
9
|
+
children?: React.ReactNode
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const PanelCard: React.FC<PanelCardProps> = (props) => {
|
|
13
|
+
const {title, cardClassName, contentClassName, contentProps, children} = props
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<Card className={cardClassName}>
|
|
17
|
+
<Flex className="panel-shell" direction="column">
|
|
18
|
+
<Text className="panel-title">{title}</Text>
|
|
19
|
+
<Flex className={contentClassName} {...contentProps}>
|
|
20
|
+
{children}
|
|
21
|
+
</Flex>
|
|
22
|
+
</Flex>
|
|
23
|
+
</Card>
|
|
24
|
+
)
|
|
25
|
+
}
|