Skip to content

Commit b310437

Browse files
committed
introduce a codemirror powered editor for url input
1 parent 7c749b3 commit b310437

4 files changed

Lines changed: 163 additions & 13 deletions

File tree

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
import React, { useRef, useEffect, useState } from 'react';
2+
3+
import { basicSetup } from 'codemirror';
4+
import { EditorState, Compartment } from '@codemirror/state';
5+
import { EditorView, keymap, lineNumbers, placeholder, Decoration, ViewPlugin, MatchDecorator } from '@codemirror/view';
6+
import { indentWithTab, history } from '@codemirror/commands';
7+
//import { json } from '@codemirror/lang-json';
8+
import { defaultKeymap } from '@codemirror/commands';
9+
import { syntaxHighlighting, defaultHighlightStyle, HighlightStyle, StreamLanguage } from '@codemirror/language';
10+
import { autocompletion, CompletionContext } from '@codemirror/autocomplete';
11+
import { tags, styleTags } from '@lezer/highlight';
12+
13+
// Define the autocomplete function
14+
const myCompletions = (context) => {
15+
let word = context.matchBefore(/{{\w*$/);
16+
if (!word) return null;
17+
18+
return {
19+
from: word.from,
20+
options: [
21+
{ label: '{{example1}}', type: 'keyword' },
22+
{ label: '{{example2}}', type: 'variable' },
23+
{ label: '{{example3}}', type: 'text' },
24+
],
25+
};
26+
};
27+
28+
// Configure the autocomplete extension
29+
const myAutocomplete = autocompletion({
30+
override: [myCompletions],
31+
activateOnTyping: true,
32+
});
33+
34+
// Custom styles to hide scrollbar
35+
const hideScrollbar = EditorView.theme({
36+
'.cm-scroller': {
37+
overflowX: 'auto',
38+
overflowY: 'hidden',
39+
whiteSpace: 'nowrap',
40+
scrollbarWidth: 'none' /* For Firefox */,
41+
},
42+
'.cm-content': {
43+
padding: '0', // Adjust padding to fit your needs
44+
overflow: 'auto',
45+
},
46+
'.cm-scroller::-webkit-scrollbar': {
47+
display: 'none' /* For Chrome, Safari, and Opera */,
48+
},
49+
'.cm-line': {
50+
padding: '0', // Adjust padding to fit your needs
51+
},
52+
'&': {
53+
height: 'auto', // Adjust height to auto for single-line input
54+
},
55+
'.cm-placeholder': {
56+
color: '#aaa', // Placeholder text color
57+
},
58+
});
59+
60+
// Rebind the Enter key to do nothing
61+
const rebindEnterKey = keymap.of([
62+
{
63+
key: 'Enter',
64+
run: () => true, // Return true to prevent the default action
65+
},
66+
]);
67+
68+
// Create a MatchDecorator to highlight {{text}} strings
69+
const decorator = new MatchDecorator({
70+
// Regular expression to match {{text}} strings
71+
regexp: /{{[^}]*}}/g,
72+
decoration: Decoration.mark({ class: 'highlight' }),
73+
});
74+
75+
// View plugin to apply the MatchDecorator
76+
const highlightPlugin = ViewPlugin.fromClass(
77+
class {
78+
constructor(view) {
79+
this.decorations = decorator.createDeco(view);
80+
}
81+
update(update) {
82+
this.decorations = decorator.updateDeco(update, this.decorations);
83+
}
84+
},
85+
{
86+
decorations: (v) => v.decorations,
87+
},
88+
);
89+
90+
// Custom styles for highlighting
91+
const highlightStyle = EditorView.baseTheme({
92+
'.highlight': {
93+
color: 'brown',
94+
},
95+
});
96+
97+
export const TextEditor = ({ id, placeHolder, onChangeHandler, name, value, disableState }) => {
98+
const editor1 = useRef();
99+
const [view, setView] = useState(null);
100+
101+
if (view) {
102+
if (value != view.state.doc.toString()) {
103+
view.dispatch({ changes: { from: 0, to: view.state.doc.length, insert: value } });
104+
}
105+
}
106+
107+
const onUpdate = EditorView.updateListener.of((v) => {
108+
if (onChangeHandler) {
109+
onChangeHandler(v.state.doc.toString());
110+
}
111+
});
112+
113+
useEffect(() => {
114+
const state = EditorState.create({
115+
doc: value,
116+
extensions: [
117+
//EditorView.lineWrapping,
118+
placeholder(placeHolder),
119+
myAutocomplete,
120+
//basicSetup,
121+
keymap.of([defaultKeymap, indentWithTab]),
122+
onUpdate,
123+
//EditorState.readOnly.of(props.readOnly || false),
124+
history(),
125+
hideScrollbar,
126+
rebindEnterKey,
127+
highlightPlugin,
128+
highlightStyle,
129+
],
130+
});
131+
132+
const view = new EditorView({ state, parent: editor1.current });
133+
setView(view);
134+
135+
return () => {
136+
view.destroy();
137+
setView(null);
138+
};
139+
}, []);
140+
141+
const mainStyles =
142+
'nodrag nowheel block w-3/4 rounded border border-slate-700 bg-background-light p-2.5 text-sm outline-none';
143+
const intentStyles = disableState ? 'cursor-not-allowed text-slate-400' : 'text-slate-900';
144+
145+
return <div ref={editor1} className={`${mainStyles} ${intentStyles}`}></div>;
146+
};

src/components/atoms/flow/FlowNode.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const FlowNode = ({ children, title, handleLeft, handleLeftData, handleRight, ha
99
<div
1010
className={`${
1111
children ? 'flex-col' : 'items-center justify-center px-6 py-4'
12-
} bg-background-lighter flex rounded-md border-2 border-slate-300`}
12+
} flex rounded-md border-2 border-slate-300 bg-background-lighter`}
1313
>
1414
{children ? (
1515
<>

src/components/molecules/flow/nodes/RequestBody.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ const RequestBody = ({ nodeId, nodeData }) => {
115115
{requestBodyTypeOptions.map((bodyTypeOption, index) => (
116116
<Menu.Item key={index} data-click-from='body-type-menu' onClick={() => handleClose(bodyTypeOption)}>
117117
<button
118-
className='flex items-center w-full px-2 py-2 text-sm text-gray-900 rounded-md hover:bg-background-light group'
118+
className='flex items-center w-full px-2 py-2 text-sm text-gray-900 rounded-md group hover:bg-background-light'
119119
data-click-from='body-type-menu'
120120
>
121121
{bodyTypeOption}
@@ -136,7 +136,7 @@ const RequestBody = ({ nodeId, nodeData }) => {
136136
name='request-body-json'
137137
onChange={(e) => handleRawJson(e)}
138138
value={nodeData.requestBody.body}
139-
classes={'w-96 h-96'}
139+
classes={'w-full max-h-96'}
140140
/>
141141
</div>
142142
<Button

src/components/molecules/flow/nodes/RequestNode.js

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import NodeHorizontalDivider from 'components/atoms/flow/NodeHorizontalDivider';
1212
import { Listbox, Transition } from '@headlessui/react';
1313
import { CheckIcon, ChevronUpDownIcon } from '@heroicons/react/20/solid';
1414
import requestNodes from '../constants/requestNodes';
15+
import { TextEditor } from 'components/atoms/common/TextEditor';
1516

1617
const RequestNode = ({ id, data }) => {
1718
const setRequestNodeUrl = useCanvasStore((state) => state.setRequestNodeUrl);
@@ -51,8 +52,8 @@ const RequestNode = ({ id, data }) => {
5152
}
5253
};
5354

54-
const handleUrlInputChange = (event) => {
55-
setRequestNodeUrl(id, event.target.value);
55+
const handleUrlInputChange = (value) => {
56+
setRequestNodeUrl(id, value);
5657
};
5758

5859
const renderVariables = (vType) => {
@@ -64,12 +65,14 @@ const RequestNode = ({ id, data }) => {
6465
{Object.keys(variables).map((id) => (
6566
<div className='flex items-center justify-between pb-2' key={id}>
6667
<div className='flex items-center justify-between text-sm border rounded-md border-neutral-500 text-neutral-500 outline-0 focus:ring-0'>
67-
<label className='px-4 py-2 border-r rounded-bl-md rounded-tl-md border-r-neutral-500'>{id}</label>
68+
<label className='w-1/4 px-4 py-2 border-r rounded-bl-md rounded-tl-md border-r-neutral-500'>
69+
{id}
70+
</label>
6871
{variables[id].type === 'Boolean' ? (
6972
<select
7073
onChange={(e) => handleVariableChange(e, vType, id)}
7174
name='boolean-val'
72-
className='nodrag h-9 w-full min-w-40 rounded-br-md rounded-tr-md p-2.5 px-1'
75+
className='nodrag h-9 w-2/4 min-w-40 rounded-br-md rounded-tr-md p-2.5 px-1'
7376
value={variables[id].value}
7477
>
7578
<option value='true'>True</option>
@@ -80,14 +83,14 @@ const RequestNode = ({ id, data }) => {
8083
) : (
8184
<input
8285
type={getInputType(variables[id].type)}
83-
className='nodrag nowheel block h-9 w-full min-w-40 rounded-bl-md rounded-tl-md p-2.5'
86+
className='nodrag nowheel block h-9 w-2/4 min-w-40 rounded-bl-md rounded-tl-md p-2.5'
8487
name='variable-value'
8588
data-type={getInputType(variables[id].type)}
8689
onChange={(e) => handleVariableChange(e, vType, id)}
8790
value={variables[id].value}
8891
/>
8992
)}
90-
<div className='px-4 py-2 border-l rounded-br-md rounded-tr-md border-l-neutral-500'>
93+
<div className='w-1/4 px-4 py-2 border-l rounded-br-md rounded-tr-md border-l-neutral-500'>
9194
{variables[id].type}
9295
</div>
9396
</div>
@@ -112,15 +115,16 @@ const RequestNode = ({ id, data }) => {
112115
handleRight={true}
113116
handleRightData={{ type: 'source' }}
114117
>
115-
<div className='min-w-80'>
118+
<div className='w-96'>
116119
<div className='flex items-center justify-center gap-2 py-4'>
117120
<Listbox
118121
value={data.requestType}
119122
onChange={(selectedValue) => {
120123
setRequestNodeType(id, selectedValue);
121124
}}
125+
className='w-1/4'
122126
>
123-
<div className='relative w-36'>
127+
<div>
124128
<Listbox.Button className='relative w-full p-2 text-left border rounded cursor-default border-cyan-950'>
125129
<span className='block truncate'>{data.requestType}</span>
126130
<span className='absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none'>
@@ -133,7 +137,7 @@ const RequestNode = ({ id, data }) => {
133137
leaveFrom='opacity-100'
134138
leaveTo='opacity-0'
135139
>
136-
<Listbox.Options className='absolute z-50 w-full py-1 mt-1 overflow-auto text-base bg-white max-h-60 focus:outline-none'>
140+
<Listbox.Options className='absolute z-50 py-1 mt-1 overflow-auto text-base bg-white max-h-60 w-36 focus:outline-none'>
137141
{requestNodes
138142
.map((el) => el.requestType)
139143
.map((reqType) => (
@@ -162,7 +166,7 @@ const RequestNode = ({ id, data }) => {
162166
</Transition>
163167
</div>
164168
</Listbox>
165-
<TextInput
169+
<TextEditor
166170
placeHolder={`Enter URL for a ${data.requestType} request`}
167171
onChangeHandler={handleUrlInputChange}
168172
name={'url'}

0 commit comments

Comments
 (0)