feat: add WSRequestBodyMode component and language detection

- Introduced a new component for selecting request body modes (JSON, XML, TEXT).
- Implemented auto-detection of language for the request body content.
- Created a styled wrapper for improved UI presentation.
This commit is contained in:
Siddharth Gelera
2025-09-22 15:44:22 +05:30
parent ec9b321e53
commit e40d99e515
5 changed files with 146 additions and 12 deletions

View File

@@ -0,0 +1,30 @@
import styled from 'styled-components';
const Wrapper = styled.div`
font-size: 0.8125rem;
.body-mode-selector {
background: transparent;
border-radius: 3px;
.dropdown-item {
padding: 0.2rem 0.6rem !important;
padding-left: 1.5rem !important;
}
.label-item {
padding: 0.2rem 0.6rem !important;
}
.selected-body-mode {
color: ${(props) => props.theme.colors.text.yellow};
}
}
.caret {
color: rgb(140, 140, 140);
fill: rgb(140 140 140);
}
`;
export default Wrapper;

View File

@@ -0,0 +1,68 @@
import React, { useRef, forwardRef } from 'react';
import { IconCaretDown } from '@tabler/icons';
import Dropdown from 'components/Dropdown';
import { humanizeRequestBodyMode } from 'utils/collections';
import StyledWrapper from './StyledWrapper';
const WSRequestBodyMode = ({ mode, onModeChange }) => {
const dropdownTippyRef = useRef();
const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
const Icon = forwardRef((props, ref) => {
return (
<div ref={ref} className="flex items-center justify-center pl-3 py-1 select-none selected-body-mode">
{humanizeRequestBodyMode(mode)} <IconCaretDown className="caret ml-2" size={14} strokeWidth={2} />
</div>
);
});
return (
<StyledWrapper>
<div className="inline-flex items-center cursor-pointer body-mode-selector">
<Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement="bottom-end">
<div className="label-item font-medium">Raw</div>
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
onModeChange('json');
}}
>
JSON
</div>
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
onModeChange('xml');
}}
>
XML
</div>
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
onModeChange('text');
}}
>
TEXT
</div>
{/* <div className="label-item font-medium">Other</div>
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
onModeChange('file');
}}
>
File / Binary
</div> */}
</Dropdown>
</div>
</StyledWrapper>
);
};
export default WSRequestBodyMode;

View File

@@ -4,14 +4,14 @@ import { saveRequest } from 'providers/ReduxStore/slices/collections/actions';
import { useTheme } from 'providers/Theme';
import React, { useEffect, useRef, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { generateGrpcSampleMessage, sendWsRequest } from 'utils/network/index';
import { IconChevronDown, IconChevronUp, IconPlus, IconRefresh, IconSend, IconTrash, IconWand } from '@tabler/icons';
import { IconChevronDown, IconChevronUp, IconPlus, IconTrash, IconWand } from '@tabler/icons';
import CodeEditor from 'components/CodeEditor/index';
import ToolHint from 'components/ToolHint/index';
import { applyEdits, format } from 'jsonc-parser';
import toast from 'react-hot-toast';
import { toastError } from 'utils/common/error';
import StyledWrapper from './StyledWrapper';
import WSRequestBodyMode from './BodyMode/index';
import { autoDetectLang } from 'utils/codemirror/lang-detect';
const SingleWSMessage = ({
message,
@@ -25,14 +25,12 @@ const SingleWSMessage = ({
canClientSendMultipleMessages
}) => {
const dispatch = useDispatch();
const { displayedTheme, theme } = useTheme();
const { displayedTheme } = useTheme();
const preferences = useSelector((state) => state.app.preferences);
const body = item.draft ? get(item, 'draft.request.body') : get(item, 'request.body');
const isConnectionActive = useSelector((state) => state.collections.activeConnections.includes(item.uid));
const canClientStream = methodType === 'client-streaming' || methodType === 'bidi-streaming';
const { name, content } = message;
const [messageFormat, setMessageFormat] = useState(autoDetectLang(content));
const onEdit = (value) => {
const currentMessages = [...(body.ws || [])];
@@ -92,6 +90,12 @@ const SingleWSMessage = ({
const getContainerHeight =
canClientSendMultipleMessages && body.ws.length > 1 ? `${isCollapsed ? '' : 'h-80'}` : 'h-full';
const codemirrorMode = {
text: 'application/text',
xml: 'application/xml',
json: 'application/ld+json'
};
return (
<div
className={`flex flex-col mb-3 border border-neutral-200 dark:border-neutral-800 rounded-md overflow-hidden ${getContainerHeight} relative`}
@@ -106,9 +110,9 @@ const SingleWSMessage = ({
) : (
<IconChevronUp size={16} strokeWidth={1.5} className="text-zinc-700 dark:text-zinc-300" />
)}
<span className="font-medium text-sm">{`Message ${canClientStream ? index + 1 : ''}`}</span>
</div>
<div className="flex items-center gap-2" onClick={(e) => e.stopPropagation()}>
<WSRequestBodyMode mode={messageFormat} onModeChange={setMessageFormat} />
<ToolHint text="Format JSON with proper indentation and spacing" toolhintId={`prettify-msg-${index}`}>
<button
onClick={onPrettify}
@@ -130,7 +134,6 @@ const SingleWSMessage = ({
)}
</div>
</div>
{!isCollapsed && (
<div className={`flex ${body.ws.length === 1 || !canClientSendMultipleMessages ? 'h-full' : 'h-80'} relative`}>
<CodeEditor
@@ -142,7 +145,7 @@ const SingleWSMessage = ({
onEdit={onEdit}
onRun={handleRun}
onSave={onSave}
mode="application/ld+json"
mode={codemirrorMode[messageFormat] ?? 'text/plain'}
enableVariableHighlighting={true}
/>
</div>

View File

@@ -0,0 +1,34 @@
/**
* @param {string} snippet
* @returns {boolean}
*/
export function isXML(snippet){
return /<\/?[a-z][\s\S]*>/i.test(snippet)
}
/**
* @param {string} snippet
* @returns {boolean}
*/
export function isJSON(snippet) {
try {
JSON.parse(snippet);
return true;
} catch (err) {
return false;
}
}
/**
* @param {string} snippet
* @returns {string}
*/
export function autoDetectLang(snippet) {
if (isJSON(snippet)) {
return 'json';
}
if(isXML(snippet)){
return 'xml'
}
return 'text'
}

View File

@@ -1016,14 +1016,13 @@ const sem = grammar.createSemantics().addAttribute('ast', {
try {
JSON.parse(messageContent);
} catch (error) {
console.error('Error validating ws message JSON:', error);
return {
body: {
mode: 'ws',
ws: [
{
name: messageName,
content: '{}'
content: messageContent
}
]
}