Refactor
Abstract dynamic button to a separate component; Move buttons to the JSONViewer; Move data-saving to a hook;
This commit is contained in:
parent
bab84f5db1
commit
f0b1e9c7d9
6 changed files with 133 additions and 133 deletions
|
@ -36,26 +36,3 @@ $navbar-width: 250px;
|
||||||
.react-datepicker-popper {
|
.react-datepicker-popper {
|
||||||
z-index: 30 !important;
|
z-index: 30 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.topic-message-button {
|
|
||||||
&::after {
|
|
||||||
content: attr(data-title);
|
|
||||||
position: absolute;
|
|
||||||
top: -140%;
|
|
||||||
z-index: 1;
|
|
||||||
background: #F5F5F5;
|
|
||||||
color: rgb(89, 89, 89);
|
|
||||||
border-radius: 5px;
|
|
||||||
font-size: 15px;
|
|
||||||
padding: 5px 10px;
|
|
||||||
opacity: 0;
|
|
||||||
pointer-events: none;
|
|
||||||
transition: .2s opacity;
|
|
||||||
transition-delay: 0s;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover::after {
|
|
||||||
opacity: 1;
|
|
||||||
transition-delay: .5s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -15,72 +15,19 @@ const MessageItem: React.FC<MessageItemProp> = ({
|
||||||
offset,
|
offset,
|
||||||
timestamp,
|
timestamp,
|
||||||
content,
|
content,
|
||||||
}) => {
|
}) => (
|
||||||
const copyData = () => {
|
<tr>
|
||||||
if (navigator.clipboard)
|
<td style={{ width: 200 }}>{format(timestamp, 'yyyy-MM-dd HH:mm:ss')}</td>
|
||||||
navigator.clipboard.writeText(JSON.stringify(content || {}));
|
<td style={{ width: 150 }}>{offset}</td>
|
||||||
};
|
<td style={{ width: 100 }}>{partition}</td>
|
||||||
const saveFile = () => {
|
<td style={{ wordBreak: 'break-word' }}>
|
||||||
let extension = 'json';
|
{content && (
|
||||||
if (typeof content === 'string') {
|
<div>
|
||||||
try {
|
<JSONViewer data={content as { [key: string]: string }} />
|
||||||
JSON.parse(content);
|
</div>
|
||||||
} catch (e) {
|
)}
|
||||||
extension = 'txt';
|
</td>
|
||||||
}
|
</tr>
|
||||||
}
|
);
|
||||||
const dataStr = `data:text/json;charset=utf-8,${encodeURIComponent(
|
|
||||||
JSON.stringify(content || {})
|
|
||||||
)}`;
|
|
||||||
const downloadAnchorNode = document.createElement('a');
|
|
||||||
downloadAnchorNode.setAttribute('href', dataStr);
|
|
||||||
downloadAnchorNode.setAttribute(
|
|
||||||
'download',
|
|
||||||
`topic-message[${timestamp}].${extension}`
|
|
||||||
);
|
|
||||||
document.body.appendChild(downloadAnchorNode);
|
|
||||||
downloadAnchorNode.click();
|
|
||||||
downloadAnchorNode.remove();
|
|
||||||
};
|
|
||||||
|
|
||||||
const buttonStyle = {
|
|
||||||
height: '30px',
|
|
||||||
};
|
|
||||||
const buttonClasses = 'button is-link is-outlined topic-message-button';
|
|
||||||
return (
|
|
||||||
<tr>
|
|
||||||
<td style={{ width: 200 }}>{format(timestamp, 'yyyy-MM-dd HH:mm:ss')}</td>
|
|
||||||
<td style={{ width: 150 }}>{offset}</td>
|
|
||||||
<td style={{ width: 100 }}>{partition}</td>
|
|
||||||
<td style={{ wordBreak: 'break-word' }}>
|
|
||||||
{content && (
|
|
||||||
<div>
|
|
||||||
<div style={{ display: 'flex', justifyContent: 'center' }}>
|
|
||||||
<button
|
|
||||||
className={buttonClasses}
|
|
||||||
data-title="Copy the message to the clipboard"
|
|
||||||
type="button"
|
|
||||||
style={{ ...buttonStyle, marginRight: '5px' }}
|
|
||||||
onClick={copyData}
|
|
||||||
>
|
|
||||||
<i className="far fa-clipboard" />
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className={buttonClasses}
|
|
||||||
data-title="Download the message as a .json/.txt file"
|
|
||||||
type="button"
|
|
||||||
style={buttonStyle}
|
|
||||||
onClick={saveFile}
|
|
||||||
>
|
|
||||||
<i className="fas fa-file-download" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<JSONViewer data={content as { [key: string]: string }} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default MessageItem;
|
export default MessageItem;
|
||||||
|
|
|
@ -37,46 +37,6 @@ exports[`MessageItem when content is defined matches snapshot 1`] = `
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<div
|
|
||||||
style={
|
|
||||||
Object {
|
|
||||||
"display": "flex",
|
|
||||||
"justifyContent": "center",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
className="button is-link is-outlined topic-message-button"
|
|
||||||
data-title="Copy the message to the clipboard"
|
|
||||||
onClick={[Function]}
|
|
||||||
style={
|
|
||||||
Object {
|
|
||||||
"height": "30px",
|
|
||||||
"marginRight": "5px",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
className="far fa-clipboard"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className="button is-link is-outlined topic-message-button"
|
|
||||||
data-title="Download the message as a .json/.txt file"
|
|
||||||
onClick={[Function]}
|
|
||||||
style={
|
|
||||||
Object {
|
|
||||||
"height": "30px",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
className="fas fa-file-download"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<JSONViewer
|
<JSONViewer
|
||||||
data={
|
data={
|
||||||
Object {
|
Object {
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface ButtonProps {
|
||||||
|
callback: () => void;
|
||||||
|
classes?: string;
|
||||||
|
title: string;
|
||||||
|
style?: { [key: string]: string | number };
|
||||||
|
text: {
|
||||||
|
default: string;
|
||||||
|
dynamic: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const DynamicButton: React.FC<ButtonProps> = ({
|
||||||
|
callback,
|
||||||
|
classes,
|
||||||
|
title,
|
||||||
|
style,
|
||||||
|
text,
|
||||||
|
children,
|
||||||
|
}) => {
|
||||||
|
const [buttonText, setButtonText] = React.useState(text.default);
|
||||||
|
let timeout: number;
|
||||||
|
const clickHandler = () => {
|
||||||
|
callback();
|
||||||
|
setButtonText(text.dynamic);
|
||||||
|
timeout = window.setTimeout(() => setButtonText(text.default), 3000);
|
||||||
|
};
|
||||||
|
React.useEffect(() => () => window.clearTimeout(timeout), [callback]);
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className={classes}
|
||||||
|
title={title}
|
||||||
|
type="button"
|
||||||
|
style={style}
|
||||||
|
onClick={clickHandler}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<span>{buttonText}</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DynamicButton;
|
|
@ -1,6 +1,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import JSONTree from 'react-json-tree';
|
import JSONTree from 'react-json-tree';
|
||||||
|
import useDataSaver from 'lib/hooks/useDataSaver';
|
||||||
import theme from './themes/google';
|
import theme from './themes/google';
|
||||||
|
import DynamicButton from './DynamicButton';
|
||||||
|
|
||||||
interface JSONViewerProps {
|
interface JSONViewerProps {
|
||||||
data: {
|
data: {
|
||||||
|
@ -8,8 +10,51 @@ interface JSONViewerProps {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const JSONViewer: React.FC<JSONViewerProps> = ({ data }) => (
|
const JSONViewer: React.FC<JSONViewerProps> = ({ data }) => {
|
||||||
<JSONTree data={data} theme={theme} shouldExpandNode={() => true} hideRoot />
|
const { copyToClipboard, saveFile } = useDataSaver();
|
||||||
);
|
const copyButtonHandler = () => {
|
||||||
|
copyToClipboard(JSON.stringify(data));
|
||||||
|
};
|
||||||
|
const buttonClasses = 'button is-link is-outlined is-small is-centered';
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
className="field has-addons"
|
||||||
|
style={{
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DynamicButton
|
||||||
|
callback={copyButtonHandler}
|
||||||
|
classes={buttonClasses}
|
||||||
|
title="Copy the message to the clipboard"
|
||||||
|
style={{ marginRight: '5px' }}
|
||||||
|
text={{ default: 'Copy', dynamic: 'Copied!' }}
|
||||||
|
>
|
||||||
|
<span className="icon">
|
||||||
|
<i className="far fa-clipboard" />
|
||||||
|
</span>
|
||||||
|
</DynamicButton>
|
||||||
|
<button
|
||||||
|
className={buttonClasses}
|
||||||
|
title="Download the message as a .json/.txt file"
|
||||||
|
type="button"
|
||||||
|
onClick={() => saveFile(JSON.stringify(data), `topic-message`)}
|
||||||
|
>
|
||||||
|
<span className="icon">
|
||||||
|
<i className="fas fa-file-download" />
|
||||||
|
</span>
|
||||||
|
<span>Save</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<JSONTree
|
||||||
|
data={data}
|
||||||
|
theme={theme}
|
||||||
|
shouldExpandNode={() => true}
|
||||||
|
hideRoot
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default JSONViewer;
|
export default JSONViewer;
|
||||||
|
|
27
kafka-ui-react-app/src/lib/hooks/useDataSaver.tsx
Normal file
27
kafka-ui-react-app/src/lib/hooks/useDataSaver.tsx
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
const useDataSaver = () => {
|
||||||
|
const copyToClipboard = (content: string) => {
|
||||||
|
if (navigator.clipboard) navigator.clipboard.writeText(content);
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveFile = (content: string, fileName: string) => {
|
||||||
|
let extension = 'json';
|
||||||
|
try {
|
||||||
|
JSON.parse(content);
|
||||||
|
} catch (e) {
|
||||||
|
extension = 'txt';
|
||||||
|
}
|
||||||
|
const dataStr = `data:text/json;charset=utf-8,${encodeURIComponent(
|
||||||
|
content
|
||||||
|
)}`;
|
||||||
|
const downloadAnchorNode = document.createElement('a');
|
||||||
|
downloadAnchorNode.setAttribute('href', dataStr);
|
||||||
|
downloadAnchorNode.setAttribute('download', `${fileName}.${extension}`);
|
||||||
|
document.body.appendChild(downloadAnchorNode);
|
||||||
|
downloadAnchorNode.click();
|
||||||
|
downloadAnchorNode.remove();
|
||||||
|
};
|
||||||
|
|
||||||
|
return { copyToClipboard, saveFile };
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useDataSaver;
|
Loading…
Add table
Reference in a new issue