Skip to content

Commit 7685d9c

Browse files
Quick action tweaks (#218)
1 parent cdfcb5a commit 7685d9c

File tree

4 files changed

+317
-69
lines changed

4 files changed

+317
-69
lines changed

packages/web/src/app/[domain]/components/configEditor.tsx

Lines changed: 114 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -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";
1818
import { Button } from "@/components/ui/button";
1919
import { Separator } from "@/components/ui/separator";
2020
import { Schema } from "ajv";
2121
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
2222
import useCaptureEvent from "@/hooks/useCaptureEvent";
2323
import { CodeHostType } from "@/lib/utils";
24+
2425
export type QuickActionFn<T> = (previous: T) => T;
2526
export type QuickAction<T> = {
2627
name: string;
2728
fn: QuickActionFn<T>;
2829
description?: string | ReactNode;
30+
selectionText?: string;
2931
};
3032

3133
interface 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+
106117
const 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
};

packages/web/src/app/[domain]/connections/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export default function Layout({
1212
<div className="min-h-screen flex flex-col">
1313
<NavigationMenu domain={domain} />
1414
<main className="flex-grow flex justify-center p-4 bg-backgroundSecondary relative">
15-
<div className="w-full max-w-6xl rounded-lg p-6">{children}</div>
15+
<div className="w-full max-w-6xl p-6">{children}</div>
1616
</main>
1717
</div>
1818
)

0 commit comments

Comments
 (0)