@@ -14,18 +14,20 @@ import {
1414 jsonSchemaLinter ,
1515 stateExtensions
1616} from "codemirror-json-schema" ;
17- import { useRef , forwardRef , useImperativeHandle , Ref , ReactNode } from "react" ;
17+ import { useRef , forwardRef , useImperativeHandle , Ref , ReactNode , useState } from "react" ;
1818import { Button } from "@/components/ui/button" ;
1919import { Separator } from "@/components/ui/separator" ;
2020import { Schema } from "ajv" ;
2121import { Tooltip , TooltipContent , TooltipProvider , TooltipTrigger } from "@/components/ui/tooltip" ;
2222import useCaptureEvent from "@/hooks/useCaptureEvent" ;
2323import { CodeHostType } from "@/lib/utils" ;
24+
2425export type QuickActionFn < T > = ( previous : T ) => T ;
2526export type QuickAction < T > = {
2627 name : string ;
2728 fn : QuickActionFn < T > ;
2829 description ?: string | ReactNode ;
30+ selectionText ?: string ;
2931} ;
3032
3133interface ConfigEditorProps < T > {
@@ -57,11 +59,13 @@ export function onQuickAction<T>(
5759 options ?: {
5860 focusEditor ?: boolean ;
5961 moveCursor ?: boolean ;
62+ selectionText ?: string ;
6063 }
6164) {
6265 const {
6366 focusEditor = false ,
6467 moveCursor = true ,
68+ selectionText = `""` ,
6569 } = options ?? { } ;
6670
6771 let previousConfig : T ;
@@ -78,7 +82,6 @@ export function onQuickAction<T>(
7882 view . focus ( ) ;
7983 }
8084
81- const cursorPos = next . lastIndexOf ( `""` ) + 1 ;
8285 view . dispatch ( {
8386 changes : {
8487 from : 0 ,
@@ -87,10 +90,16 @@ export function onQuickAction<T>(
8790 }
8891 } ) ;
8992
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+ }
94103 }
95104}
96105
@@ -103,10 +112,15 @@ export const isConfigValidJson = (config: string) => {
103112 }
104113}
105114
115+ const DEFAULT_ACTIONS_VISIBLE = 4 ;
116+
106117const ConfigEditor = < T , > ( props : ConfigEditorProps < T > , forwardedRef : Ref < ReactCodeMirrorRef > ) => {
107118 const { value, type, onChange, actions, schema } = props ;
108119 const captureEvent = useCaptureEvent ( ) ;
109120 const editorRef = useRef < ReactCodeMirrorRef > ( null ) ;
121+ const [ isViewMoreActionsEnabled , setIsViewMoreActionsEnabled ] = useState ( false ) ;
122+ const [ height , setHeight ] = useState ( 224 ) ;
123+
110124 useImperativeHandle (
111125 forwardedRef ,
112126 ( ) => editorRef . current as ReactCodeMirrorRef
@@ -117,7 +131,79 @@ const ConfigEditor = <T,>(props: ConfigEditorProps<T>, forwardedRef: Ref<ReactCo
117131
118132 return (
119133 < 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 } } >
121207 < CodeMirror
122208 ref = { editorRef }
123209 value = { value }
@@ -142,55 +228,27 @@ const ConfigEditor = <T,>(props: ConfigEditorProps<T>, forwardedRef: Ref<ReactCo
142228 theme = { theme === "dark" ? "dark" : "light" }
143229 />
144230 </ 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+ />
194252 </ div >
195253 )
196254} ;
0 commit comments