33import { useState , useEffect } from 'react' ;
44import Editor from 'react-simple-code-editor' ;
55import { styled , SupersetTheme , t } from '@superset-ui/core' ;
6- import { Popover , Button , Typography } from 'antd' ;
7- import {
8- InfoCircleOutlined ,
9- CopyOutlined ,
10- CheckOutlined ,
11- } from '@ant-design/icons' ;
126import Prism from 'prismjs' ;
137import 'prismjs/components/prism-json' ;
148import { prettyStringify } from '../utils/safeStringify' ;
159
16- const { Text } = Typography ;
17-
1810const Container = styled . div (
1911 ( { theme } : { theme : SupersetTheme } ) => `
2012 font-family: ${ theme . fontFamilyCode } ;
@@ -53,50 +45,17 @@ const Label = styled.span(
5345` ,
5446) ;
5547
56- const InfoIcon = styled ( InfoCircleOutlined ) (
48+ const DocsLink = styled . a (
5749 ( { theme } ) => `
58- color: ${ theme . colorTextSecondary } ;
50+ color: ${ theme . colorPrimary } ;
51+ font-size: 12px;
52+ font-weight: 500;
53+ text-decoration: none;
5954 cursor: pointer;
60- font-size: 14px;
6155
6256 &:hover {
63- color: ${ theme . colorPrimary } ;
64- }
65- ` ,
66- ) ;
67-
68- const PopoverContentWrapper = styled . div `
69- width: 380px;
70- max-height: 450px;
71- overflow: hidden;
72- display: flex;
73- flex-direction: column;
74- ` ;
75-
76- const PopoverHeader = styled . div `
77- display: flex;
78- justify-content: space-between;
79- align-items: center;
80- margin-bottom: 12px;
81- ` ;
82-
83- const TemplateCode = styled . pre (
84- ( { theme } ) => `
85- font-family: ${ theme . fontFamilyCode } ;
86- font-size: 11px;
87- background: ${ theme . colorBgContainer } ;
88- border: 1px solid ${ theme . colorBorder } ;
89- border-radius: 4px;
90- padding: 12px;
91- margin: 0;
92- overflow: auto;
93- white-space: pre;
94- color: ${ theme . colorText } ;
95- max-height: 380px;
96-
97- .optional-comment {
98- color: ${ theme . colorTextSecondary } ;
99- font-style: italic;
57+ text-decoration: underline;
58+ color: ${ theme . colorPrimaryHover } ;
10059 }
10160` ,
10261) ;
@@ -121,108 +80,8 @@ const EditorWrapper = styled.div`
12180 }
12281` ;
12382
124- // Template JSON with optional fields marked
125- const templateJson = `{
126- "globalColoring": {
127- "fillColor": [40, 147, 179, 255],
128- "strokeColor": [0, 0, 0, 255],
129- "strokeWidth": 2,
130- "pointType": "circle", // optional
131- "lineStyle": "solid",
132- "fillPattern": "solid"
133- },
134- "pointSize": 6, // static size (number), OR use the object form below:
135- // "pointSize": { // dynamic — scales point size by a data column value
136- // "valueColumn": "your_numeric_column",
137- // "startSize": 4, // pixel size at lowerBound
138- // "endSize": 30, // pixel size at upperBound
139- // "lowerBound": null, // optional — defaults to data min
140- // "upperBound": null // optional — defaults to data max
141- // },
142- "colorByCategory": {
143- "dimension": "category_field",
144- "categoricalColors": [
145- {
146- "example_category_1": {
147- "fillColor": [0, 0, 255, 255],
148- "legend_entry_name": "category_1"
149- }
150- },
151- {
152- "example_category_2": {
153- "fillColor": [255, 0, 0, 255],
154- "legend_entry_name": "category_2"
155- }
156- }
157- ],
158- "defaultLegendName": ["Other", "Unknown"]
159- },
160- "colorByValue": {
161- "valueColumn": "column_metrics_will_be_applied_to",
162- "upperBound": null, // optional
163- "lowerBound": null, // optional
164- "startColor": [0, 255, 0, 255],
165- "endColor": [255, 0, 0, 255],
166- "breakpoints": []
167- },
168- "textOverlayStyle": {
169- "fontFamily": "Arial, sans-serif", // Web Safe Fonts: Arial, Verdana, Georgia, Times New Roman, Courier New
170- "fontSize": 14,
171- "bold": false,
172- "offset": [0, 0] // pixel offset (x, y) from point
173- },
174- "legend": {
175- "name": "human_readable_legend_chart_title",
176- "title": "title_for_legend_section"
177- }
178- }` ;
179-
180- // Clean template (without comments) for copying
181- const cleanTemplateJson = JSON . stringify (
182- {
183- globalColoring : {
184- fillColor : [ 40 , 147 , 179 , 255 ] ,
185- strokeColor : [ 0 , 0 , 0 , 255 ] ,
186- strokeWidth : 2 ,
187- pointType : 'circle' ,
188- lineStyle : 'solid' ,
189- fillPattern : 'solid' ,
190- } ,
191- pointSize : 6 ,
192- colorByCategory : {
193- dimension : 'category_field' ,
194- categoricalColors : [
195- {
196- example_category_1 : {
197- fillColor : [ 0 , 0 , 255 , 255 ] ,
198- legend_entry_name : 'category_1' ,
199- } ,
200- } ,
201- {
202- example_category_2 : {
203- fillColor : [ 255 , 0 , 0 , 255 ] ,
204- legend_entry_name : 'category_2' ,
205- } ,
206- } ,
207- ] ,
208- defaultLegendName : [ 'Other' , 'Unknown' ] ,
209- } ,
210- colorByValue : {
211- valueColumn : 'column_metrics_will_be_applied_to' ,
212- upperBound : null ,
213- lowerBound : null ,
214- startColor : [ 0 , 255 , 0 , 255 ] ,
215- endColor : [ 255 , 0 , 0 , 255 ] ,
216- breakpoints : [ ] ,
217- } ,
218- legend : {
219- name : 'human_readable_legend_chart_title' ,
220- title : 'title_for_legend_section' ,
221- } ,
222- } ,
223- null ,
224- 2 ,
225- ) ;
83+ const JSON_CONFIG_SPEC_URL =
84+ 'https://github.com/raft-tech/GeoSet/wiki/JSON-Config-Spec' ;
22685
22786type JsonEditorControlProps = {
22887 label ?: string ;
@@ -256,8 +115,6 @@ export default function JsonEditorControl({
256115 if ( defaultValue != null ) return defaultValue ;
257116 return '' ;
258117 } ) ;
259- const [ copied , setCopied ] = useState ( false ) ;
260-
261118 useEffect ( ( ) => {
262119 if ( value != null && value !== '' && value !== localValue ) {
263120 setLocalValue ( normalizeJson ( value ) ) ;
@@ -277,66 +134,19 @@ export default function JsonEditorControl({
277134 }
278135 } ;
279136
280- const handleCopy = async ( ) => {
281- try {
282- await navigator . clipboard . writeText ( cleanTemplateJson ) ;
283- setCopied ( true ) ;
284- setTimeout ( ( ) => setCopied ( false ) , 2000 ) ;
285- } catch ( err ) {
286- // Fallback for older browsers
287- const textArea = document . createElement ( 'textarea' ) ;
288- textArea . value = cleanTemplateJson ;
289- document . body . appendChild ( textArea ) ;
290- textArea . select ( ) ;
291- document . execCommand ( 'copy' ) ;
292- document . body . removeChild ( textArea ) ;
293- setCopied ( true ) ;
294- setTimeout ( ( ) => setCopied ( false ) , 2000 ) ;
295- }
296- } ;
297-
298- // Highlight optional comments in the template
299- const highlightTemplate = ( code : string ) => {
300- const highlighted = Prism . highlight ( code , Prism . languages . json , 'json' ) ;
301- return highlighted . replace (
302- / ( \/ \/ o p t i o n a l ) / g,
303- '<span class="optional-comment">$1</span>' ,
304- ) ;
305- } ;
306-
307- const popoverContent = (
308- < PopoverContentWrapper >
309- < PopoverHeader >
310- < Text strong > { t ( 'Example Configuration' ) } </ Text >
311- < Button
312- size = "small"
313- icon = { copied ? < CheckOutlined /> : < CopyOutlined /> }
314- onClick = { handleCopy }
315- >
316- { copied ? t ( 'Copied!' ) : t ( 'Copy' ) }
317- </ Button >
318- </ PopoverHeader >
319- < TemplateCode
320- dangerouslySetInnerHTML = { {
321- __html : highlightTemplate ( templateJson ) ,
322- } }
323- />
324- </ PopoverContentWrapper >
325- ) ;
326-
327137 return (
328138 < Container >
329139 { label && (
330140 < LabelRow >
331141 < Label id = { `${ label } -label` } > { label } </ Label >
332- < Popover
333- content = { popoverContent }
334- trigger = "click "
335- placement = "rightTop "
336- overlayStyle = { { maxWidth : 420 } }
142+ < DocsLink
143+ href = { JSON_CONFIG_SPEC_URL }
144+ target = "_blank "
145+ rel = "noopener noreferrer "
146+ aria-label = { t ( 'View JSON Config Spec wiki' ) }
337147 >
338- < InfoIcon title = { t ( 'View template configuration ' ) } />
339- </ Popover >
148+ { t ( '[see docs] ' ) }
149+ </ DocsLink >
340150 </ LabelRow >
341151 ) }
342152 < EditorWrapper onBlur = { handleBlur } >
0 commit comments