@forge-kit/plugin-json-viewer 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/dist/icon.svg ADDED
@@ -0,0 +1 @@
1
+ <?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1763716801467" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1687" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M213.333333 128h85.333334v85.333333H213.333333v213.333334a85.333333 85.333333 0 0 1-85.333333 85.333333 85.333333 85.333333 0 0 1 85.333333 85.333333v213.333334h85.333334v85.333333H213.333333c-45.653333-11.52-85.333333-38.4-85.333333-85.333333v-170.666667a85.333333 85.333333 0 0 0-85.333333-85.333333H0v-85.333334h42.666667a85.333333 85.333333 0 0 0 85.333333-85.333333V213.333333a85.333333 85.333333 0 0 1 85.333333-85.333333m597.333334 0a85.333333 85.333333 0 0 1 85.333333 85.333333v170.666667a85.333333 85.333333 0 0 0 85.333333 85.333333h42.666667v85.333334h-42.666667a85.333333 85.333333 0 0 0-85.333333 85.333333v170.666667a85.333333 85.333333 0 0 1-85.333333 85.333333h-85.333334v-85.333333h85.333334v-213.333334a85.333333 85.333333 0 0 1 85.333333-85.333333 85.333333 85.333333 0 0 1-85.333333-85.333333V213.333333h-85.333334V128h85.333334m-298.666667 512a42.666667 42.666667 0 0 1 42.666667 42.666667 42.666667 42.666667 0 0 1-42.666667 42.666666 42.666667 42.666667 0 0 1-42.666667-42.666666 42.666667 42.666667 0 0 1 42.666667-42.666667m-170.666667 0a42.666667 42.666667 0 0 1 42.666667 42.666667 42.666667 42.666667 0 0 1-42.666667 42.666666 42.666667 42.666667 0 0 1-42.666666-42.666666 42.666667 42.666667 0 0 1 42.666666-42.666667m341.333334 0a42.666667 42.666667 0 0 1 42.666666 42.666667 42.666667 42.666667 0 0 1-42.666666 42.666666 42.666667 42.666667 0 0 1-42.666667-42.666666 42.666667 42.666667 0 0 1 42.666667-42.666667z" fill="" p-id="1688"></path></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,46 @@
1
+ {
2
+ "name": "@forge-kit/plugin-json-viewer",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "entry": "./dist/app.js",
6
+ "publishConfig": {
7
+ "access": "public"
8
+ },
9
+ "files": [
10
+ "dist",
11
+ "src"
12
+ ],
13
+ "author": "yu.pan <panyupy@vip.qq.com>",
14
+ "license": "MIT",
15
+ "scripts": {
16
+ "dev": "vite",
17
+ "build": "tsc -b && vite build",
18
+ "lint": "eslint .",
19
+ "preview": "vite preview"
20
+ },
21
+ "dependencies": {
22
+ "@forge-kit/component": "workspace:*",
23
+ "@forge-kit/forge-kit": "workspace:*",
24
+ "@forge-kit/icons": "workspace:*",
25
+ "classnames": "^2.5.1",
26
+ "jsonc-parser": "^3.3.1",
27
+ "prettier": "^3.6.2",
28
+ "react": "^19.0.0",
29
+ "react-dom": "^19.0.0"
30
+ },
31
+ "devDependencies": {
32
+ "@eslint/js": "^9.39.1",
33
+ "@types/node": "^24.10.0",
34
+ "@types/react": "^19.0.0",
35
+ "@types/react-dom": "^19.0.0",
36
+ "@vitejs/plugin-react": "^5.1.0",
37
+ "eslint": "^9.39.1",
38
+ "eslint-plugin-react-hooks": "^7.0.1",
39
+ "eslint-plugin-react-refresh": "^0.4.24",
40
+ "globals": "^16.5.0",
41
+ "typescript": "~5.9.3",
42
+ "typescript-eslint": "^8.46.3",
43
+ "vite": "^7.2.2",
44
+ "@forge-kit/vite-plugin-forge-kit": "workspace:*"
45
+ }
46
+ }
package/src/App.less ADDED
@@ -0,0 +1,44 @@
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-json-viewer {
10
+ width: 100%;
11
+ height: 100%;
12
+ overflow: hidden;
13
+
14
+ &-content {
15
+ width: 100%;
16
+ height: 100%;
17
+ overflow: hidden;
18
+
19
+ .formatted-viewer-card,
20
+ .text-area-card {
21
+ flex: 1;
22
+
23
+ .text-area {
24
+ height: 100%;
25
+ }
26
+ }
27
+
28
+ .json-viewer-card-layout {
29
+ display: flex;
30
+ flex-direction: column;
31
+
32
+ .layout-title {
33
+ padding-bottom: var(--space-3);
34
+ border-bottom: solid 1px var(--gray-5);
35
+ }
36
+
37
+ .layout-content {
38
+ padding-top: var(--space-3);
39
+ flex: 1;
40
+ overflow: hidden;
41
+ }
42
+ }
43
+ }
44
+ }
package/src/App.tsx ADDED
@@ -0,0 +1,238 @@
1
+ import prettier from "prettier/standalone";
2
+ import prettierPluginBabel from "prettier/plugins/babel";
3
+ import prettierPluginEstree from "prettier/plugins/estree";
4
+ import {Flex, TextArea, Text, Box, ScrollArea, Card, Theme} from "@forge-kit/component";
5
+ import {parseTree} from "jsonc-parser";
6
+ import type {Node, NodeType} from "jsonc-parser";
7
+ import React, {useContext, useEffect, useState} from "react";
8
+ import {TriangleRightIcon, FilePlusIcon, FileMinusIcon, MagicWandIcon} from '@forge-kit/icons'
9
+ import type {ComponentProps} from 'react'
10
+ import '@forge-kit/component/style.css'
11
+ import classNames from "classnames";
12
+ import './App.less'
13
+
14
+ type ColorConfig = Partial<Record<NodeType, ComponentProps<typeof Text>['color']>>
15
+
16
+ interface JsonViewerOperate {
17
+ isCollapsed: boolean;
18
+ operateId: string;
19
+ }
20
+
21
+ interface JsonViewerCtxVal extends JsonViewerOperate {
22
+ isCollapsed: boolean;
23
+ operateId: string;
24
+ colorConfig?: ColorConfig
25
+ }
26
+
27
+ const JsonViewerContext = React.createContext<Partial<JsonViewerCtxVal>>({})
28
+
29
+ interface JsonNodeProps {
30
+ label?: string
31
+ jsonNode?: Node
32
+ }
33
+
34
+ /**
35
+ * 渲染JSON节点
36
+ * @param props
37
+ * @constructor
38
+ */
39
+ const JsonNode: React.FC<JsonNodeProps> = (props) => {
40
+ const {jsonNode} = props
41
+ if (jsonNode?.type === 'object') return <JsonNodeContainer {...props} />
42
+ if (jsonNode?.type === 'array') return <JsonNodeContainer {...props} />
43
+ if (jsonNode?.type === 'property') return <JsonNodeProperty {...props} />
44
+ return <JsonNodeValue {...props} />
45
+ }
46
+
47
+ type JsonNodeContainerProps = JsonNodeProps
48
+
49
+ /**
50
+ * 渲染JSON节点容器
51
+ * @param props
52
+ * @constructor
53
+ */
54
+ const JsonNodeContainer: React.FC<JsonNodeContainerProps> = (props) => {
55
+ const {label, jsonNode} = props
56
+ // 解析JSON节点类型和子节点
57
+ const {type: jsonNodeType, children: jsonNodeChildren = [], parent} = jsonNode || {}
58
+ // 初始化折叠状态为true
59
+ const [isCollapsed, setIsCollapsed] = useState<boolean>(false)
60
+ // 从上下文获取操作ID和折叠状态
61
+ const {operateId, isCollapsed: contextIsCollapsed} = useContext(JsonViewerContext) || {}
62
+ // 判断是否为复杂类型节点(对象或数组)
63
+ const isComplexType = ['object', 'array'].includes(jsonNodeType!)
64
+ // 判断是否为数组节点
65
+ const isArrayNode = jsonNodeType === 'array'
66
+ // 判断是否为根节点
67
+ const isRootNode = Boolean(!parent)
68
+
69
+ useEffect(() => {
70
+ setIsCollapsed(Boolean(contextIsCollapsed))
71
+ }, [operateId])
72
+
73
+ return (
74
+ <Flex direction={isComplexType ? 'column' : 'row'} pl={isRootNode ? '0' : '4'} align="start">
75
+ <Flex align="center" gap="1" mr="1" onClick={() => setIsCollapsed(!isCollapsed)}>
76
+ {/* 渲染JSON节点折叠图标 */}
77
+ <Flex width="16px" align="center">{isComplexType && <TriangleRightIcon width={16} height={16}/>}</Flex>
78
+ {/* 渲染JSON节点标签 */}
79
+ {!isRootNode && <Text color="purple">{label}:</Text>}
80
+ {/* 渲染JSON节点类型 */}
81
+ <Box>
82
+ {/* 渲染JSON节点类型 */}
83
+ {isComplexType && <Text>{jsonNodeType}</Text>}
84
+ {/* 渲染JSON节点数组长度 */}
85
+ {isArrayNode && <Text>({jsonNodeChildren?.length})</Text>}
86
+ </Box>
87
+ </Flex>
88
+ {/* 渲染JSON节点值 */}
89
+ {!isComplexType && <JsonNode jsonNode={jsonNode}/>}
90
+ {/* 渲染JSON节点子节点 */}
91
+ {isComplexType && !isCollapsed && (
92
+ <Flex direction="column">
93
+ {jsonNodeChildren.map((child, index) => {
94
+ // 渲染JSON节点属性
95
+ if (isArrayNode) return <JsonNodeContainer label={String(index)} jsonNode={child}/>
96
+ // 渲染JSON节点数组元素
97
+ return <JsonNode jsonNode={child}/>
98
+ })}
99
+ </Flex>
100
+ )}
101
+ </Flex>
102
+ )
103
+ }
104
+
105
+ /**
106
+ * 渲染JSON节点属性
107
+ * @param props
108
+ * @constructor
109
+ */
110
+ const JsonNodeProperty: React.FC<JsonNodeProps> = (props) => {
111
+ const {jsonNode} = props
112
+ const [key, value] = jsonNode?.children || []
113
+ return <JsonNodeContainer label={key.value} jsonNode={value}/>
114
+ }
115
+
116
+ /**
117
+ * 渲染JSON节点值
118
+ * @param props
119
+ * @constructor
120
+ */
121
+ const JsonNodeValue: React.FC<JsonNodeProps> = (props) => {
122
+ const {jsonNode,} = props
123
+ const {type, value} = jsonNode || {}
124
+ const {colorConfig = {}} = useContext(JsonViewerContext) || {}
125
+ if (!type) return null
126
+ return <Text color={colorConfig[type]}>{type === "string" ? `"${value}"` : String(value)}</Text>
127
+ }
128
+
129
+
130
+ interface JsonViewerProps extends JsonNodeProps {
131
+ className?: string
132
+ colorConfig?: ColorConfig
133
+ operate?: Partial<JsonViewerOperate>
134
+ }
135
+
136
+ /**
137
+ * 渲染JSON查看器
138
+ * @param props
139
+ * @constructor
140
+ */
141
+ const JsonViewer: React.FC<JsonViewerProps> = (props) => {
142
+ const {className, jsonNode, colorConfig, operate = {}} = props
143
+ return (
144
+ <ScrollArea className={className} type="auto" scrollbars="vertical">
145
+ <JsonViewerContext.Provider value={{...operate, colorConfig}}>
146
+ <JsonNode jsonNode={jsonNode}/>
147
+ </JsonViewerContext.Provider>
148
+ </ScrollArea>
149
+ )
150
+ }
151
+
152
+ interface CardLayoutProps {
153
+ title?: string
154
+ extra?: React.ReactNode
155
+ className?: string
156
+ children?: React.ReactNode
157
+ }
158
+
159
+ const CardLayout: React.FC<CardLayoutProps> = (props) => {
160
+ const {title, extra, className, children} = props
161
+ return (
162
+ <Card className={classNames("json-viewer-card-layout", className)}>
163
+ <Flex className="layout-title" justify="between" align="center">
164
+ <Text size="1">{title}</Text>
165
+ <Flex align="center" gap="2">{extra}</Flex>
166
+ </Flex>
167
+ <Box className="layout-content">{children}</Box>
168
+ </Card>
169
+ )
170
+ }
171
+
172
+ /**
173
+ * 默认颜色配置
174
+ */
175
+ const colorConfig: ColorConfig = {
176
+ string: "green",
177
+ boolean: "orange",
178
+ number: "orange",
179
+ null: "orange",
180
+ }
181
+
182
+ export const App: React.FC = () => {
183
+
184
+ const [json, setJson] = useState("");
185
+
186
+ const [jsonViewerOperate, setJsonViewerOperate] = useState<Partial<JsonViewerOperate>>({})
187
+
188
+ const errors: any[] = []
189
+
190
+ const result = parseTree(json, errors)
191
+
192
+ const handleFormatJson = async () => {
193
+ const result = await prettier.format(json, {
194
+ parser: "json",
195
+ printWidth: 0,
196
+ plugins: [prettierPluginBabel, prettierPluginEstree],
197
+ })
198
+ return setJson(result)
199
+ }
200
+
201
+ const handleJsonViewerCollapse = () => {
202
+ const operateId = Date.now().toString(16)
203
+ setJsonViewerOperate({isCollapsed: true, operateId})
204
+ }
205
+
206
+ const handleJsonViewerExpand = () => {
207
+ const operateId = Date.now().toString(16)
208
+ setJsonViewerOperate({isCollapsed: false, operateId})
209
+ }
210
+
211
+ const handleTextAreaChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
212
+ const {value} = event.target
213
+ setJson(value)
214
+ }
215
+
216
+ const sourceInputExtra = <MagicWandIcon width={18} height={18} onClick={handleFormatJson}/>
217
+
218
+ const parsedTreeViewExtra = (
219
+ <>
220
+ <FilePlusIcon width={18} height={18} onClick={handleJsonViewerExpand}/>
221
+ <FileMinusIcon width={18} height={18} onClick={handleJsonViewerCollapse}/>
222
+ </>
223
+ )
224
+
225
+ return (
226
+ <Theme className="forge-kit-plugin-json-viewer" accentColor="gray" appearance="dark" radius="full"
227
+ apply={import.meta.env.DEV}>
228
+ <Flex className="forge-kit-plugin-json-viewer-content" gap="4">
229
+ <CardLayout className="text-area-card" title="SOURCE INPUT" extra={sourceInputExtra}>
230
+ <TextArea className="text-area" size="3" value={json} onChange={handleTextAreaChange}/>
231
+ </CardLayout>
232
+ <CardLayout className="formatted-viewer-card" title="PARSED TREE VIEW" extra={parsedTreeViewExtra}>
233
+ <JsonViewer jsonNode={result} colorConfig={colorConfig} operate={jsonViewerOperate}/>
234
+ </CardLayout>
235
+ </Flex>
236
+ </Theme>
237
+ )
238
+ }
package/src/icon.svg ADDED
@@ -0,0 +1 @@
1
+ <?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1763716801467" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1687" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M213.333333 128h85.333334v85.333333H213.333333v213.333334a85.333333 85.333333 0 0 1-85.333333 85.333333 85.333333 85.333333 0 0 1 85.333333 85.333333v213.333334h85.333334v85.333333H213.333333c-45.653333-11.52-85.333333-38.4-85.333333-85.333333v-170.666667a85.333333 85.333333 0 0 0-85.333333-85.333333H0v-85.333334h42.666667a85.333333 85.333333 0 0 0 85.333333-85.333333V213.333333a85.333333 85.333333 0 0 1 85.333333-85.333333m597.333334 0a85.333333 85.333333 0 0 1 85.333333 85.333333v170.666667a85.333333 85.333333 0 0 0 85.333333 85.333333h42.666667v85.333334h-42.666667a85.333333 85.333333 0 0 0-85.333333 85.333333v170.666667a85.333333 85.333333 0 0 1-85.333333 85.333333h-85.333334v-85.333333h85.333334v-213.333334a85.333333 85.333333 0 0 1 85.333333-85.333333 85.333333 85.333333 0 0 1-85.333333-85.333333V213.333333h-85.333334V128h85.333334m-298.666667 512a42.666667 42.666667 0 0 1 42.666667 42.666667 42.666667 42.666667 0 0 1-42.666667 42.666666 42.666667 42.666667 0 0 1-42.666667-42.666666 42.666667 42.666667 0 0 1 42.666667-42.666667m-170.666667 0a42.666667 42.666667 0 0 1 42.666667 42.666667 42.666667 42.666667 0 0 1-42.666667 42.666666 42.666667 42.666667 0 0 1-42.666666-42.666666 42.666667 42.666667 0 0 1 42.666666-42.666667m341.333334 0a42.666667 42.666667 0 0 1 42.666666 42.666667 42.666667 42.666667 0 0 1-42.666666 42.666666 42.666667 42.666667 0 0 1-42.666667-42.666666 42.666667 42.666667 0 0 1 42.666667-42.666667z" fill="" p-id="1688"></path></svg>
package/src/main.tsx ADDED
@@ -0,0 +1,30 @@
1
+ import {StrictMode} from 'react'
2
+ import ReactDOM from "react-dom/client";
3
+ import {definePlugin} from '@forge-kit/forge-kit'
4
+ import {App} from './App.tsx'
5
+
6
+ let rootInstance: ReturnType<typeof ReactDOM.createRoot> | null = null;
7
+
8
+ const plugin = definePlugin({
9
+ bundleId: "com.forge-kit.plugin.json-viewer",
10
+ name: 'JSON 解析工具',
11
+ icon: `${import.meta.env.BASE_URL}icon.svg`,
12
+ description: "JSON 解析工具",
13
+ mount: (container: any) => {
14
+ rootInstance = ReactDOM.createRoot(container);
15
+ rootInstance.render(
16
+ <StrictMode>
17
+ <App/>
18
+ </StrictMode>,
19
+ )
20
+ },
21
+ unmount: () => {
22
+ if (!rootInstance) return
23
+ rootInstance.unmount();
24
+ rootInstance = null;
25
+ }
26
+ });
27
+ if (import.meta.env.DEV) plugin.mount(document.getElementById('root')!)
28
+
29
+ export default plugin
30
+