@@ -14,18 +14,20 @@ import {
14
14
jsonSchemaLinter ,
15
15
stateExtensions
16
16
} from "codemirror-json-schema" ;
17
- import { useRef , forwardRef , useImperativeHandle , Ref , ReactNode } from "react" ;
17
+ import { useRef , forwardRef , useImperativeHandle , Ref , ReactNode , useState } from "react" ;
18
18
import { Button } from "@/components/ui/button" ;
19
19
import { Separator } from "@/components/ui/separator" ;
20
20
import { Schema } from "ajv" ;
21
21
import { Tooltip , TooltipContent , TooltipProvider , TooltipTrigger } from "@/components/ui/tooltip" ;
22
22
import useCaptureEvent from "@/hooks/useCaptureEvent" ;
23
23
import { CodeHostType } from "@/lib/utils" ;
24
+
24
25
export type QuickActionFn < T > = ( previous : T ) => T ;
25
26
export type QuickAction < T > = {
26
27
name : string ;
27
28
fn : QuickActionFn < T > ;
28
29
description ?: string | ReactNode ;
30
+ selectionText ?: string ;
29
31
} ;
30
32
31
33
interface ConfigEditorProps < T > {
@@ -57,11 +59,13 @@ export function onQuickAction<T>(
57
59
options ?: {
58
60
focusEditor ?: boolean ;
59
61
moveCursor ?: boolean ;
62
+ selectionText ?: string ;
60
63
}
61
64
) {
62
65
const {
63
66
focusEditor = false ,
64
67
moveCursor = true ,
68
+ selectionText = `""` ,
65
69
} = options ?? { } ;
66
70
67
71
let previousConfig : T ;
@@ -78,7 +82,6 @@ export function onQuickAction<T>(
78
82
view . focus ( ) ;
79
83
}
80
84
81
- const cursorPos = next . lastIndexOf ( `""` ) + 1 ;
82
85
view . dispatch ( {
83
86
changes : {
84
87
from : 0 ,
@@ -87,10 +90,16 @@ export function onQuickAction<T>(
87
90
}
88
91
} ) ;
89
92
90
- if ( moveCursor ) {
91
- view . dispatch ( {
92
- selection : { anchor : cursorPos , head : cursorPos }
93
- } ) ;
93
+ if ( moveCursor && selectionText ) {
94
+ const cursorPos = next . lastIndexOf ( selectionText ) ;
95
+ if ( cursorPos >= 0 ) {
96
+ view . dispatch ( {
97
+ selection : {
98
+ anchor : cursorPos ,
99
+ head : cursorPos + selectionText . length
100
+ }
101
+ } ) ;
102
+ }
94
103
}
95
104
}
96
105
@@ -103,10 +112,15 @@ export const isConfigValidJson = (config: string) => {
103
112
}
104
113
}
105
114
115
+ const DEFAULT_ACTIONS_VISIBLE = 4 ;
116
+
106
117
const ConfigEditor = < T , > ( props : ConfigEditorProps < T > , forwardedRef : Ref < ReactCodeMirrorRef > ) => {
107
118
const { value, type, onChange, actions, schema } = props ;
108
119
const captureEvent = useCaptureEvent ( ) ;
109
120
const editorRef = useRef < ReactCodeMirrorRef > ( null ) ;
121
+ const [ isViewMoreActionsEnabled , setIsViewMoreActionsEnabled ] = useState ( false ) ;
122
+ const [ height , setHeight ] = useState ( 224 ) ;
123
+
110
124
useImperativeHandle (
111
125
forwardedRef ,
112
126
( ) => editorRef . current as ReactCodeMirrorRef
@@ -117,7 +131,79 @@ const ConfigEditor = <T,>(props: ConfigEditorProps<T>, forwardedRef: Ref<ReactCo
117
131
118
132
return (
119
133
< div className = "border rounded-md" >
120
- < ScrollArea className = "p-1 overflow-auto flex-1 h-56" >
134
+ < div className = "flex flex-row items-center flex-wrap p-1" >
135
+ < TooltipProvider >
136
+ { actions
137
+ . slice ( 0 , isViewMoreActionsEnabled ? actions . length : DEFAULT_ACTIONS_VISIBLE )
138
+ . map ( ( { name, fn, description, selectionText } , index , truncatedActions ) => (
139
+ < div
140
+ key = { index }
141
+ className = "flex flex-row items-center"
142
+ >
143
+ < Tooltip
144
+ delayDuration = { 100 }
145
+ >
146
+ < TooltipTrigger asChild >
147
+ < Button
148
+ variant = "ghost"
149
+ className = "disabled:opacity-100 disabled:pointer-events-auto disabled:cursor-not-allowed text-sm font-mono tracking-tight"
150
+ size = "sm"
151
+ disabled = { ! isConfigValidJson ( value ) }
152
+ onClick = { ( e ) => {
153
+ e . preventDefault ( ) ;
154
+ captureEvent ( 'wa_config_editor_quick_action_pressed' , {
155
+ name,
156
+ type,
157
+ } ) ;
158
+ if ( editorRef . current ?. view ) {
159
+ onQuickAction ( fn , value , editorRef . current . view , {
160
+ focusEditor : true ,
161
+ selectionText,
162
+ } ) ;
163
+ }
164
+ } }
165
+ >
166
+ { name }
167
+ </ Button >
168
+ </ TooltipTrigger >
169
+ < TooltipContent
170
+ hidden = { ! description }
171
+ className = "max-w-xs"
172
+ >
173
+ { description }
174
+ </ TooltipContent >
175
+ </ Tooltip >
176
+ { index !== truncatedActions . length - 1 && (
177
+ < Separator
178
+ orientation = "vertical" className = "h-4 mx-1"
179
+ />
180
+ ) }
181
+ { index === truncatedActions . length - 1 && truncatedActions . length < actions . length && (
182
+ < >
183
+ < Separator
184
+ orientation = "vertical" className = "h-4 mx-1"
185
+ />
186
+ < Button
187
+ variant = "link"
188
+ size = "sm"
189
+ className = "text-xs text-muted-foreground"
190
+ onClick = { ( e ) => {
191
+ e . preventDefault ( ) ;
192
+ setIsViewMoreActionsEnabled ( ! isViewMoreActionsEnabled ) ;
193
+ } }
194
+ >
195
+ +{ actions . length - truncatedActions . length } more
196
+ </ Button >
197
+ </ >
198
+ ) }
199
+ </ div >
200
+ ) ) }
201
+
202
+ </ TooltipProvider >
203
+ </ div >
204
+ < Separator />
205
+
206
+ < ScrollArea className = "p-1 overflow-auto flex-1" style = { { height } } >
121
207
< CodeMirror
122
208
ref = { editorRef }
123
209
value = { value }
@@ -142,55 +228,27 @@ const ConfigEditor = <T,>(props: ConfigEditorProps<T>, forwardedRef: Ref<ReactCo
142
228
theme = { theme === "dark" ? "dark" : "light" }
143
229
/>
144
230
</ ScrollArea >
145
- < Separator />
146
- < div className = "flex flex-row items-center flex-wrap w-full p-1" >
147
- < TooltipProvider >
148
- { actions . map ( ( { name, fn, description } , index ) => (
149
- < div
150
- key = { index }
151
- className = "flex flex-row items-center"
152
- >
153
- < Tooltip
154
- delayDuration = { 100 }
155
- >
156
- < TooltipTrigger asChild >
157
- < Button
158
- variant = "ghost"
159
- className = "disabled:opacity-100 disabled:pointer-events-auto disabled:cursor-not-allowed text-sm font-mono tracking-tight"
160
- size = "sm"
161
- disabled = { ! isConfigValidJson ( value ) }
162
- onClick = { ( e ) => {
163
- e . preventDefault ( ) ;
164
- captureEvent ( 'wa_config_editor_quick_action_pressed' , {
165
- name,
166
- type,
167
- } ) ;
168
- if ( editorRef . current ?. view ) {
169
- onQuickAction ( fn , value , editorRef . current . view , {
170
- focusEditor : true ,
171
- } ) ;
172
- }
173
- } }
174
- >
175
- { name }
176
- </ Button >
177
- </ TooltipTrigger >
178
- < TooltipContent
179
- hidden = { ! description }
180
- className = "max-w-xs"
181
- >
182
- { description }
183
- </ TooltipContent >
184
- </ Tooltip >
185
- { index !== actions . length - 1 && (
186
- < Separator
187
- orientation = "vertical" className = "h-4 mx-1"
188
- />
189
- ) }
190
- </ div >
191
- ) ) }
192
- </ TooltipProvider >
193
- </ div >
231
+ < div
232
+ className = "h-1 cursor-ns-resize bg-border rounded-md hover:bg-primary/50 transition-colors"
233
+ onMouseDown = { ( e ) => {
234
+ e . preventDefault ( ) ;
235
+ const startY = e . clientY ;
236
+ const startHeight = height ;
237
+
238
+ function onMouseMove ( e : MouseEvent ) {
239
+ const delta = e . clientY - startY ;
240
+ setHeight ( Math . max ( 112 , startHeight + delta ) ) ;
241
+ }
242
+
243
+ function onMouseUp ( ) {
244
+ document . removeEventListener ( 'mousemove' , onMouseMove ) ;
245
+ document . removeEventListener ( 'mouseup' , onMouseUp ) ;
246
+ }
247
+
248
+ document . addEventListener ( 'mousemove' , onMouseMove ) ;
249
+ document . addEventListener ( 'mouseup' , onMouseUp ) ;
250
+ } }
251
+ />
194
252
</ div >
195
253
)
196
254
} ;
0 commit comments