mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-25 05:35:41 +00:00
feat: beautify the code editor in each message
This commit is contained in:
13
package-lock.json
generated
13
package-lock.json
generated
@@ -15363,6 +15363,18 @@
|
||||
"he": "bin/he"
|
||||
}
|
||||
},
|
||||
"node_modules/hexy": {
|
||||
"version": "0.3.5",
|
||||
"resolved": "https://registry.npmjs.org/hexy/-/hexy-0.3.5.tgz",
|
||||
"integrity": "sha512-UCP7TIZPXz5kxYJnNOym+9xaenxCLor/JyhKieo8y8/bJWunGh9xbhy3YrgYJUQ87WwfXGm05X330DszOfINZw==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"hexy": "bin/hexy_cmd.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.4"
|
||||
}
|
||||
},
|
||||
"node_modules/hey-listen": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/hey-listen/-/hey-listen-1.0.8.tgz",
|
||||
@@ -30112,6 +30124,7 @@
|
||||
"form-data": "^4.0.0",
|
||||
"fs-extra": "^10.1.0",
|
||||
"graphql": "^16.6.0",
|
||||
"hexy": "^0.3.5",
|
||||
"http-proxy-agent": "^7.0.0",
|
||||
"https-proxy-agent": "^7.0.2",
|
||||
"iconv-lite": "^0.6.3",
|
||||
|
||||
@@ -45,7 +45,7 @@ export default class CodeEditor extends React.Component {
|
||||
const editor = (this.editor = CodeMirror(this._node, {
|
||||
value: this.props.value || '',
|
||||
lineNumbers: true,
|
||||
lineWrapping: true,
|
||||
lineWrapping: this.props.enableLineWrapping ?? true,
|
||||
tabSize: TAB_SIZE,
|
||||
mode: this.props.mode || 'application/ld+json',
|
||||
brunoVarInfo: {
|
||||
@@ -237,6 +237,14 @@ export default class CodeEditor extends React.Component {
|
||||
this.editor.scrollTo(null, this.props.initialScroll);
|
||||
}
|
||||
|
||||
if (this.props.enableLineWrapping !== prevProps.enableLineWrapping){
|
||||
this.editor.setOption("lineWrapping", this.props.enableLineWrapping);
|
||||
}
|
||||
|
||||
if (this.props.mode !== prevProps.mode){
|
||||
this.editor.setOption("mode", this.props.mode);
|
||||
}
|
||||
|
||||
this.ignoreChangeEvent = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,11 +25,20 @@ const StyledWrapper = styled.div`
|
||||
border-color: ${(props) => props.theme.table.border};
|
||||
}
|
||||
|
||||
.CodeMirror {
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
.CodeMirror-foldgutter, .CodeMirror-linenumbers, .CodeMirror-lint-markers {
|
||||
background: ${({theme})=> theme.bg};
|
||||
}
|
||||
|
||||
div[role='tablist'] {
|
||||
.active {
|
||||
color: ${(props) => props.theme.colors.text.yellow};
|
||||
}
|
||||
}
|
||||
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
|
||||
@@ -34,7 +34,7 @@ const parseContent = (content) => {
|
||||
let contentMeta = getContentMeta(content);
|
||||
return {
|
||||
type: contentMeta.isJSON ? 'application/json' : 'text/plain',
|
||||
content: contentMeta.isJSON ? JSON.stringify(JSON.parse(contentMeta.content), null, 2) : contentMeta.content,
|
||||
content: contentMeta.isJSON ? JSON.stringify(JSON.parse(contentMeta.content), null, 2) : contentMeta.content
|
||||
};
|
||||
};
|
||||
|
||||
@@ -47,67 +47,66 @@ const getDataTypeText = (type) => {
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {"incoming"|"outgoing"|"info"} type
|
||||
*
|
||||
* @param {"incoming"|"outgoing"|"info"} type
|
||||
*/
|
||||
const TypeIcon = ({type})=>{
|
||||
const TypeIcon = ({ type }) => {
|
||||
const commonProps = {
|
||||
size: 18
|
||||
}
|
||||
};
|
||||
return {
|
||||
"incoming": <IconArrowDownLeft {...commonProps} />,
|
||||
"outgoing": <IconArrowUpRight {...commonProps} />,
|
||||
"info": <IconInfoCircle {...commonProps} />
|
||||
}[type]
|
||||
}
|
||||
incoming: <IconArrowDownLeft {...commonProps} />,
|
||||
outgoing: <IconArrowUpRight {...commonProps} />,
|
||||
info: <IconInfoCircle {...commonProps} />
|
||||
}[type];
|
||||
};
|
||||
|
||||
const WSMessageItem = ({ message, inFocus }) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [showHex, setShowHex] = useState(false);
|
||||
const preferences = useSelector((state) => state.app.preferences);
|
||||
const { displayedTheme } = useTheme();
|
||||
const [isNew, setIsNew] = useState(false)
|
||||
const notified = useRef(false)
|
||||
const [isNew, setIsNew] = useState(false);
|
||||
const notified = useRef(false);
|
||||
|
||||
const isIncoming = message.type === 'incoming';
|
||||
const isInfo = message.type === 'info';
|
||||
let contentHexdump = message.messageHexdump;
|
||||
let parsedContent = parseContent(message.message);
|
||||
const dataType = getDataTypeText(parsedContent.type);
|
||||
|
||||
useEffect(()=>{
|
||||
if(notified.current === true) return
|
||||
const dateDiff = Date.now() - new Date(message.timestamp).getTime()
|
||||
if(dateDiff < 1000 * 10){
|
||||
setIsNew(true)
|
||||
setTimeout(()=>{
|
||||
notified.current = true
|
||||
setIsNew(false)
|
||||
},2500)
|
||||
useEffect(() => {
|
||||
if (notified.current === true) return;
|
||||
const dateDiff = Date.now() - new Date(message.timestamp).getTime();
|
||||
if (dateDiff < 1000 * 10) {
|
||||
setIsNew(true);
|
||||
setTimeout(() => {
|
||||
notified.current = true;
|
||||
setIsNew(false);
|
||||
}, 2500);
|
||||
}
|
||||
}, [message])
|
||||
|
||||
}, [message]);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={(node) => {
|
||||
if (!node) return;
|
||||
if (inFocus) node.scrollIntoView()
|
||||
if (inFocus) node.scrollIntoView();
|
||||
}}
|
||||
className={classnames('ws-message flex flex-col p-2', {
|
||||
'ws-incoming': isIncoming,
|
||||
'ws-outgoing': !isIncoming,
|
||||
'open': isOpen,
|
||||
'new': isNew
|
||||
open: isOpen,
|
||||
new: isNew
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={
|
||||
classnames("flex items-center justify-between",{
|
||||
"cursor-pointer": !isInfo,
|
||||
"cursor-not-allowed": isInfo
|
||||
})
|
||||
}
|
||||
className={classnames('flex items-center justify-between', {
|
||||
'cursor-pointer': !isInfo,
|
||||
'cursor-not-allowed': isInfo
|
||||
})}
|
||||
onClick={(e) => {
|
||||
if(isInfo) return
|
||||
if (isInfo) return;
|
||||
setIsOpen(!isOpen);
|
||||
}}
|
||||
>
|
||||
@@ -126,26 +125,50 @@ const WSMessageItem = ({ message, inFocus }) => {
|
||||
{message.timestamp && (
|
||||
<span className="text-xs text-gray-400">{new Date(message.timestamp).toISOString()}</span>
|
||||
)}
|
||||
{!isInfo && <span className="text-gray-600">
|
||||
{isOpen? (
|
||||
<IconChevronDown size={16} strokeWidth={1.5} className="text-zinc-700 dark:text-zinc-300" />
|
||||
) : (
|
||||
<IconChevronUp size={16} strokeWidth={1.5} className="text-zinc-700 dark:text-zinc-300" />
|
||||
)}
|
||||
</span>}
|
||||
{!isInfo && (
|
||||
<span className="text-gray-600">
|
||||
{isOpen ? (
|
||||
<IconChevronDown size={16} strokeWidth={1.5} className="text-zinc-700 dark:text-zinc-300" />
|
||||
) : (
|
||||
<IconChevronUp size={16} strokeWidth={1.5} className="text-zinc-700 dark:text-zinc-300" />
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{isOpen && (
|
||||
<div className="my-2 relative h-[300px] w-full">
|
||||
<CodeEditor
|
||||
mode={parsedContent.type}
|
||||
theme={displayedTheme}
|
||||
font={preferences.codeFont || 'default'}
|
||||
value={parsedContent.content}
|
||||
/>
|
||||
<div className="absolute top-1 right-1 p-1 rounded-sm">
|
||||
{isOpen ? <span className="text-xs mr-1 font-bold">{dataType}</span> : null}
|
||||
<div className="my-2 h-[300px] w-full">
|
||||
<div className="w-full flex items-center justify-end gap-2 ws-message-toolbar top-1 right-0 p-1 rounded-sm">
|
||||
<div className="flex justify-end gap-2 text-xs" role="tablist">
|
||||
<div
|
||||
className={classnames('select-none capitalize', {
|
||||
active: showHex,
|
||||
'cursor-pointer': !showHex
|
||||
})}
|
||||
role="tab"
|
||||
onClick={() => setShowHex(true)}
|
||||
>
|
||||
hexdump
|
||||
</div>
|
||||
<div
|
||||
className={classnames('select-none capitalize', {
|
||||
active: !showHex,
|
||||
'cursor-pointer': showHex
|
||||
})}
|
||||
role="tab"
|
||||
onClick={() => setShowHex(false)}
|
||||
>
|
||||
{dataType.toLowerCase()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<CodeEditor
|
||||
mode={showHex ? 'text/plain' : parsedContent.type}
|
||||
theme={displayedTheme}
|
||||
enableLineWrapping={showHex ? false : true}
|
||||
font={preferences.codeFont || 'default'}
|
||||
value={showHex ? contentHexdump : parsedContent.content}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -156,14 +179,13 @@ const WSMessagesList = ({ order = -1, messages = [] }) => {
|
||||
if (!messages.length) {
|
||||
return <div className="p-4 text-gray-500">No messages yet.</div>;
|
||||
}
|
||||
const ordered = order === -1 ? messages : messages.slice().reverse()
|
||||
const ordered = order === -1 ? messages : messages.slice().reverse();
|
||||
return (
|
||||
<StyledWrapper className="ws-messages-list flex flex-col">
|
||||
{ordered
|
||||
.map((msg, idx, src) => {
|
||||
const inFocus = order === -1 ? src.length - 1 === idx : idx === 0;
|
||||
return <WSMessageItem inFocus={inFocus} id={idx} message={msg} />;
|
||||
})}
|
||||
{ordered.map((msg, idx, src) => {
|
||||
const inFocus = order === -1 ? src.length - 1 === idx : idx === 0;
|
||||
return <WSMessageItem inFocus={inFocus} id={idx} message={msg} />;
|
||||
})}
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -57,6 +57,7 @@
|
||||
"form-data": "^4.0.0",
|
||||
"fs-extra": "^10.1.0",
|
||||
"graphql": "^16.6.0",
|
||||
"hexy": "^0.3.5",
|
||||
"http-proxy-agent": "^7.0.0",
|
||||
"https-proxy-agent": "^7.0.2",
|
||||
"iconv-lite": "^0.6.3",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import ws from 'ws';
|
||||
import { hexy as hexdump } from "hexy"
|
||||
|
||||
/**
|
||||
* Safely parse JSON string with error handling
|
||||
@@ -169,6 +170,7 @@ class WsClient {
|
||||
// Emit message sent event
|
||||
this.eventCallback('ws:message', requestId, collectionUid, {
|
||||
message: messageToSend,
|
||||
messageHexdump: hexdump(messageToSend),
|
||||
type: 'outgoing',
|
||||
timestamp: Date.now()
|
||||
});
|
||||
@@ -291,6 +293,7 @@ class WsClient {
|
||||
const message = JSON.parse(data.toString());
|
||||
this.eventCallback('ws:message', requestId, collectionUid, {
|
||||
message,
|
||||
messageHexdump: hexdump(data),
|
||||
type: 'incoming',
|
||||
timestamp: Date.now()
|
||||
});
|
||||
@@ -298,6 +301,7 @@ class WsClient {
|
||||
// If parsing fails, send as raw data
|
||||
this.eventCallback('ws:message', requestId, collectionUid, {
|
||||
message: data.toString(),
|
||||
messageHexdump: hexdump(data),
|
||||
type: 'incoming',
|
||||
timestamp: Date.now()
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user