mirror of
https://github.com/RoboSats/robosats.git
synced 2024-12-13 19:06:26 +00:00
Prepare for v0.2.3 candidates
This commit is contained in:
parent
227610c84a
commit
046898deda
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "frontend",
|
"name": "frontend",
|
||||||
"version": "0.2.2",
|
"version": "0.2.3",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -1,565 +0,0 @@
|
|||||||
import React, { Component } from 'react';
|
|
||||||
import { withTranslation } from 'react-i18next';
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
IconButton,
|
|
||||||
Badge,
|
|
||||||
Tooltip,
|
|
||||||
TextField,
|
|
||||||
Grid,
|
|
||||||
Container,
|
|
||||||
Card,
|
|
||||||
CardHeader,
|
|
||||||
Paper,
|
|
||||||
Avatar,
|
|
||||||
Typography,
|
|
||||||
} from '@mui/material';
|
|
||||||
import ReconnectingWebSocket from 'reconnecting-websocket';
|
|
||||||
import { encryptMessage, decryptMessage } from '../../pgp';
|
|
||||||
import { saveAsJson } from '../../utils';
|
|
||||||
import { AuditPGPDialog } from '../Dialogs';
|
|
||||||
import RobotAvatar from '../RobotAvatar';
|
|
||||||
import { systemClient } from '../../services/System';
|
|
||||||
import { websocketClient } from '../../services/Websocket';
|
|
||||||
|
|
||||||
// Icons
|
|
||||||
import CheckIcon from '@mui/icons-material/Check';
|
|
||||||
import CloseIcon from '@mui/icons-material/Close';
|
|
||||||
import ContentCopy from '@mui/icons-material/ContentCopy';
|
|
||||||
import VisibilityIcon from '@mui/icons-material/Visibility';
|
|
||||||
import CircularProgress from '@mui/material/CircularProgress';
|
|
||||||
import KeyIcon from '@mui/icons-material/Key';
|
|
||||||
import { ExportIcon } from '../Icons';
|
|
||||||
|
|
||||||
class Chat extends Component {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
}
|
|
||||||
|
|
||||||
state = {
|
|
||||||
own_pub_key: systemClient.getCookie('pub_key').split('\\').join('\n'),
|
|
||||||
own_enc_priv_key: systemClient.getCookie('enc_priv_key').split('\\').join('\n'),
|
|
||||||
peer_pub_key: null,
|
|
||||||
token: systemClient.getCookie('robot_token'),
|
|
||||||
messages: [],
|
|
||||||
value: '',
|
|
||||||
connected: false,
|
|
||||||
connection: null,
|
|
||||||
peer_connected: false,
|
|
||||||
audit: false,
|
|
||||||
showPGP: new Array(),
|
|
||||||
waitingEcho: false,
|
|
||||||
lastSent: '---BLANK---',
|
|
||||||
latestIndex: 0,
|
|
||||||
scrollNow: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
websocketClient
|
|
||||||
.open(`ws://${window.location.host}/ws/chat/${this.props.orderId}/`)
|
|
||||||
.then((connection) => {
|
|
||||||
console.log('Connected!');
|
|
||||||
|
|
||||||
connection.send({
|
|
||||||
message: this.state.own_pub_key,
|
|
||||||
nick: this.props.ur_nick,
|
|
||||||
});
|
|
||||||
|
|
||||||
connection.onMessage(this.onMessage);
|
|
||||||
connection.onClose(() => {
|
|
||||||
console.log('Socket is closed. Reconnect will be attempted');
|
|
||||||
this.setState({ connected: false });
|
|
||||||
});
|
|
||||||
connection.onError(() => {
|
|
||||||
console.error('Socket encountered error: Closing socket');
|
|
||||||
this.setState({ connected: false });
|
|
||||||
});
|
|
||||||
|
|
||||||
this.setState({ connected: true, connection });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate() {
|
|
||||||
// Only fire the scroll and audio when the reason for Update is a new message
|
|
||||||
if (this.state.scrollNow) {
|
|
||||||
const audio = new Audio(`/static/assets/sounds/chat-open.mp3`);
|
|
||||||
audio.play();
|
|
||||||
this.scrollToBottom();
|
|
||||||
this.setState({ scrollNow: false });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMessage = (message) => {
|
|
||||||
const dataFromServer = JSON.parse(message.data);
|
|
||||||
console.log('Got reply!', dataFromServer.type);
|
|
||||||
console.log('PGP message index', dataFromServer.index, ' latestIndex ', this.state.latestIndex);
|
|
||||||
if (dataFromServer) {
|
|
||||||
console.log(dataFromServer);
|
|
||||||
this.setState({ peer_connected: dataFromServer.peer_connected });
|
|
||||||
|
|
||||||
// If we receive our own key on a message
|
|
||||||
if (dataFromServer.message == this.state.own_pub_key) {
|
|
||||||
console.log('OWN PUB KEY RECEIVED!!');
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we receive a public key other than ours (our peer key!)
|
|
||||||
if (
|
|
||||||
dataFromServer.message.substring(0, 36) == `-----BEGIN PGP PUBLIC KEY BLOCK-----` &&
|
|
||||||
dataFromServer.message != this.state.own_pub_key
|
|
||||||
) {
|
|
||||||
if (dataFromServer.message == this.state.peer_pub_key) {
|
|
||||||
console.log('PEER HAS RECONNECTED USING HIS PREVIOUSLY KNOWN PUBKEY');
|
|
||||||
} else if (
|
|
||||||
(dataFromServer.message != this.state.peer_pub_key) &
|
|
||||||
(this.state.peer_pub_key != null)
|
|
||||||
) {
|
|
||||||
console.log('PEER PUBKEY HAS CHANGED');
|
|
||||||
}
|
|
||||||
console.log('PEER PUBKEY RECEIVED!!');
|
|
||||||
this.setState({ peer_pub_key: dataFromServer.message });
|
|
||||||
|
|
||||||
// After receiving the peer pubkey we ask the server for the historic messages if any
|
|
||||||
this.state.connection.send({
|
|
||||||
message: `-----SERVE HISTORY-----`,
|
|
||||||
nick: this.props.ur_nick,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we receive an encrypted message
|
|
||||||
else if (
|
|
||||||
dataFromServer.message.substring(0, 27) == `-----BEGIN PGP MESSAGE-----` &&
|
|
||||||
dataFromServer.index > this.state.latestIndex
|
|
||||||
) {
|
|
||||||
decryptMessage(
|
|
||||||
dataFromServer.message.split('\\').join('\n'),
|
|
||||||
dataFromServer.user_nick == this.props.ur_nick
|
|
||||||
? this.state.own_pub_key
|
|
||||||
: this.state.peer_pub_key,
|
|
||||||
this.state.own_enc_priv_key,
|
|
||||||
this.state.token,
|
|
||||||
).then((decryptedData) =>
|
|
||||||
this.setState((state) => ({
|
|
||||||
scrollNow: true,
|
|
||||||
waitingEcho:
|
|
||||||
this.state.waitingEcho == true
|
|
||||||
? decryptedData.decryptedMessage != this.state.lastSent
|
|
||||||
: false,
|
|
||||||
lastSent:
|
|
||||||
decryptedData.decryptedMessage == this.state.lastSent
|
|
||||||
? '----BLANK----'
|
|
||||||
: this.state.lastSent,
|
|
||||||
latestIndex:
|
|
||||||
dataFromServer.index > this.state.latestIndex
|
|
||||||
? dataFromServer.index
|
|
||||||
: this.state.latestIndex,
|
|
||||||
messages: [
|
|
||||||
...state.messages,
|
|
||||||
{
|
|
||||||
index: dataFromServer.index,
|
|
||||||
encryptedMessage: dataFromServer.message.split('\\').join('\n'),
|
|
||||||
plainTextMessage: decryptedData.decryptedMessage,
|
|
||||||
validSignature: decryptedData.validSignature,
|
|
||||||
userNick: dataFromServer.user_nick,
|
|
||||||
time: dataFromServer.time,
|
|
||||||
},
|
|
||||||
].sort(function (a, b) {
|
|
||||||
// order the message array by their index (increasing)
|
|
||||||
return a.index - b.index;
|
|
||||||
}),
|
|
||||||
})),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// We allow plaintext communication. The user must write # to start
|
|
||||||
// If we receive an plaintext message
|
|
||||||
else if (dataFromServer.message.substring(0, 1) == '#') {
|
|
||||||
console.log('Got plaintext message', dataFromServer.message);
|
|
||||||
this.setState((state) => ({
|
|
||||||
scrollNow: true,
|
|
||||||
messages: [
|
|
||||||
...state.messages,
|
|
||||||
{
|
|
||||||
index: this.state.latestIndex + 0.001,
|
|
||||||
encryptedMessage: dataFromServer.message,
|
|
||||||
plainTextMessage: dataFromServer.message,
|
|
||||||
validSignature: false,
|
|
||||||
userNick: dataFromServer.user_nick,
|
|
||||||
time: new Date().toString(),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
scrollToBottom = () => {
|
|
||||||
this.messagesEnd.scrollIntoView({ behavior: 'smooth' });
|
|
||||||
};
|
|
||||||
|
|
||||||
onButtonClicked = (e) => {
|
|
||||||
// If input string contains token. Do not set message
|
|
||||||
if (this.state.value.indexOf(this.state.token) !== -1) {
|
|
||||||
alert(
|
|
||||||
`Aye! You just sent your own robot token to your peer in chat, that's a catastrophic idea! So bad your message was blocked.`,
|
|
||||||
);
|
|
||||||
this.setState({ value: '' });
|
|
||||||
}
|
|
||||||
|
|
||||||
// If input string contains '#' send unencrypted and unlogged message
|
|
||||||
else if (this.state.value.substring(0, 1) == '#') {
|
|
||||||
this.state.connection.send({
|
|
||||||
message: this.state.value,
|
|
||||||
nick: this.props.ur_nick,
|
|
||||||
});
|
|
||||||
this.setState({ value: '' });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Else if message is not empty send message
|
|
||||||
else if (this.state.value != '') {
|
|
||||||
this.setState({ value: '', waitingEcho: true, lastSent: this.state.value });
|
|
||||||
encryptMessage(
|
|
||||||
this.state.value,
|
|
||||||
this.state.own_pub_key,
|
|
||||||
this.state.peer_pub_key,
|
|
||||||
this.state.own_enc_priv_key,
|
|
||||||
this.state.token,
|
|
||||||
).then(
|
|
||||||
(encryptedMessage) =>
|
|
||||||
console.log('Sending Encrypted MESSAGE', encryptedMessage) &
|
|
||||||
this.state.connection.send({
|
|
||||||
message: encryptedMessage.split('\n').join('\\'),
|
|
||||||
nick: this.props.ur_nick,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
e.preventDefault();
|
|
||||||
};
|
|
||||||
|
|
||||||
createJsonFile = () => {
|
|
||||||
return {
|
|
||||||
credentials: {
|
|
||||||
own_public_key: this.state.own_pub_key,
|
|
||||||
peer_public_key: this.state.peer_pub_key,
|
|
||||||
encrypted_private_key: this.state.own_enc_priv_key,
|
|
||||||
passphrase: this.state.token,
|
|
||||||
},
|
|
||||||
messages: this.state.messages,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
messageCard = (props) => {
|
|
||||||
const { t } = this.props;
|
|
||||||
return (
|
|
||||||
<Card elevation={5} align='left'>
|
|
||||||
<CardHeader
|
|
||||||
sx={{ color: '#333333' }}
|
|
||||||
avatar={
|
|
||||||
<RobotAvatar
|
|
||||||
statusColor={props.userConnected ? 'success' : 'error'}
|
|
||||||
nickname={props.message.userNick}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
style={{ backgroundColor: props.cardColor }}
|
|
||||||
title={
|
|
||||||
<Tooltip
|
|
||||||
placement='top'
|
|
||||||
enterTouchDelay={0}
|
|
||||||
enterDelay={500}
|
|
||||||
enterNextDelay={2000}
|
|
||||||
title={t(
|
|
||||||
props.message.validSignature
|
|
||||||
? 'Verified signature by {{nickname}}'
|
|
||||||
: 'Cannot verify signature of {{nickname}}',
|
|
||||||
{ nickname: props.message.userNick },
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
flexWrap: 'wrap',
|
|
||||||
position: 'relative',
|
|
||||||
left: -5,
|
|
||||||
width: 240,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style={{ width: 168, display: 'flex', alignItems: 'center', flexWrap: 'wrap' }}
|
|
||||||
>
|
|
||||||
{props.message.userNick}
|
|
||||||
{props.message.validSignature ? (
|
|
||||||
<CheckIcon sx={{ height: 16 }} color='success' />
|
|
||||||
) : (
|
|
||||||
<CloseIcon sx={{ height: 16 }} color='error' />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div style={{ width: 20 }}>
|
|
||||||
<IconButton
|
|
||||||
sx={{ height: 18, width: 18 }}
|
|
||||||
onClick={() =>
|
|
||||||
this.setState((prevState) => {
|
|
||||||
const newShowPGP = [...prevState.showPGP];
|
|
||||||
newShowPGP[props.index] = !newShowPGP[props.index];
|
|
||||||
return { showPGP: newShowPGP };
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<VisibilityIcon
|
|
||||||
color={this.state.showPGP[props.index] ? 'primary' : 'inherit'}
|
|
||||||
sx={{
|
|
||||||
height: 16,
|
|
||||||
width: 16,
|
|
||||||
color: this.state.showPGP[props.index] ? 'primary' : '#333333',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</IconButton>
|
|
||||||
</div>
|
|
||||||
<div style={{ width: 20 }}>
|
|
||||||
<Tooltip disableHoverListener enterTouchDelay={0} title={t('Copied!')}>
|
|
||||||
<IconButton
|
|
||||||
sx={{ height: 18, width: 18 }}
|
|
||||||
onClick={() =>
|
|
||||||
systemClient.copyToClipboard(
|
|
||||||
this.state.showPGP[props.index]
|
|
||||||
? props.message.encryptedMessage
|
|
||||||
: props.message.plainTextMessage,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<ContentCopy sx={{ height: 16, width: 16, color: '#333333' }} />
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
}
|
|
||||||
subheader={
|
|
||||||
this.state.showPGP[props.index] ? (
|
|
||||||
<a>
|
|
||||||
{' '}
|
|
||||||
{props.message.time} <br /> {'Valid signature: ' + props.message.validSignature}{' '}
|
|
||||||
<br /> {props.message.encryptedMessage}{' '}
|
|
||||||
</a>
|
|
||||||
) : (
|
|
||||||
props.message.plainTextMessage
|
|
||||||
)
|
|
||||||
}
|
|
||||||
subheaderTypographyProps={{
|
|
||||||
sx: {
|
|
||||||
wordWrap: 'break-word',
|
|
||||||
width: '200px',
|
|
||||||
color: '#444444',
|
|
||||||
fontSize: this.state.showPGP[props.index] ? 11 : null,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { t } = this.props;
|
|
||||||
return (
|
|
||||||
<Container component='main'>
|
|
||||||
<Grid container spacing={0.5}>
|
|
||||||
<Grid item xs={0.3} />
|
|
||||||
<Grid item xs={5.5}>
|
|
||||||
<Paper
|
|
||||||
elevation={1}
|
|
||||||
style={
|
|
||||||
this.state.connected
|
|
||||||
? { backgroundColor: '#e8ffe6' }
|
|
||||||
: { backgroundColor: '#FFF1C5' }
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Typography variant='caption' sx={{ color: '#333333' }}>
|
|
||||||
{t('You') + ': '}
|
|
||||||
{this.state.connected ? t('connected') : t('disconnected')}
|
|
||||||
</Typography>
|
|
||||||
</Paper>
|
|
||||||
</Grid>
|
|
||||||
<Grid item xs={0.4} />
|
|
||||||
<Grid item xs={5.5}>
|
|
||||||
<Paper
|
|
||||||
elevation={1}
|
|
||||||
style={
|
|
||||||
this.state.peer_connected
|
|
||||||
? { backgroundColor: '#e8ffe6' }
|
|
||||||
: { backgroundColor: '#FFF1C5' }
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Typography variant='caption' sx={{ color: '#333333' }}>
|
|
||||||
{t('Peer') + ': '}
|
|
||||||
{this.state.peer_connected ? t('connected') : t('disconnected')}
|
|
||||||
</Typography>
|
|
||||||
</Paper>
|
|
||||||
</Grid>
|
|
||||||
<Grid item xs={0.3} />
|
|
||||||
</Grid>
|
|
||||||
<div style={{ position: 'relative', left: '-2px', margin: '0 auto', width: '285px' }}>
|
|
||||||
<Paper
|
|
||||||
elevation={1}
|
|
||||||
style={{
|
|
||||||
height: '300px',
|
|
||||||
maxHeight: '300px',
|
|
||||||
width: '285px',
|
|
||||||
overflow: 'auto',
|
|
||||||
backgroundColor: '#F7F7F7',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{this.state.messages.map((message, index) => (
|
|
||||||
<li style={{ listStyleType: 'none' }} key={index}>
|
|
||||||
{message.userNick == this.props.ur_nick ? (
|
|
||||||
<this.messageCard
|
|
||||||
message={message}
|
|
||||||
index={index}
|
|
||||||
cardColor={'#eeeeee'}
|
|
||||||
userConnected={this.state.connected}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<this.messageCard
|
|
||||||
message={message}
|
|
||||||
index={index}
|
|
||||||
cardColor={'#fafafa'}
|
|
||||||
userConnected={this.state.peer_connected}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
<div
|
|
||||||
style={{ float: 'left', clear: 'both' }}
|
|
||||||
ref={(el) => {
|
|
||||||
this.messagesEnd = el;
|
|
||||||
}}
|
|
||||||
></div>
|
|
||||||
</Paper>
|
|
||||||
<form noValidate onSubmit={this.onButtonClicked}>
|
|
||||||
<Grid alignItems='stretch' style={{ display: 'flex' }}>
|
|
||||||
<Grid item alignItems='stretch' style={{ display: 'flex' }}>
|
|
||||||
<TextField
|
|
||||||
label={t('Type a message')}
|
|
||||||
variant='standard'
|
|
||||||
size='small'
|
|
||||||
helperText={
|
|
||||||
this.state.connected
|
|
||||||
? this.state.peer_pub_key
|
|
||||||
? null
|
|
||||||
: t('Waiting for peer public key...')
|
|
||||||
: t('Connecting...')
|
|
||||||
}
|
|
||||||
value={this.state.value}
|
|
||||||
onChange={(e) => {
|
|
||||||
this.setState({ value: e.target.value });
|
|
||||||
this.value = this.state.value;
|
|
||||||
}}
|
|
||||||
sx={{ width: 219 }}
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
<Grid item alignItems='stretch' style={{ display: 'flex' }}>
|
|
||||||
<Button
|
|
||||||
sx={{ width: 68 }}
|
|
||||||
disabled={
|
|
||||||
!this.state.connected ||
|
|
||||||
this.state.waitingEcho ||
|
|
||||||
this.state.peer_pub_key == null
|
|
||||||
}
|
|
||||||
type='submit'
|
|
||||||
variant='contained'
|
|
||||||
color='primary'
|
|
||||||
>
|
|
||||||
{this.state.waitingEcho ? (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
flexWrap: 'wrap',
|
|
||||||
minWidth: 68,
|
|
||||||
width: 68,
|
|
||||||
position: 'relative',
|
|
||||||
left: 15,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div style={{ width: 20 }}>
|
|
||||||
<KeyIcon sx={{ width: 18 }} />
|
|
||||||
</div>
|
|
||||||
<div style={{ width: 18 }}>
|
|
||||||
<CircularProgress size={16} thickness={5} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
t('Send')
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style={{ height: 4 }} />
|
|
||||||
|
|
||||||
<Grid container spacing={0}>
|
|
||||||
<AuditPGPDialog
|
|
||||||
open={this.state.audit}
|
|
||||||
onClose={() => this.setState({ audit: false })}
|
|
||||||
orderId={Number(this.props.orderId)}
|
|
||||||
messages={this.state.messages}
|
|
||||||
own_pub_key={this.state.own_pub_key}
|
|
||||||
own_enc_priv_key={this.state.own_enc_priv_key}
|
|
||||||
peer_pub_key={this.state.peer_pub_key ? this.state.peer_pub_key : 'Not received yet'}
|
|
||||||
passphrase={this.state.token}
|
|
||||||
onClickBack={() => this.setState({ audit: false })}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Grid item xs={6}>
|
|
||||||
<Tooltip
|
|
||||||
placement='bottom'
|
|
||||||
enterTouchDelay={0}
|
|
||||||
enterDelay={500}
|
|
||||||
enterNextDelay={2000}
|
|
||||||
title={t('Verify your privacy')}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
size='small'
|
|
||||||
color='primary'
|
|
||||||
variant='outlined'
|
|
||||||
onClick={() => this.setState({ audit: !this.state.audit })}
|
|
||||||
>
|
|
||||||
<KeyIcon />
|
|
||||||
{t('Audit PGP')}{' '}
|
|
||||||
</Button>
|
|
||||||
</Tooltip>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<Grid item xs={6}>
|
|
||||||
<Tooltip
|
|
||||||
placement='bottom'
|
|
||||||
enterTouchDelay={0}
|
|
||||||
enterDelay={500}
|
|
||||||
enterNextDelay={2000}
|
|
||||||
title={t('Save full log as a JSON file (messages and credentials)')}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
size='small'
|
|
||||||
color='primary'
|
|
||||||
variant='outlined'
|
|
||||||
onClick={() =>
|
|
||||||
saveAsJson(
|
|
||||||
'complete_log_chat_' + this.props.orderId + '.json',
|
|
||||||
this.createJsonFile(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div style={{ width: 28, height: 20 }}>
|
|
||||||
<ExportIcon sx={{ width: 20, height: 20 }} />
|
|
||||||
</div>{' '}
|
|
||||||
{t('Export')}{' '}
|
|
||||||
</Button>
|
|
||||||
</Tooltip>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
</Container>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default withTranslation()(Chat);
|
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@ -12,8 +12,8 @@ import {
|
|||||||
Paper,
|
Paper,
|
||||||
Typography,
|
Typography,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { encryptMessage, decryptMessage } from '../../../utils/pgp';
|
import { encryptMessage, decryptMessage } from '../../../pgp';
|
||||||
import { saveAsJson } from '../../../utils/saveFile';
|
import { saveAsJson } from '../../../utils';
|
||||||
import { AuditPGPDialog } from '../../Dialogs';
|
import { AuditPGPDialog } from '../../Dialogs';
|
||||||
import RobotAvatar from '../../RobotAvatar';
|
import RobotAvatar from '../../RobotAvatar';
|
||||||
import { systemClient } from '../../../services/System';
|
import { systemClient } from '../../../services/System';
|
||||||
|
@ -149,7 +149,7 @@ android {
|
|||||||
minSdkVersion rootProject.ext.minSdkVersion
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
versionCode 1
|
versionCode 1
|
||||||
versionName "0.2.2-alpha"
|
versionName "0.2.3-alpha"
|
||||||
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
|
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
|
||||||
|
|
||||||
if (isNewArchitectureEnabled()) {
|
if (isNewArchitectureEnabled()) {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "robosats",
|
"name": "robosats",
|
||||||
"version": "0.2.2",
|
"version": "0.2.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"android": "react-native run-android",
|
"android": "react-native run-android",
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"major": 0,
|
"major": 0,
|
||||||
"minor": 2,
|
"minor": 2,
|
||||||
"patch": 2
|
"patch": 3
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user