mirror of
https://github.com/RoboSats/robosats.git
synced 2025-01-31 10:31:35 +00:00
Add dynamic countdown. Attach countdown to expiry progress bar.
This commit is contained in:
parent
18a8038466
commit
abb1bdd0be
@ -56,8 +56,16 @@ class ChatRoomConsumer(AsyncWebsocketConsumer):
|
|||||||
message = event['message']
|
message = event['message']
|
||||||
nick = event['nick']
|
nick = event['nick']
|
||||||
|
|
||||||
|
# Insert a white space in words longer than 22 characters.
|
||||||
|
# Helps when messages overflow in a single line.
|
||||||
|
words = message.split(' ')
|
||||||
|
fix_message = ''
|
||||||
|
for word in words:
|
||||||
|
word = ' '.join(word[i:i+22] for i in range(0, len(word), 22))
|
||||||
|
fix_message = fix_message +' '+ word
|
||||||
|
|
||||||
await self.send(text_data=json.dumps({
|
await self.send(text_data=json.dumps({
|
||||||
'message': message,
|
'message': fix_message,
|
||||||
'user_nick': nick,
|
'user_nick': nick,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ from django.urls import path
|
|||||||
|
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
urlpatterns = [
|
# urlpatterns = [
|
||||||
path('', views.index, name='index'),
|
# path('', views.index, name='index'),
|
||||||
path('<str:order_id>/', views.room, name='order_chat'),
|
# path('<str:order_id>/', views.room, name='order_chat'),
|
||||||
]
|
# ]
|
@ -1,10 +1,7 @@
|
|||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
|
||||||
def index(request):
|
# def room(request, order_id):
|
||||||
return render(request, 'index.html', {})
|
# return render(request, 'chatroom.html', {
|
||||||
|
# 'order_id': order_id
|
||||||
def room(request, order_id):
|
# })
|
||||||
return render(request, 'chatroom.html', {
|
|
||||||
'order_id': order_id
|
|
||||||
})
|
|
8
frontend/package-lock.json
generated
8
frontend/package-lock.json
generated
@ -6503,6 +6503,14 @@
|
|||||||
"object-assign": "^4.1.1"
|
"object-assign": "^4.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-countdown": {
|
||||||
|
"version": "2.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-countdown/-/react-countdown-2.3.2.tgz",
|
||||||
|
"integrity": "sha512-Q4SADotHtgOxNWhDdvgupmKVL0pMB9DvoFcxv5AzjsxVhzOVxnttMbAywgqeOdruwEAmnPhOhNv/awAgkwru2w==",
|
||||||
|
"requires": {
|
||||||
|
"prop-types": "^15.7.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"react-devtools-core": {
|
"react-devtools-core": {
|
||||||
"version": "4.22.1",
|
"version": "4.22.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-4.22.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-4.22.1.tgz",
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
"@mui/material": "^5.2.7",
|
"@mui/material": "^5.2.7",
|
||||||
"@mui/system": "^5.2.6",
|
"@mui/system": "^5.2.6",
|
||||||
"material-ui-image": "^3.3.2",
|
"material-ui-image": "^3.3.2",
|
||||||
|
"react-countdown": "^2.3.2",
|
||||||
"react-markdown": "^7.1.2",
|
"react-markdown": "^7.1.2",
|
||||||
"react-native": "^0.66.4",
|
"react-native": "^0.66.4",
|
||||||
"react-native-svg": "^12.1.1",
|
"react-native-svg": "^12.1.1",
|
||||||
|
@ -13,15 +13,13 @@ export default class Chat extends Component {
|
|||||||
state = {
|
state = {
|
||||||
messages: [],
|
messages: [],
|
||||||
value:'',
|
value:'',
|
||||||
orderId: 2,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
client = new W3CWebSocket('ws://' + window.location.host + '/ws/chat/' + this.props.data.orderId + '/');
|
client = new W3CWebSocket('ws://' + window.location.host + '/ws/chat/' + this.props.orderId + '/');
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.client.onopen = () => {
|
this.client.onopen = () => {
|
||||||
console.log('WebSocket Client Connected')
|
console.log('WebSocket Client Connected')
|
||||||
console.log(this.props.data)
|
|
||||||
}
|
}
|
||||||
this.client.onmessage = (message) => {
|
this.client.onmessage = (message) => {
|
||||||
const dataFromServer = JSON.parse(message.data);
|
const dataFromServer = JSON.parse(message.data);
|
||||||
@ -43,11 +41,19 @@ export default class Chat extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidUpdate() {
|
||||||
|
this.scrollToBottom();
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollToBottom = () => {
|
||||||
|
this.messagesEnd.scrollIntoView({ behavior: "smooth" });
|
||||||
|
}
|
||||||
|
|
||||||
onButtonClicked = (e) => {
|
onButtonClicked = (e) => {
|
||||||
this.client.send(JSON.stringify({
|
this.client.send(JSON.stringify({
|
||||||
type: "message",
|
type: "message",
|
||||||
message: this.state.value,
|
message: this.state.value,
|
||||||
nick: this.props.data.urNick,
|
nick: this.props.urNick,
|
||||||
}));
|
}));
|
||||||
this.state.value = ''
|
this.state.value = ''
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@ -60,7 +66,7 @@ export default class Chat extends Component {
|
|||||||
{this.state.messages.map(message => <>
|
{this.state.messages.map(message => <>
|
||||||
<Card elevation={5} align="left" >
|
<Card elevation={5} align="left" >
|
||||||
{/* If message sender is not our nick, gray color, if it is our nick, green color */}
|
{/* If message sender is not our nick, gray color, if it is our nick, green color */}
|
||||||
{message.userNick == this.props.data.urNick ?
|
{message.userNick == this.props.urNick ?
|
||||||
<CardHeader
|
<CardHeader
|
||||||
avatar={
|
avatar={
|
||||||
<Avatar
|
<Avatar
|
||||||
@ -86,6 +92,7 @@ export default class Chat extends Component {
|
|||||||
/>}
|
/>}
|
||||||
</Card>
|
</Card>
|
||||||
</>)}
|
</>)}
|
||||||
|
<div style={{ float:"left", clear: "both" }} ref={(el) => { this.messagesEnd = el; }}></div>
|
||||||
</Paper>
|
</Paper>
|
||||||
<form noValidate onSubmit={this.onButtonClicked}>
|
<form noValidate onSubmit={this.onButtonClicked}>
|
||||||
<Grid containter alignItems="stretch" style={{ display: "flex" }}>
|
<Grid containter alignItems="stretch" style={{ display: "flex" }}>
|
||||||
|
@ -1,44 +1,8 @@
|
|||||||
import React, { Component } from "react";
|
import React, { Component } from "react";
|
||||||
import { Alert, Paper, CircularProgress, Button , Grid, Typography, List, ListItem, ListItemText, ListItemAvatar, Avatar, Divider, Box, LinearProgress} from "@mui/material"
|
import { Alert, Paper, CircularProgress, Button , Grid, Typography, List, ListItem, ListItemText, ListItemAvatar, Avatar, Divider, Box, LinearProgress} from "@mui/material"
|
||||||
|
import Countdown, { zeroPad, calcTimeDelta } from 'react-countdown';
|
||||||
import TradeBox from "./TradeBox";
|
import TradeBox from "./TradeBox";
|
||||||
|
|
||||||
function msToTime(duration) {
|
|
||||||
var seconds = Math.floor((duration / 1000) % 60),
|
|
||||||
minutes = Math.floor((duration / (1000 * 60)) % 60),
|
|
||||||
hours = Math.floor((duration / (1000 * 60 * 60)) % 24);
|
|
||||||
|
|
||||||
minutes = (minutes < 10) ? "0" + minutes : minutes;
|
|
||||||
seconds = (seconds < 10) ? "0" + seconds : seconds;
|
|
||||||
|
|
||||||
return hours + "h " + minutes + "m " + seconds + "s";
|
|
||||||
}
|
|
||||||
|
|
||||||
// TO DO fix Progress bar to go from 100 to 0, from total_expiration time, showing time_left
|
|
||||||
function LinearDeterminate() {
|
|
||||||
const [progress, setProgress] = React.useState(0);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
const timer = setInterval(() => {
|
|
||||||
setProgress((oldProgress) => {
|
|
||||||
if (oldProgress === 0) {
|
|
||||||
return 100;
|
|
||||||
}
|
|
||||||
const diff = 1;
|
|
||||||
return Math.max(oldProgress - diff, 0);
|
|
||||||
});
|
|
||||||
}, 500);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
clearInterval(timer);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box sx={{ width: '100%' }}>
|
|
||||||
<LinearProgress variant="determinate" value={progress} />
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCookie(name) {
|
function getCookie(name) {
|
||||||
let cookieValue = null;
|
let cookieValue = null;
|
||||||
@ -67,8 +31,9 @@ export default class OrderPage extends Component {
|
|||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
isExplicit: false,
|
isExplicit: false,
|
||||||
delay: 2000, // Refresh every 2 seconds by default
|
delay: 60000, // Refresh every 60 seconds by default
|
||||||
currencies_dict: {"1":"USD"}
|
currencies_dict: {"1":"USD"},
|
||||||
|
total_secs_expiry: 300,
|
||||||
};
|
};
|
||||||
this.orderId = this.props.match.params.orderId;
|
this.orderId = this.props.match.params.orderId;
|
||||||
this.getCurrencyDict();
|
this.getCurrencyDict();
|
||||||
@ -136,6 +101,50 @@ export default class OrderPage extends Component {
|
|||||||
window.history.back();
|
window.history.back();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Countdown Renderer callback with condition
|
||||||
|
countdownRenderer = ({ total, hours, minutes, seconds, completed }) => {
|
||||||
|
if (completed) {
|
||||||
|
// Render a completed state
|
||||||
|
this.getOrderDetails();
|
||||||
|
} else {
|
||||||
|
var col = 'black'
|
||||||
|
var fraction_left = (total/1000) / this.state.total_secs_expiry
|
||||||
|
console.log(fraction_left)
|
||||||
|
// Make orange at -25% of time left
|
||||||
|
if (fraction_left < 0.25){col = 'orange'}
|
||||||
|
// Make red at 10% of time left
|
||||||
|
if (fraction_left < 0.1){col = 'red'}
|
||||||
|
// Render a countdown
|
||||||
|
return (
|
||||||
|
fraction_left < 0.25 ? <b><span style={{color:col}}>{hours}h {zeroPad(minutes)}m {zeroPad(seconds)}s </span></b>
|
||||||
|
:<span style={{color:col}}>{hours}h {zeroPad(minutes)}m {zeroPad(seconds)}s </span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
LinearDeterminate =()=> {
|
||||||
|
const [progress, setProgress] = React.useState(0);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const timer = setInterval(() => {
|
||||||
|
setProgress((oldProgress) => {
|
||||||
|
var left = calcTimeDelta( new Date(this.state.expiresAt)).total /1000;
|
||||||
|
return (left / this.state.total_secs_expiry) * 100;
|
||||||
|
});
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearInterval(timer);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ width: '100%' }}>
|
||||||
|
<LinearProgress variant="determinate" value={progress} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
handleClickTakeOrderButton=()=>{
|
handleClickTakeOrderButton=()=>{
|
||||||
console.log(this.state)
|
console.log(this.state)
|
||||||
const requestOptions = {
|
const requestOptions = {
|
||||||
@ -246,9 +255,11 @@ export default class OrderPage extends Component {
|
|||||||
</ListItem>
|
</ListItem>
|
||||||
<Divider />
|
<Divider />
|
||||||
<ListItem>
|
<ListItem>
|
||||||
<ListItemText primary={msToTime( new Date(this.state.expiresAt) - Date.now())} secondary="Expires"/>
|
<ListItemText secondary="Expires">
|
||||||
|
<Countdown onTick={console.log(this.seconds)} date={new Date(this.state.expiresAt)} renderer={this.countdownRenderer} />
|
||||||
|
</ListItemText>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<LinearDeterminate />
|
<this.LinearDeterminate />
|
||||||
</List>
|
</List>
|
||||||
|
|
||||||
{/* If the user has a penalty/limit */}
|
{/* If the user has a penalty/limit */}
|
||||||
|
@ -377,7 +377,7 @@ handleRatingChange=(e)=>{
|
|||||||
<Divider/>
|
<Divider/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Chat data={this.props.data}/>
|
<Chat orderId={this.props.data.id} urNick={this.props.data.urNick}/>
|
||||||
|
|
||||||
<Grid item xs={12} align="center">
|
<Grid item xs={12} align="center">
|
||||||
{openDisputeButton ? this.showOpenDisputeButton() : ""}
|
{openDisputeButton ? this.showOpenDisputeButton() : ""}
|
||||||
|
@ -19,6 +19,6 @@ from django.urls import path, include
|
|||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
path('api/', include('api.urls')),
|
path('api/', include('api.urls')),
|
||||||
path('chat/', include('chat.urls')),
|
# path('chat/', include('chat.urls')),
|
||||||
path('', include('frontend.urls')),
|
path('', include('frontend.urls')),
|
||||||
]
|
]
|
||||||
|
1
setup.md
1
setup.md
@ -114,6 +114,7 @@ npm install react-qr-code
|
|||||||
npm install @mui/material
|
npm install @mui/material
|
||||||
npm install react-markdown
|
npm install react-markdown
|
||||||
npm install websocket
|
npm install websocket
|
||||||
|
npm install react-countdown
|
||||||
```
|
```
|
||||||
Note we are using mostly MaterialUI V5 (@mui/material) but Image loading from V4 (@material-ui/core) extentions (so both V4 and V5 are needed)
|
Note we are using mostly MaterialUI V5 (@mui/material) but Image loading from V4 (@material-ui/core) extentions (so both V4 and V5 are needed)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user