parent
607d02f340
commit
5915c72411
47 changed files with 3180 additions and 35 deletions
@ -1,13 +1,13 @@ |
||||
// import proxy from '../proxy';
|
||||
// export const list =
|
||||
// (data = {}) =>
|
||||
// async (dispatch) =>
|
||||
// (await proxy.get('message/list', data)).dispatch(dispatch);
|
||||
// export const send =
|
||||
// (data = {}) =>
|
||||
// async (dispatch) =>
|
||||
// (await proxy.post('message/send', data)).dispatch(dispatch);
|
||||
// export const sendKeys =
|
||||
// (data = {}) =>
|
||||
// async (dispatch) =>
|
||||
// (await proxy.post('message/sendKeys', data)).dispatch(dispatch);
|
||||
import proxy from '../proxy'; |
||||
export const list = |
||||
(data = {}) => |
||||
async (dispatch) => |
||||
(await proxy.get('message/list', data)).dispatch(dispatch); |
||||
export const send = |
||||
(data = {}) => |
||||
async (dispatch) => |
||||
(await proxy.post('message/send', data)).dispatch(dispatch); |
||||
export const sendKeys = |
||||
(data = {}) => |
||||
async (dispatch) => |
||||
(await proxy.post('message/sendKeys', data)).dispatch(dispatch); |
||||
|
@ -1,16 +1,16 @@ |
||||
// const initialState = {
|
||||
// list: [],
|
||||
// };
|
||||
// export default function chat(state = initialState, action) {
|
||||
// let { type, data } = action;
|
||||
// switch (type) {
|
||||
// case 'message/list':
|
||||
// return { ...state, list: data };
|
||||
// case 'message/send':
|
||||
// return { ...state, list: [...list, ...data] };
|
||||
// case 'message/sendKeys':
|
||||
// return { ...state, list: [...list, ...data] };
|
||||
// default:
|
||||
// return state;
|
||||
// }
|
||||
// }
|
||||
const initialState = { |
||||
list: [], |
||||
}; |
||||
export default function chat(state = initialState, action) { |
||||
let { type, data } = action; |
||||
switch (type) { |
||||
case 'message/list': |
||||
return { ...state, list: data }; |
||||
case 'message/send': |
||||
return { ...state, list: [...state.list, ...data] }; |
||||
case 'message/sendKeys': |
||||
return { ...state, list: [...state.list, ...data] }; |
||||
default: |
||||
return state; |
||||
} |
||||
} |
||||
|
After Width: | Height: | Size: 682 KiB |
@ -0,0 +1,22 @@ |
||||
import React, { Component } from 'react'; |
||||
import { Link } from 'react-router-dom'; |
||||
import moment from 'jalali-moment'; |
||||
import Avatar from '@material-ui/core/Avatar'; |
||||
|
||||
import './index.scss'; |
||||
export default class Chat extends Component { |
||||
render() { |
||||
const { data } = this.props; |
||||
return ( |
||||
<Link to={`/chatroom/${data.id}`} className="chat d-flex"> |
||||
<Avatar src={data.imageUrl} alt={data.name} className={'avatar fontLight'} /> |
||||
<div className="chat__data d-flex flex-column"> |
||||
<h1 className="chat__data--name">{data.name}</h1> |
||||
<p className="chat__data--lastMessage">{data.lastMessage.slice(0, 40) + '...'}</p> |
||||
<span>{data.badge}</span> |
||||
<div>{moment(data.date || new Date()).format('jD/jM hh:mm')}</div> |
||||
</div> |
||||
</Link> |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,72 @@ |
||||
@media screen and (max-width: 4500px) { |
||||
.chat { |
||||
width: 100%; |
||||
direction: rtl; |
||||
align-items: center; |
||||
margin-bottom: 10px; |
||||
position: relative; |
||||
padding: 0px 0px 0px; |
||||
text-decoration: none; |
||||
.avatar { |
||||
background-color: #b4b4b4; |
||||
width: 55px; |
||||
height: 55px; |
||||
// padding: 5px; |
||||
border-radius: 35px; |
||||
object-fit: cover; |
||||
} |
||||
&__data { |
||||
text-align: right; |
||||
flex: 1; |
||||
justify-content: space-around; |
||||
height: 100%; |
||||
padding: 10px 10px 20px; |
||||
border-bottom: rgba(209, 208, 208, 0.7) solid 1px; |
||||
|
||||
&--name { |
||||
font-size: 18px; |
||||
} |
||||
h1 { |
||||
font-family: numeralMedium; |
||||
font-size: 15px; |
||||
margin: 0; |
||||
font-weight: 600; |
||||
color: #6f7074; |
||||
} |
||||
p { |
||||
font-size: 12px; |
||||
margin: 0; |
||||
max-width: calc(100vw - 100px); |
||||
font-family: numeralLight; |
||||
color: #6f7074; |
||||
} |
||||
span { |
||||
background-color: #25d366; |
||||
min-width: 20px; |
||||
font-size: 11px; |
||||
height: 20px; |
||||
display: flex; |
||||
justify-content: center; |
||||
align-items: center; |
||||
border-radius: 13px; |
||||
position: absolute; |
||||
padding-top: 1px; |
||||
padding: 0 5px; |
||||
left: 20px; |
||||
bottom: 10px; |
||||
color: white; |
||||
} |
||||
div { |
||||
color: rgb(99, 99, 99); |
||||
position: absolute; |
||||
left: 20px; |
||||
font-size: 12px; |
||||
top: 5px; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
a{ |
||||
text-decoration: none; |
||||
} |
@ -0,0 +1,37 @@ |
||||
import React, { Component } from 'react'; |
||||
import {Link} from 'react-router-dom'; |
||||
import { list } from '../../Redux/actions/chat'; |
||||
import { connect } from 'react-redux'; |
||||
import ChatIcon from '@material-ui/icons/Chat'; |
||||
import Chat from './Chat/index'; |
||||
import Navbar from '../Home/Navbar/index'; |
||||
|
||||
|
||||
import './index.scss'; |
||||
|
||||
class ChatList extends Component { |
||||
componentDidMount() { |
||||
this.props.list(); |
||||
} |
||||
render() { |
||||
return ( |
||||
<div className="chat-list d-flex flex-column"> |
||||
<Navbar /> |
||||
{this.props.chat_list.map((item, i) => ( |
||||
<Chat key={i} data={item} /> |
||||
))} |
||||
<Link to="/characters" className="chat-list__float d-flex"> |
||||
<ChatIcon fontSize="small" /> |
||||
</Link> |
||||
</div> |
||||
); |
||||
} |
||||
} |
||||
|
||||
export default connect( |
||||
(state) => ({ |
||||
chat_list: state.chat.list || [], |
||||
}), |
||||
{ list } |
||||
)(ChatList); |
||||
|
@ -0,0 +1,26 @@ |
||||
@media screen and (max-width: 4500px) { |
||||
.chat-list { |
||||
flex: 1; |
||||
padding: 70px 14px 10px; |
||||
position: relative; |
||||
background: white; |
||||
&__float { |
||||
position: fixed; |
||||
height: 50px; |
||||
width: 50px; |
||||
bottom: 20px; |
||||
left: 20px; |
||||
justify-content: center; |
||||
align-items: center; |
||||
background-color: #01db38; |
||||
border-radius: 100px; |
||||
|
||||
box-shadow: 0px 1px 10px 0px rgba(0, 0, 0, 0.4); |
||||
& svg { |
||||
color: white; |
||||
width: 25px; |
||||
height: 25px; |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,171 @@ |
||||
import React, { Component } from 'react'; |
||||
import AudioReactRecorder from 'audio-react-recorder'; |
||||
import MicIcon from '@material-ui/icons/Mic'; |
||||
import CheckIcon from '@material-ui/icons/Check'; |
||||
import CloseIcon from '@material-ui/icons/Close'; |
||||
import AttachmentIcon from '@material-ui/icons/Attachment'; |
||||
import TextField from '@material-ui/core/TextField'; |
||||
import SendIcon from '@material-ui/icons/Send'; |
||||
import EmojiEmotions from '@material-ui/icons/EmojiEmotions'; |
||||
import KeyboardIcon from '@material-ui/icons/Keyboard'; |
||||
import PhotoCamera from '@material-ui/icons/PhotoCamera'; |
||||
|
||||
import './index.scss'; |
||||
export default class ChatInputs extends Component { |
||||
state = { |
||||
emoji: false, |
||||
}; |
||||
render() { |
||||
const { |
||||
onStop, |
||||
stop, |
||||
start, |
||||
recordState, |
||||
cancel, |
||||
typeText, |
||||
message, |
||||
sendMessage, |
||||
addFile, |
||||
file, |
||||
addcounter, |
||||
} = this.props; |
||||
return ( |
||||
<div className="chat-inputs d-flex flex-row-reverse"> |
||||
<AudioReactRecorder state={recordState} onStop={onStop} /> |
||||
{/* <button onClick={start}>Start</button> |
||||
<button onClick={stop}>Stop</button> |
||||
{ |
||||
recordState ?
|
||||
<ReactAudioPlayer |
||||
src={recordState.url} |
||||
autoPlay |
||||
controls |
||||
/> : null |
||||
} */} |
||||
|
||||
<div className="chat-inputs__voice d-flex flex-row-reverse"> |
||||
<div className="chat-inputs__voice d-flex"> |
||||
{(recordState === null && message === '' && file === null) || |
||||
(recordState && recordState.url && message === '' && file === null) ? ( |
||||
<div className="chat-inputs__voice--icon d-flex" onClick={start}> |
||||
<MicIcon |
||||
style={{ |
||||
color: 'white', |
||||
backgroundColor: '#128c7e', |
||||
width: 50, |
||||
height: 50, |
||||
borderRadius: 30, |
||||
padding: 10, |
||||
}} |
||||
/> |
||||
</div> |
||||
) : null} |
||||
{message !== '' || file !== null ? ( |
||||
<div className="chat-inputs__voice--icon d-flex" onClick={() => addcounter()}> |
||||
<SendIcon |
||||
style={{ |
||||
padding: '12px 10px 12px 13px', |
||||
color: 'white', |
||||
backgroundColor: '#128c7e', |
||||
width: 50, |
||||
height: 50, |
||||
borderRadius: 30, |
||||
transform: 'rotate(180deg)', |
||||
}} |
||||
/> |
||||
</div> |
||||
) : null} |
||||
{recordState === 'start' ? ( |
||||
<> |
||||
<div className="chat-inputs__voice--icon d-flex mr-2" onClick={cancel}> |
||||
<CloseIcon |
||||
style={{ color: 'red', backgroundColor: 'white', width: 30, height: 30 }} |
||||
/> |
||||
</div> |
||||
<div className="chat-inputs__voice--icon d-flex" onClick={stop}> |
||||
<CheckIcon |
||||
style={{ color: 'green', backgroundColor: 'white', width: 30, height: 30 }} |
||||
/> |
||||
</div> |
||||
</> |
||||
) : null} |
||||
</div> |
||||
</div> |
||||
{recordState !== 'start' ? ( |
||||
<div className="chat-inputs__box d-flex flex-row-reverse"> |
||||
{message === '' ? ( |
||||
<PhotoCamera |
||||
style={{ |
||||
color: '#aaa', |
||||
marginRight: 0, |
||||
width: 25, |
||||
height: 25, |
||||
margin: '0 3px', |
||||
}} |
||||
/> |
||||
) : null} |
||||
<div className="chat-inputs__box--file"> |
||||
<input type="file" onChange={(e) => addFile(e)} /> |
||||
|
||||
<AttachmentIcon |
||||
style={{ |
||||
color: '#aaa', |
||||
marginRight: 10, |
||||
width: 25, |
||||
height: 25, |
||||
zIndex: 10, |
||||
position: 'absolute', |
||||
top: 0, |
||||
left: 0, |
||||
transform: 'rotate(45deg)', |
||||
}} |
||||
/> |
||||
</div> |
||||
|
||||
<TextField |
||||
id="standard-textarea" |
||||
label="" |
||||
value={message} |
||||
placeholder="یک پیام بنویسید..." |
||||
rowsMax={4} |
||||
onChange={(e) => typeText('text',e.target.value)} |
||||
multiline |
||||
style={{ |
||||
marginRight: 5, |
||||
}} |
||||
/> |
||||
{this.props.parent.state.emoji ? ( |
||||
<KeyboardIcon |
||||
style={{ |
||||
color: '#aaa', |
||||
marginRight: -5, |
||||
width: 25, |
||||
height: 25, |
||||
zIndex: 10, |
||||
position: 'relative', |
||||
bottom: 5, |
||||
alignSelf: 'flex-end', |
||||
}} |
||||
onClick={() => this.props.parent.setState({ emoji: false })} |
||||
/> |
||||
) : ( |
||||
<EmojiEmotions |
||||
style={{ |
||||
color: '#aaa', |
||||
marginRight: -5, |
||||
width: 25, |
||||
height: 25, |
||||
zIndex: 10, |
||||
position: 'relative', |
||||
bottom: 5, |
||||
alignSelf: 'flex-end', |
||||
}} |
||||
onClick={() => this.props.parent.setState({ emoji: true })} |
||||
/> |
||||
)} |
||||
</div> |
||||
) : null} |
||||
</div> |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,97 @@ |
||||
@media screen and (max-width: 4500px) { |
||||
.chat-inputs { |
||||
border-bottom-right-radius: 40px; |
||||
border-bottom-left-radius: 40px; |
||||
width: 100%; |
||||
padding: 2px 5px 0px; |
||||
align-items: flex-end; |
||||
justify-content: flex-end; |
||||
&__box { |
||||
width: 100%; |
||||
border-radius: 30px; |
||||
background-color: #fff; |
||||
box-shadow: 0 0.4px 1px 0px #aaa; |
||||
// background: rgb(18, 15, 153); |
||||
// background: linear-gradient(90deg, rgba(18, 15, 153, 1) 0%, rgba(105, 9, 121, 1) 60%); |
||||
min-height: 45px; |
||||
margin-left: 10px; |
||||
align-items: center; |
||||
padding: 8px 15px; |
||||
&--file { |
||||
width: 30px; |
||||
height: 30px; |
||||
position: relative; |
||||
align-self: flex-end; |
||||
input[type='file'] { |
||||
opacity: 0; |
||||
width: 100%; |
||||
height: 100%; |
||||
z-index: 100; |
||||
position: relative; |
||||
} |
||||
} |
||||
// input[type=text]{ |
||||
// flex : 1; |
||||
// background-color: inherit; |
||||
// border : 0px; |
||||
// text-align: right; |
||||
// direction: rtl; |
||||
// color : white; |
||||
// font-size: 14px; |
||||
// &:focus{ |
||||
// outline:none; |
||||
// } |
||||
// &::placeholder{ |
||||
// color : white; |
||||
// } |
||||
// } |
||||
.MuiTextField-root { |
||||
flex: 1; |
||||
border: 0px !important; |
||||
outline: none !important; |
||||
text-align: right; |
||||
textarea { |
||||
text-align: right; |
||||
direction: rtl; |
||||
color: black; |
||||
font-size: 14px; |
||||
font-family: IRANSansNumeralLight; |
||||
} |
||||
} |
||||
} |
||||
&__voice { |
||||
&--icon { |
||||
width: 50px; |
||||
height: 50px; |
||||
border-radius: 30px; |
||||
justify-content: center; |
||||
align-items: center; |
||||
background-color: white; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
.audio-react-recorder { |
||||
display: none; |
||||
} |
||||
|
||||
.MuiInput-underline:hover:not(.Mui-disabled):before { |
||||
border-bottom: 0px !important; |
||||
} |
||||
|
||||
.MuiInput-underline:hover:not(.Mui-disabled):before { |
||||
border-bottom: 0px !important; |
||||
} |
||||
|
||||
.MuiInput-underline:hover:not(.Mui-disabled):before { |
||||
border-bottom: 0px !important; |
||||
} |
||||
|
||||
.MuiInput-underline:after { |
||||
border-bottom: 0px !important; |
||||
} |
||||
|
||||
.MuiInput-underline:before { |
||||
border-bottom: 0px !important; |
||||
} |
@ -0,0 +1,32 @@ |
||||
import React, { Component } from 'react'; |
||||
import Carousel from 'react-elastic-carousel'; |
||||
import './index.scss'; |
||||
export default function Keyboard({ options, action }) { |
||||
return ( |
||||
<div className="chat-suggest portrait-slider d-flex flex-column"> |
||||
<Carousel itemsToShow={1} isRTL={true} enableTilt={true} showArrows={false}> |
||||
{options.map((row, i) => ( |
||||
<Row row={row} key={i} action={action} /> |
||||
))} |
||||
</Carousel> |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
const Row = ({ row, action }) => { |
||||
return ( |
||||
<div className="chat-suggest__row d-flex"> |
||||
{row.map((item, i) => ( |
||||
<KeyboardItem item={item} key={i} action={action} /> |
||||
))} |
||||
</div> |
||||
); |
||||
}; |
||||
|
||||
const KeyboardItem = ({ item, action }) => { |
||||
return ( |
||||
<div className="chat-suggest__row--item d-flex" onclick={() => action(item)}> |
||||
{item.text} |
||||
</div> |
||||
); |
||||
}; |
@ -0,0 +1,74 @@ |
||||
@media screen and (max-width: 4500px) { |
||||
.chat-suggest { |
||||
& .rec-slider-container { |
||||
margin: 0 !important; |
||||
} |
||||
// border-bottom-right-radius: 40px; |
||||
// border-bottom-left-radius: 40px; |
||||
width: 100%; |
||||
// height : 40px; |
||||
padding: 2px 0px 0px; |
||||
align-items: flex-start; |
||||
margin-top: 5px; |
||||
&__row { |
||||
width: 100%; |
||||
&--item { |
||||
padding: 8px 6px; |
||||
background-color: white; |
||||
border-radius: 5px; |
||||
margin: 0 2px; |
||||
width: 100% !important; |
||||
justify-content: right; |
||||
color: black; |
||||
height: 90px; |
||||
text-align: right; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
.portrait-slider { |
||||
width: 100% !important; |
||||
height: 100% !important; |
||||
position: relative; |
||||
padding: 0px 6px 0px; |
||||
margin-bottom: 2px; |
||||
justify-content: center !important; |
||||
} |
||||
|
||||
.rec-dot_active { |
||||
background-color: white !important; |
||||
color: white !important; |
||||
// box-shadow: 0 0 1px rgba(255, 255, 255, 1) !important; |
||||
} |
||||
|
||||
.Carousel-button-10 { |
||||
background-color: white !important; |
||||
} |
||||
.rec-pagination { |
||||
z-index: 600; |
||||
margin-top: 2px !important; |
||||
// position: absolute; |
||||
// top: -12px; |
||||
// left: calc(50% - 20px) !important; |
||||
& button { |
||||
background-color: grey; |
||||
margin: 2px !important; |
||||
box-shadow: 0px 0px 0px rgba(255, 255, 255, 1); |
||||
} |
||||
} |
||||
|
||||
// .guuruq{ |
||||
// box-shadow: 0 1px 2px rgba(0, 0, 0, 0%); |
||||
// } |
||||
|
||||
// .cJeRRR{ |
||||
// background-color: white !important; |
||||
|
||||
// } |
||||
|
||||
// .cJeRRR:hover, .cJeRRR:focus{ |
||||
// cursor: pointer; |
||||
// box-shadow: 0px 0px 0px rgba(255,255,255,1) !important; |
||||
// background-color: white !important; |
||||
// } |
@ -0,0 +1,83 @@ |
||||
import React, { Component } from 'react'; |
||||
import DoneIcon from '@material-ui/icons/Done'; |
||||
import DoneAllIcon from '@material-ui/icons/DoneAll'; |
||||
import EditIcon from '@material-ui/icons/Edit'; |
||||
import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; |
||||
import './index.scss'; |
||||
import Menu from '@material-ui/core/Menu'; |
||||
import MenuItem from '@material-ui/core/MenuItem'; |
||||
import ReplyIcon from '@material-ui/icons/Reply'; |
||||
import ForwardIcon from '@material-ui/icons/Forward'; |
||||
import DeleteIcon from '@material-ui/icons/Delete'; |
||||
import ScheduleIcon from '@material-ui/icons/Schedule'; |
||||
const menu = [ |
||||
{ text: 'پسند', icon: FavoriteIcon, onClick: () => {} }, |
||||
{ text: 'تبادل نظر', icon: TextsmsIcon, onClick: () => {} }, |
||||
{ text: 'پاسخ', icon: ReplyIcon, onClick: () => {} }, |
||||
{ text: 'باز ارسال', icon: ForwardIcon, onClick: () => {} }, |
||||
]; |
||||
const selfMenu = [ |
||||
{ text: 'باز ارسال', icon: ForwardIcon, onClick: () => {} }, |
||||
{ text: 'ویرایش', icon: EditIcon, onClick: () => {} }, |
||||
{ text: 'حذف', icon: DeleteIcon, onClick: () => {} }, |
||||
]; |
||||
export default class Comment extends Component { |
||||
state = { messageMenu: null }; |
||||
handleClose = () => { |
||||
this.setState({ |
||||
messageMenu: null, |
||||
}); |
||||
}; |
||||
handleClick = (event) => { |
||||
this.setState({ |
||||
messageMenu: event.currentTarget, |
||||
}); |
||||
}; |
||||
render() { |
||||
const { message, user, document, children, addcounter } = this.props; |
||||
const { messageMenu } = this.state; |
||||
const hasavatar = prevUserId != message.userId; |
||||
|
||||
return ( |
||||
<div |
||||
className="comment-message d-flex flex-column d-flex" |
||||
style={{ |
||||
filter: document !== null ? 'blur(16px)' : 'blur(0px)', |
||||
}} |
||||
> |
||||
<div className={`comment-message__box`}> |
||||
<div |
||||
className="comment-message__box--detail d-flex" |
||||
> |
||||
<div onClick={() => addcounter('nothing')}> |
||||
<ExpandMoreIcon fontSize="small" onClick={this.handleClick} /> |
||||
</div> |
||||
<Menu |
||||
anchorEl={messageMenu} |
||||
keepMounted |
||||
open={Boolean(messageMenu)} |
||||
onClose={() => this.handleClose()} |
||||
className="rtl fontLight" |
||||
> |
||||
{(isSelf ? selfMenu : menu).map((e) => ( |
||||
<MenuItem |
||||
className="fontLight" |
||||
onClick={() => { |
||||
this.handleClose(); |
||||
e.onClick(); |
||||
addcounter('nothing'); |
||||
}} |
||||
> |
||||
<e.icon fontSize="small" /> |
||||
{e.text} |
||||
</MenuItem> |
||||
))} |
||||
</Menu> |
||||
</div> |
||||
|
||||
<p>{message.text}</p> |
||||
</div> |
||||
</div> |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,46 @@ |
||||
import React, { Component } from 'react'; |
||||
import Avatar from '@material-ui/core/Avatar'; |
||||
|
||||
import './index.scss'; |
||||
import { toast } from 'react-toastify'; |
||||
|
||||
export default class Message2D extends Component { |
||||
state = { value: this.props.message.value }; |
||||
click(id) { |
||||
if (this.props.isActive) { |
||||
this.setState({ value: id }); |
||||
toast.success('ثبت شد'); |
||||
this.props.action(); |
||||
} |
||||
} |
||||
render() { |
||||
const { message, user, isSelf, isActive, action } = this.props; |
||||
return ( |
||||
<div className="message-2d d-flex flex-column d-flex"> |
||||
<div className="message-2d__options d-flex flex-column"> |
||||
{message.options.map((option, i) => ( |
||||
<div |
||||
key={message.id + '_' + option.id + '_' + i} |
||||
className="message-2d__options--row d-flex" |
||||
> |
||||
{option.map((item, i) => ( |
||||
<div |
||||
key={message.id + '_' + item.id + '_' + i} |
||||
className="d-flex" |
||||
style={{ |
||||
width: `calc(100% / ${option.length} )`, |
||||
|
||||
backgroundColor: item.id == this.state.value ? '#64cd82' : '', |
||||
}} |
||||
onClick={() => this.click(item.id)} |
||||
> |
||||
<span>{item.name}</span> |
||||
</div> |
||||
))} |
||||
</div> |
||||
))} |
||||
</div> |
||||
</div> |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,101 @@ |
||||
@media screen and (max-width: 4500px) { |
||||
.message-2d { |
||||
display: flex; |
||||
justify-content: flex-end; |
||||
// width: 100%; |
||||
position: relative; |
||||
img { |
||||
width: 65px; |
||||
height: 65px; |
||||
object-fit: cover; |
||||
border-radius: 30px; |
||||
border: 3px solid rgba(201, 201, 201, 0.857); |
||||
position: relative; |
||||
top: 20px; |
||||
z-index: 50; |
||||
} |
||||
&__text { |
||||
justify-content: flex-end; |
||||
width: 100%; |
||||
padding: 15px 15px 5px; |
||||
background-color: white; |
||||
border-radius: 5px; |
||||
position: relative; |
||||
margin-bottom: 5px; |
||||
text-align: right; |
||||
direction: rtl; |
||||
p { |
||||
width: 100%; |
||||
text-align: right; |
||||
font-size: 16px; |
||||
// font-family: IRANSansNumeralBold; |
||||
} |
||||
} |
||||
&__options { |
||||
width: 100%; |
||||
z-index: 200; |
||||
&--row { |
||||
width: 100%; |
||||
justify-content: space-around; |
||||
div { |
||||
background-color: rgb(255 255 255 / 40%); |
||||
border-radius: 10px; |
||||
justify-content: center; |
||||
align-items: center; |
||||
margin: 2px 3px; |
||||
padding: 6px; |
||||
flex: 1; |
||||
text-align: center; |
||||
span { |
||||
font-size: 16px; |
||||
color: black; |
||||
font-weight: 400; |
||||
} |
||||
} |
||||
} |
||||
&--radio { |
||||
width: calc(50% - 6px); |
||||
margin: 3px; |
||||
padding: 10px; |
||||
text-align: center; |
||||
font-size: 16px; |
||||
color: black; |
||||
font-weight: 600; |
||||
border-radius: 5px; |
||||
} |
||||
&--draggable { |
||||
background-color: rgb(255 255 255 / 40%); |
||||
border-radius: 10px; |
||||
justify-content: center; |
||||
align-items: center; |
||||
margin: 2px 3px; |
||||
padding: 6px; |
||||
font-size: 16px; |
||||
color: black; |
||||
font-weight: 600; |
||||
position: relative; |
||||
} |
||||
} |
||||
& button { |
||||
background: #64cd82; |
||||
// background: linear-gradient(180deg, rgb(163, 73, 186) 0%, rgb(108, 72, 191) 52%); |
||||
width: 100%; |
||||
margin: 3px auto 0px; |
||||
border: none; |
||||
padding: 5px 0px; |
||||
font-size: 18px; |
||||
border-radius: 10px; |
||||
color: white; |
||||
} |
||||
} |
||||
} |
||||
|
||||
.MuiGrid-container { |
||||
display: flex !important; |
||||
width: 100% !important; |
||||
margin: 0px !important; |
||||
background-color: rgb(255 255 255 / 40%); |
||||
justify-content: center !important; |
||||
align-items: center !important; |
||||
border-radius: 10px; |
||||
} |
@ -0,0 +1,63 @@ |
||||
import React, { Component } from 'react'; |
||||
import ArrowRightAltIcon from '@material-ui/icons/ArrowRightAlt'; |
||||
import Avatar from '@material-ui/core/Avatar'; |
||||
import { toast } from 'react-toastify'; |
||||
|
||||
export default class RadioMessage extends Component { |
||||
constructor(props) { |
||||
super(props); |
||||
this.state = { |
||||
value: this.props.message.value, |
||||
}; |
||||
} |
||||
|
||||
handleRadioChange = (newValue) => { |
||||
if (this.props.isActive) |
||||
this.setState({ |
||||
value: newValue, |
||||
}); |
||||
}; |
||||
|
||||
render() { |
||||
const { message, user, isSelf, isActive } = this.props; |
||||
return ( |
||||
<div className="message-2d d-flex flex-column d-flex"> |
||||
<div className="message-2d__options d-flex flex-wrap justify-content-center"> |
||||
{message.options.map((row) => ( |
||||
<Row list={row} handleRadioChange={this.handleRadioChange} value={this.state.value} /> |
||||
))} |
||||
</div> |
||||
{isActive && ( |
||||
<button className="message-2d__submit" onClick={() => toast.success('ثبت شد')}> |
||||
ثبت |
||||
<ArrowRightAltIcon style={{ color: 'white', width: 32, height: 32, marginLeft: 10 }} /> |
||||
</button> |
||||
)} |
||||
</div> |
||||
); |
||||
} |
||||
} |
||||
|
||||
const Row = (props) => { |
||||
return ( |
||||
<div className="message-2d__options--row d-flex"> |
||||
{props.list.map((item) => ( |
||||
<Item item={item} {...props} /> |
||||
))} |
||||
</div> |
||||
); |
||||
}; |
||||
|
||||
const Item = (props) => { |
||||
return ( |
||||
<div |
||||
onClick={() => props.handleRadioChange(props.item.name)} |
||||
style={{ |
||||
backgroundColor: props.item.name === props.value ? '#6f42c1' : 'rgb(181 224 252 / 47%)', |
||||
color: props.item.name === props.value ? 'white' : 'black', |
||||
}} |
||||
> |
||||
{props.item.name} |
||||
</div> |
||||
); |
||||
}; |
@ -0,0 +1,245 @@ |
||||
import React, { Component } from 'react'; |
||||
import ReactAudioPlayer from 'react-audio-player'; |
||||
import ReactPlayer from 'react-player'; |
||||
import CloudDownloadIcon from '@material-ui/icons/CloudDownload'; |
||||
import Avatar from '@material-ui/core/Avatar'; |
||||
import moment from 'jalali-moment'; |
||||
import DoneIcon from '@material-ui/icons/Done'; |
||||
import DoneAllIcon from '@material-ui/icons/DoneAll'; |
||||
import EditIcon from '@material-ui/icons/Edit'; |
||||
import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; |
||||
import './index.scss'; |
||||
import Menu from '@material-ui/core/Menu'; |
||||
import MenuItem from '@material-ui/core/MenuItem'; |
||||
import ReplyIcon from '@material-ui/icons/Reply'; |
||||
import ForwardIcon from '@material-ui/icons/Forward'; |
||||
import DeleteIcon from '@material-ui/icons/Delete'; |
||||
import FavoriteIcon from '@material-ui/icons/Favorite'; |
||||
import TextsmsIcon from '@material-ui/icons/Textsms'; |
||||
import ScheduleIcon from '@material-ui/icons/Schedule'; |
||||
const file = (fileId) => `${window.baseURL}/file/download/${fileId}`; |
||||
const menu = [ |
||||
{ text: 'پسند', icon: FavoriteIcon, onClick: () => {} }, |
||||
{ text: 'تبادل نظر', icon: TextsmsIcon, onClick: () => {} }, |
||||
{ text: 'پاسخ', icon: ReplyIcon, onClick: () => {} }, |
||||
{ text: 'باز ارسال', icon: ForwardIcon, onClick: () => {} }, |
||||
]; |
||||
const selfMenu = [ |
||||
{ text: 'باز ارسال', icon: ForwardIcon, onClick: () => {} }, |
||||
{ text: 'ویرایش', icon: EditIcon, onClick: () => {} }, |
||||
{ text: 'حذف', icon: DeleteIcon, onClick: () => {} }, |
||||
]; |
||||
export default class NormalMessage extends Component { |
||||
state = { messageMenu: null }; |
||||
handleClose = () => { |
||||
this.setState({ |
||||
messageMenu: null, |
||||
}); |
||||
}; |
||||
handleClick = (event) => { |
||||
this.setState({ |
||||
messageMenu: event.currentTarget, |
||||
}); |
||||
}; |
||||
render() { |
||||
const { message, user, document, isSelf, prevUserId, children,addcounter } = this.props; |
||||
const { messageMenu } = this.state; |
||||
const hasavatar = prevUserId != message.userId; |
||||
|
||||
return ( |
||||
<div |
||||
className="normal-message d-flex flex-column d-flex" |
||||
style={{ |
||||
filter: document !== null ? 'blur(16px)' : 'blur(0px)', |
||||
}} |
||||
> |
||||
{hasavatar && false && ( |
||||
<Avatar |
||||
alt={user.name} |
||||
src={window.baseURL + '/file/download/' + user.fileId} |
||||
className="" |
||||
style={{ |
||||
marginTop: -5, |
||||
backgroundColor: user.color, |
||||
alignSelf: isSelf ? 'flex-end' : 'flex-start', |
||||
}} |
||||
> |
||||
{user.name |
||||
.split(' ') |
||||
.map((e) => e.slice(0, 1)) |
||||
.join(' ')} |
||||
</Avatar> |
||||
)} |
||||
|
||||
<div |
||||
className="d-flex" |
||||
style={{ direction: isSelf ? 'rtl' : 'ltr', marginTop: hasavatar ? 0 : 0 }} |
||||
> |
||||
<div |
||||
className={ |
||||
message.type == 'text' |
||||
? `normal-message__box ` |
||||
: `normal-message__box normal-message__box--file` |
||||
} |
||||
style={{ |
||||
paddingTop: hasavatar ? 25 : 15, |
||||
backgroundColor: isSelf ? 'white' : 'rgb(220, 248, 198)', |
||||
width: 'calc(100%-70px)', |
||||
}} |
||||
> |
||||
<div |
||||
className="normal-message__box--detail d-flex" |
||||
style={{ |
||||
top: 0, |
||||
|
||||
direction: isSelf ? 'ltr' : 'rtl', |
||||
paddingRight: isSelf ? 20 : 20, |
||||
paddingLeft: isSelf ? 0 : 0, |
||||
}} |
||||
> |
||||
<div style={{ color: isSelf ? '#aaa' : 'gray' }} onClick={() => addcounter('nothing')}> |
||||
<ExpandMoreIcon fontSize="small" onClick={this.handleClick} /> |
||||
</div> |
||||
<Menu |
||||
anchorEl={messageMenu} |
||||
keepMounted |
||||
open={Boolean(messageMenu)} |
||||
onClose={() => this.handleClose()} |
||||
className="rtl fontLight" |
||||
> |
||||
{(isSelf ? selfMenu : menu).map((e) => ( |
||||
<MenuItem |
||||
className="fontLight" |
||||
onClick={() => { |
||||
this.handleClose(); |
||||
e.onClick(); |
||||
addcounter('nothing'); |
||||
}} |
||||
> |
||||
{e.text} |
||||
<e.icon fontSize="samall" /> |
||||
</MenuItem> |
||||
))} |
||||
</Menu> |
||||
{hasavatar && <div style={{ color: isSelf ? '#aaa' : 'gray' }}>{user.name}</div>} |
||||
</div> |
||||
|
||||
{message.type == 'text' ? ( |
||||
<p style={{ color: isSelf ? 'black' : 'black' }}>{message.text}</p> |
||||
) : null} |
||||
{message.type == 'voice' ? ( |
||||
<ReactAudioPlayer src={file(message.fileId)} controls /> |
||||
) : null} |
||||
{message.type == 'image' ? ( |
||||
<div className="normal-message__box--caption"> |
||||
<a href={file(message.fileId)} download> |
||||
<img src={file(message.fileId)} download alt="" /> |
||||
</a> |
||||
<p style={{ color: isSelf ? 'black' : 'black' }}>{message.text}</p> |
||||
</div> |
||||
) : null} |
||||
{message.type == 'video' ? ( |
||||
<div className="normal-message__box--caption"> |
||||
<a href={file(message.fileId)} download> |
||||
<ReactPlayer url={file(message.fileId)} controls width={'100%'} height={250} /> |
||||
</a> |
||||
<p style={{ color: isSelf ? 'black' : 'black' }}>{message.text}</p> |
||||
</div> |
||||
) : null} |
||||
{message.type == 'audio' ? ( |
||||
<div className="normal-message__box--caption"> |
||||
<ReactAudioPlayer src={file(message.fileId)} controls /> |
||||
<p style={{ color: isSelf ? 'black' : 'black' }}>{message.text}</p> |
||||
</div> |
||||
) : null} |
||||
{message.type == 'document' ? ( |
||||
<a |
||||
href={file(message.fileId)} |
||||
download |
||||
className="normal-message__box--document d-flex" |
||||
style={{ |
||||
backgroundColor: isSelf ? 'rgb(15, 93, 190)' : 'white', |
||||
color: isSelf ? 'black' : 'black', |
||||
}} |
||||
> |
||||
<CloudDownloadIcon |
||||
style={{ |
||||
color: isSelf ? 'black' : 'black', |
||||
width: 32, |
||||
height: 32, |
||||
marginRight: 10, |
||||
}} |
||||
/> |
||||
<div>{message.text}</div> |
||||
</a> |
||||
) : null} |
||||
|
||||
<div className={'normal-message__box--date d-flex ' + (isSelf ? 'self' : '')}> |
||||
<span style={{ color: isSelf ? '#aaa' : 'gray' }}> |
||||
{message.updatedAt != message.createdAt ? ( |
||||
<> |
||||
<span> ویرایش در</span> |
||||
|
||||
{moment(message.updatedAt).format(' hh:mm jMM/jDD')} |
||||
</> |
||||
) : ( |
||||
moment(message.createdAt).format('hh:mm') |
||||
)} |
||||
</span> |
||||
<span |
||||
style={{ |
||||
color: isSelf ? '#aaa' : 'gray', |
||||
padding: '0 2px', |
||||
}} |
||||
> |
||||
{isSelf && message.status == 0 && <ScheduleIcon fontSize="small" />} |
||||
{isSelf && message.status == 2 && <DoneIcon fontSize="small" />} |
||||
{isSelf && message.status == 1 && ( |
||||
<DoneAllIcon fontSize="small" style={{ color: '#00afea', fontSize: '17px' }} /> |
||||
)} |
||||
</span> |
||||
</div> |
||||
</div> |
||||
|
||||
<div className={isSelf ? 'icons self' : 'icons'} onClick={() => addcounter('nothing')}> |
||||
{!isSelf ? ( |
||||
<> |
||||
{' '} |
||||
<div> |
||||
<span> {10}</span> |
||||
<FavoriteIcon fontSize="samall" color="primary" /> |
||||
|
||||
</div> |
||||
<div> |
||||
<span> {10}</span> |
||||
<TextsmsIcon fontSize="samall" color="secondary" /> |
||||
</div> |
||||
</> |
||||
) : ( |
||||
<> |
||||
<div> |
||||
<span> |
||||
+5 |
||||
<br /> |
||||
خلاقیت |
||||
</span> |
||||
</div> |
||||
</> |
||||
)} |
||||
</div> |
||||
</div> |
||||
<div |
||||
className="f-flex keys" |
||||
style={{ |
||||
width: 'calc(100% - 70px)', |
||||
marginRight: isSelf ? 0 : 70, |
||||
marginLeft: isSelf ? 70 : 0, |
||||
alignSelf: isSelf ? 'flex-end' : 'flex-start', |
||||
}} |
||||
> |
||||
{children} |
||||
</div> |
||||
</div> |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,155 @@ |
||||
@media screen and (max-width: 4500px) { |
||||
.normal-message__box--date.self { |
||||
padding-right: 20px 0 0 !important ; |
||||
} |
||||
.keys { |
||||
max-width: 465px; |
||||
min-width: 250px; |
||||
padding: 2px 0; |
||||
} |
||||
.icons { |
||||
//position: absolute; |
||||
width: 90px; |
||||
padding: 0 5px; |
||||
display: inline-block !important; |
||||
// top: 55px; |
||||
// right: -35px; |
||||
direction:rtl; |
||||
text-align: left; |
||||
& .MuiSvgIcon-root { |
||||
font-size: 18px !important; |
||||
opacity: 0.8; |
||||
} |
||||
& span { |
||||
font-size: 11px !important; |
||||
opacity: 0.8; |
||||
} |
||||
} |
||||
.icons.self { |
||||
direction: ltr; |
||||
text-align: right; |
||||
font-size: small; |
||||
// left: -35px; |
||||
// right: none; |
||||
} |
||||
.MuiMenuItem-root { |
||||
min-height: 10px !important; |
||||
font-size: 12px !important; |
||||
color: grey; |
||||
padding: 5px 10px !important; |
||||
& .MuiSvgIcon-root { |
||||
font-size: 16px !important; |
||||
padding: 2px; |
||||
color: grey; |
||||
} |
||||
} |
||||
|
||||
.normal-message { |
||||
// width: 100%; |
||||
position: relative; |
||||
margin-bottom: 2px; |
||||
img { |
||||
width: 65px; |
||||
height: 65px; |
||||
object-fit: cover; |
||||
border-radius: 15px; |
||||
border: 3px solid rgba(201, 201, 201, 0.857); |
||||
position: relative; |
||||
top: 20px; |
||||
z-index: 50; |
||||
} |
||||
&__box { |
||||
line-height: 1.3em; |
||||
max-width: 500px; |
||||
min-width: 250px; |
||||
display: flex; |
||||
justify-content: flex-end; |
||||
// width: 100%; |
||||
padding: 20px 10px 15px; |
||||
background-color: white; |
||||
border-radius: 5px; |
||||
position: relative; |
||||
p { |
||||
width: 100%; |
||||
text-align: right; |
||||
direction: rtl; |
||||
font-size: 15px; |
||||
margin: 0 0 5px !important; |
||||
// font-family: IRANSansNumeral; |
||||
font-weight: 200; |
||||
color: #afafaf; |
||||
} |
||||
&--detail { |
||||
position: absolute; |
||||
width: 100%; |
||||
top: 10px; |
||||
justify-content: space-between; |
||||
font-size: 12px; |
||||
span { |
||||
font-family: IRANSansNumeralLight; |
||||
} |
||||
} |
||||
|
||||
&--date { |
||||
position: absolute; |
||||
width: 100%; |
||||
padding: 0px 0px 0; |
||||
bottom: 0px; |
||||
justify-content: flex-end; |
||||
font-size: 11px; |
||||
span { |
||||
font-family: IRANSansNumeralLight; |
||||
} |
||||
} |
||||
&--date.self { |
||||
justify-content: flex-end; |
||||
padding: 0; |
||||
} |
||||
&--caption { |
||||
width: 100%; |
||||
margin-bottom: 20px; |
||||
text-align: right; |
||||
& img { |
||||
height: 100%; |
||||
max-height: 250px; |
||||
width: 100%; |
||||
max-width: 250px; |
||||
border-radius: 0px; |
||||
border: 0px; |
||||
position: static; |
||||
} |
||||
& video { |
||||
height: 180px !important; |
||||
width: 100%; |
||||
} |
||||
& p { |
||||
margin-top: 20px; |
||||
} |
||||
// & audio{ |
||||
// min-width: 100% !important; |
||||
// } |
||||
} |
||||
&--file { |
||||
background-color: rgb(143, 228, 143) !important; |
||||
} |
||||
&--document { |
||||
width: 100%; |
||||
max-width: 300px; |
||||
align-items: center; |
||||
justify-content: space-between; |
||||
height: 50px; |
||||
padding: 10px; |
||||
border-radius: 10px; |
||||
div { |
||||
font-size: 14px; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
.react-audio-player { |
||||
width: 100% !important; |
||||
color: red; |
||||
fill: red; |
||||
} |
@ -0,0 +1,56 @@ |
||||
import React, { Component } from 'react'; |
||||
import ArrowRightAltIcon from '@material-ui/icons/ArrowRightAlt'; |
||||
|
||||
export default class RadioMessage extends Component { |
||||
state = { |
||||
value: this.props.message.value, |
||||
}; |
||||
handleRadioChange = (value) => { |
||||
if (this.props.isActive) this.setState({ value }); |
||||
}; |
||||
|
||||
render() { |
||||
const { message, user, isSelf, isActive, action } = this.props; |
||||
const { value } = this.state; |
||||
return ( |
||||
<div className="message-2d d-flex flex-column d-flex"> |
||||
<div className="message-2d__options d-flex flex-wrap justify-content-center"> |
||||
{message.options.map((row) => ( |
||||
<Row list={row} handleRadioChange={this.handleRadioChange} value={value} /> |
||||
))} |
||||
</div> |
||||
{isActive && ( |
||||
<button className="message-2d__submit" onClick={() => action({ value })}> |
||||
ثبت |
||||
<ArrowRightAltIcon /> |
||||
</button> |
||||
)} |
||||
</div> |
||||
); |
||||
} |
||||
} |
||||
|
||||
const Row = (props) => { |
||||
return ( |
||||
<div className="message-2d__options--row d-flex"> |
||||
{props.list.map((item) => ( |
||||
<Item item={item} {...props} /> |
||||
))} |
||||
</div> |
||||
); |
||||
}; |
||||
|
||||
const Item = (props) => { |
||||
return ( |
||||
<div |
||||
onClick={() => props.handleRadioChange(props.item.name)} |
||||
style={{ |
||||
backgroundColor: |
||||
props.item.name === props.value ? 'rgb(100, 205, 130)' : 'rgba(255, 255, 255, 0.4)', |
||||
color: props.item.name === props.value ? 'white' : 'black', |
||||
}} |
||||
> |
||||
{props.item.name} |
||||
</div> |
||||
); |
||||
}; |
@ -0,0 +1,62 @@ |
||||
import React, { Component } from 'react'; |
||||
import Grid from '@material-ui/core/Grid'; |
||||
import Slider from '@material-ui/core/Slider'; |
||||
import Input from '@material-ui/core/Input'; |
||||
import ArrowRightAltIcon from '@material-ui/icons/ArrowRightAlt'; |
||||
import Avatar from '@material-ui/core/Avatar'; |
||||
import { toast } from 'react-toastify'; |
||||
export default class SliderMessage extends Component { |
||||
state = { |
||||
value: this.props.message.value || 0, |
||||
}; |
||||
onChange = (value) => { |
||||
if (!this.props.isActive || !value) return; |
||||
const [min, max, step] = this.props.message.options[0] || [0, 100, 1]; |
||||
this.setState({ value: Math.min(max, Math.max(min, parseFloat(value))) }); |
||||
}; |
||||
|
||||
render() { |
||||
const { message, isActive, action } = this.props; |
||||
const [min, max, step] = message.options[0] || [0, 100, 1]; |
||||
const { value } = this.state; |
||||
return ( |
||||
<div className="message-2d d-flex flex-column d-flex sliderBox"> |
||||
<Grid container spacing={2} alignItems="center"> |
||||
<Grid item xs> |
||||
<Slider |
||||
className="slider" |
||||
value={value} |
||||
onChange={(e, value) => this.onChange(value)} |
||||
aria-labelledby="input-slider" |
||||
min={min} |
||||
max={max} |
||||
step={step} |
||||
/> |
||||
</Grid> |
||||
<Grid item> |
||||
<Input |
||||
className="input" |
||||
disabled={!isActive} |
||||
value={this.state.value} |
||||
onChange={(e) => this.onChange(e.target.value)} |
||||
onBlur={() => this.onChange(value)} |
||||
inputProps={{ |
||||
step: step, |
||||
min: min, |
||||
max: max, |
||||
type: 'number', |
||||
'aria-labelledby': 'input-slider', |
||||
}} |
||||
/> |
||||
</Grid> |
||||
</Grid> |
||||
{isActive && ( |
||||
<button className="message-2d__submit" onClick={() => action()}> |
||||
ثبت |
||||
<ArrowRightAltIcon /> |
||||
</button> |
||||
)} |
||||
</div> |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,13 @@ |
||||
.sliderBox { |
||||
margin-top: 2px; |
||||
.slider { |
||||
margin-top: 10px; |
||||
} |
||||
input { |
||||
width: 42px; |
||||
border-bottom: 2px solid #000099; |
||||
padding-left: 10px; |
||||
margin-bottom: 18px; |
||||
color: #000099; |
||||
} |
||||
} |
@ -0,0 +1,53 @@ |
||||
import React, { Component, useEffect } from 'react'; |
||||
import { SortableContainer, SortableElement } from 'react-sortable-hoc'; |
||||
import arrayMove from 'array-move'; |
||||
import ArrowRightAltIcon from '@material-ui/icons/ArrowRightAlt'; |
||||
import Avatar from '@material-ui/core/Avatar'; |
||||
import { toast } from 'react-toastify'; |
||||
|
||||
export default class DraggableMessage extends Component { |
||||
constructor(props) { |
||||
super(props); |
||||
this.state = this.props.message; |
||||
} |
||||
onSortEnd = ({ oldIndex, newIndex }) => { |
||||
if (this.props.isActive) |
||||
this.setState(({ options }) => ({ |
||||
options: arrayMove(options, oldIndex, newIndex), |
||||
})); |
||||
}; |
||||
|
||||
render() { |
||||
const { message, user, isSelf, isActive, action } = this.props; |
||||
|
||||
return ( |
||||
<div className="message-2d d-flex flex-column d-flex"> |
||||
<SortableList items={this.state.options} onSortEnd={this.onSortEnd} disabled={!isActive} /> |
||||
{isActive && ( |
||||
<button className="message-2d__submit" onClick={() => action()}> |
||||
ثبت |
||||
<ArrowRightAltIcon /> |
||||
</button> |
||||
)} |
||||
</div> |
||||
); |
||||
} |
||||
} |
||||
|
||||
const SortableList = SortableContainer(({ items, disabled }) => { |
||||
return ( |
||||
<div className="message-2d__options d-flex flex-column"> |
||||
{items.map((row, index) => ( |
||||
<SortableItem key={`item-${index}`} index={index} value={row} disabled={disabled} /> |
||||
))} |
||||
</div> |
||||
); |
||||
}); |
||||
|
||||
const SortableItem = SortableElement(({ value }) => ( |
||||
<div className="message-2d__options--row d-flex"> |
||||
{value.map((item) => ( |
||||
<div>{item.name}</div> |
||||
))} |
||||
</div> |
||||
)); |
@ -0,0 +1,27 @@ |
||||
import React, { Component } from 'react'; |
||||
import arrayMove from 'array-move'; |
||||
import './index.scss'; |
||||
|
||||
export default class DraggableMessage extends Component { |
||||
constructor(props) { |
||||
super(props); |
||||
this.state = this.props.message; |
||||
} |
||||
onSortEnd = ({ oldIndex, newIndex }) => { |
||||
if (this.props.isActive) |
||||
this.setState(({ options }) => ({ |
||||
options: arrayMove(options, oldIndex, newIndex), |
||||
})); |
||||
}; |
||||
|
||||
render() { |
||||
const { message, user, isSelf, isActive } = this.props; |
||||
if (!isActive) return null; |
||||
return <div className="ptapbox"></div>; |
||||
} |
||||
a = ( |
||||
<button className="message-2d__submit" onClick={(e) => this.props.action({ value : e.target.value })}> |
||||
ثبت |
||||
</button> |
||||
); |
||||
} |
@ -0,0 +1,15 @@ |
||||
.tapbox { |
||||
z-index: 500; |
||||
position: absolute; |
||||
left: -10px; |
||||
background-color: #fff; |
||||
width: 100vw; |
||||
height: calc(100% - 90px); |
||||
opacity: 0.1; |
||||
cursor: pointer; |
||||
} |
||||
.ptapbox { |
||||
top: 0; |
||||
left: 0; |
||||
position: absolute; |
||||
} |
@ -0,0 +1,48 @@ |
||||
import React, { Component } from 'react'; |
||||
import Normal from './Normal'; |
||||
import Inline from './Inline'; |
||||
import Sort from './Sort'; |
||||
import Slider from './Slider'; |
||||
import Select from './Select'; |
||||
import MultiSelect from './MultiSelect'; |
||||
import Tap from './Tap'; |
||||
import ArrowRightAltIcon from '@material-ui/icons/ArrowRightAlt'; |
||||
|
||||
import './index.scss'; |
||||
const types = { |
||||
sort: Sort, |
||||
inline: Inline, |
||||
slider: Slider, |
||||
select: Select, |
||||
multiselect: MultiSelect, |
||||
tap: Tap, |
||||
}; |
||||
export default class Message extends Component { |
||||
render() { |
||||
const { message, user, isSelf, file, document, prevUserId, isActive, addcounter } = this.props; |
||||
const Action = types[message.action]; |
||||
return ( |
||||
<div className="Message"> |
||||
<Normal |
||||
addcounter={addcounter} |
||||
message={message} |
||||
prevUserId={prevUserId} |
||||
user={user} |
||||
isSelf={isSelf} |
||||
file={file} |
||||
document={document} |
||||
> |
||||
{Action ? ( |
||||
<Action message={message} isSelf={isSelf} isActive={isActive} action={addcounter} /> |
||||
) : null} |
||||
{/* {isActive && ( |
||||
<button className="message-2d__submit" onClick={() => action({ value })}> |
||||
ثبت |
||||
<ArrowRightAltIcon /> |
||||
</button> |
||||
)} */} |
||||
</Normal> |
||||
</div> |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,5 @@ |
||||
@media screen and (max-width: 4500px) { |
||||
.Message { |
||||
margin-top: 2px; |
||||
} |
||||
} |
@ -0,0 +1,493 @@ |
||||
import React, { Component } from 'react'; |
||||
import Message from './Message'; |
||||
import ChatInputs from './Input'; |
||||
import Keyboard from './Keyboard'; |
||||
import { RecordState } from 'audio-react-recorder'; |
||||
import ReactPlayer from 'react-player'; |
||||
import ReactAudioPlayer from 'react-audio-player'; |
||||
import { ToastContainer } from 'react-toastify'; |
||||
import { list, send, sendKeys } from '../../../Redux/actions/message'; |
||||
import { info, bio } from '../../../Redux/actions/chat'; |
||||
import { connect } from 'react-redux'; |
||||
import Emoji from './Emoji/index'; |
||||
|
||||
import './index.scss'; |
||||
class List extends Component { |
||||
constructor(props) { |
||||
super(props); |
||||
this.state = { |
||||
emoji: false, |
||||
counter: 1, |
||||
id: window.location.href.split('/').pop() * 1, |
||||
data: {}, |
||||
message: '', |
||||
voice: null, |
||||
file: null, |
||||
image: null, |
||||
video: null, |
||||
music: null, |
||||
document: null, |
||||
}; |
||||
} |
||||
addcounter(type) { |
||||
if (type === 'parent') { |
||||
if (localStorage.getItem('tapType') === 'nothing') { |
||||
localStorage.removeItem('tapType'); |
||||
return; |
||||
} else { |
||||
this.setState({ counter: this.state.counter + 1 }); |
||||
} |
||||
} else { |
||||
localStorage.setItem('tapType', 'nothing'); |
||||
return; |
||||
} |
||||
} |
||||
|
||||
start = () => { |
||||
this.setState({ |
||||
voice: RecordState.START, |
||||
}); |
||||
}; |
||||
|
||||
stop = () => { |
||||
this.setState({ |
||||
voice: RecordState.STOP, |
||||
}); |
||||
}; |
||||
|
||||
//audioData contains blob and blobUrl
|
||||
onStop = (audioData) => { |
||||
this.setState({ |
||||
voice: audioData, |
||||
}); |
||||
this.sendVoice(audioData); |
||||
}; |
||||
|
||||
cancel = () => { |
||||
this.setState({ |
||||
voice: null, |
||||
}); |
||||
}; |
||||
|
||||
typeText = (type,val) => { |
||||
if(type === 'text'){ |
||||
this.setState({ |
||||
message: val, |
||||
}); |
||||
} |
||||
else{ |
||||
this.setState(prevState => ({ |
||||
message: prevState.message + val , |
||||
})); |
||||
} |
||||
}; |
||||
|
||||
|
||||
|
||||
addFile = async (e) => { |
||||
if ( |
||||
e.target.files[0].type === 'image/apng' || |
||||
e.target.files[0].type === 'image/avif' || |
||||
e.target.files[0].type === 'image/gif' || |
||||
e.target.files[0].type === 'image/jpeg' || |
||||
e.target.files[0].type === 'image/png' || |
||||
e.target.files[0].type === 'image/svg+xml' || |
||||
e.target.files[0].type === 'image/webp' |
||||
) { |
||||
this.setState({ |
||||
image: URL.createObjectURL(e.target.files[0]), |
||||
file: 'fill', |
||||
video: null, |
||||
document: null, |
||||
music: null, |
||||
}); |
||||
} else if ( |
||||
e.target.files[0].type === 'video/3gpp' || |
||||
e.target.files[0].type === 'video/mpeg' || |
||||
e.target.files[0].type === 'video/mp4' || |
||||
e.target.files[0].type === 'video/ogg' || |
||||
e.target.files[0].type === 'video/quicktime' || |
||||
e.target.files[0].type === 'video/webm' |
||||
) { |
||||
this.setState({ |
||||
image: null, |
||||
file: 'fill', |
||||
video: URL.createObjectURL(e.target.files[0]), |
||||
document: null, |
||||
music: null, |
||||
}); |
||||
} else if ( |
||||
e.target.files[0].type === 'audio/3gpp' || |
||||
e.target.files[0].type === 'audio/aac' || |
||||
e.target.files[0].type === 'audio/flac' || |
||||
e.target.files[0].type === 'audio/mpeg' || |
||||
e.target.files[0].type === 'audio/mp3' || |
||||
e.target.files[0].type === 'audio/mp4' || |
||||
e.target.files[0].type === 'audio/ogg' || |
||||
e.target.files[0].type === 'audio/wav' || |
||||
e.target.files[0].type === 'audio/webm' |
||||
) { |
||||
this.setState({ |
||||
image: null, |
||||
file: 'fill', |
||||
video: null, |
||||
document: null, |
||||
music: URL.createObjectURL(e.target.files[0]), |
||||
}); |
||||
} else { |
||||
this.setState({ |
||||
image: null, |
||||
file: 'fill', |
||||
video: null, |
||||
music: null, |
||||
document: { data: URL.createObjectURL(e.target.files[0]), name: e.target.files[0].name }, |
||||
}); |
||||
} |
||||
}; |
||||
|
||||
sendMessage = () => { |
||||
if (this.state.file !== null) { |
||||
this.sendFile(); |
||||
return; |
||||
} else { |
||||
let now = new Date(); |
||||
this.setState((prevState) => ({ |
||||
data: { |
||||
...prevState.data, |
||||
messages: [ |
||||
...prevState.data.messages, |
||||
{ |
||||
id: 'm12', |
||||
type: 'normal', |
||||
text: prevState.message, |
||||
voice: null, |
||||
image: null, |
||||
file: null, |
||||
video: null, |
||||
music: null, |
||||
document: null, |
||||
createdAt: now.toString(), |
||||
user: { |
||||
id: 'u1', |
||||
name: 'علی', |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
})); |
||||
this.setState({ |
||||
message: '', |
||||
}); |
||||
} |
||||
}; |
||||
|
||||
sendVoice = (audioData) => { |
||||
let now = new Date(); |
||||
this.setState((prevState) => ({ |
||||
data: { |
||||
...prevState.data, |
||||
messages: [ |
||||
...prevState.data.messages, |
||||
{ |
||||
id: 'm9', |
||||
text: null, |
||||
type: 'normal', |
||||
voice: audioData, |
||||
createdAt: now.toString(), |
||||
image: null, |
||||
video: null, |
||||
document: null, |
||||
music: null, |
||||
user: { |
||||
id: 'u1', |
||||
name: 'علی', |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
})); |
||||
this.setState({ |
||||
voice: null, |
||||
}); |
||||
}; |
||||
|
||||
sendFile = () => { |
||||
const { image, video, music, document } = this.state; |
||||
let now = new Date(); |
||||
if (image !== null) { |
||||
this.setState((prevState) => ({ |
||||
data: { |
||||
...prevState.data, |
||||
messages: [ |
||||
...prevState.data.messages, |
||||
{ |
||||
id: 'm8', |
||||
type: 'normal', |
||||
text: prevState.message, |
||||
voice: null, |
||||
createdAt: now.toString(), |
||||
image: image, |
||||
video: null, |
||||
document: null, |
||||
music: null, |
||||
user: { |
||||
id: 'u1', |
||||
name: 'علی', |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
})); |
||||
this.setState({ |
||||
file: null, |
||||
image: null, |
||||
message: '', |
||||
}); |
||||
} else if (video !== null) { |
||||
this.setState((prevState) => ({ |
||||
data: { |
||||
...prevState.data, |
||||
messages: [ |
||||
...prevState.data.messages, |
||||
{ |
||||
id: 'm8', |
||||
type: 'normal', |
||||
text: prevState.message, |
||||
voice: null, |
||||
createdAt: now.toString(), |
||||
image: null, |
||||
video: video, |
||||
document: null, |
||||
music: null, |
||||
user: { |
||||
id: 'u1', |
||||
name: 'علی', |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
})); |
||||
this.setState({ |
||||
file: null, |
||||
video: null, |
||||
message: '', |
||||
}); |
||||
} else if (music !== null) { |
||||
this.setState((prevState) => ({ |
||||
data: { |
||||
...prevState.data, |
||||
messages: [ |
||||
...prevState.data.messages, |
||||
{ |
||||
id: 'm8', |
||||
text: prevState.message, |
||||
type: 'normal', |
||||
voice: null, |
||||
createdAt: now.toString(), |
||||
image: null, |
||||
video: null, |
||||
document: null, |
||||
music: music, |
||||
user: { |
||||
id: 'u1', |
||||
name: 'علی', |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
})); |
||||
this.setState({ |
||||
file: null, |
||||
music: null, |
||||
message: '', |
||||
}); |
||||
} else if (document !== null) { |
||||
this.setState((prevState) => ({ |
||||
data: { |
||||
...prevState.data, |
||||
messages: [ |
||||
...prevState.data.messages, |
||||
{ |
||||
id: 'm8', |
||||
text: prevState.message, |
||||
type: 'normal', |
||||
voice: null, |
||||
|
||||
createdAt: now.toString(), |
||||
image: null, |
||||
video: null, |
||||
document: document, |
||||
music: null, |
||||
user: { |
||||
id: 'u1', |
||||
name: 'علی', |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
})); |
||||
this.setState({ |
||||
file: null, |
||||
document: null, |
||||
message: '', |
||||
}); |
||||
} |
||||
}; |
||||
|
||||
cancelSendFile = () => { |
||||
this.setState({ |
||||
file: null, |
||||
document: null, |
||||
message: '', |
||||
image: null, |
||||
video: null, |
||||
voice: null, |
||||
music: null, |
||||
}); |
||||
}; |
||||
|
||||
componentDidMount() { |
||||
this.props.list({ chatId: this.state.id }); |
||||
this.props.info({ id: this.state.id }); |
||||
// this.componentDidUpdate();
|
||||
} |
||||
|
||||
componentDidUpdate() { |
||||
let chatroom = document.getElementById('chatroom'); |
||||
chatroom.scrollTop = chatroom.scrollHeight + 40; |
||||
} |
||||
randomColor() { |
||||
let hex = Math.floor(Math.random() * 0x999999); |
||||
let color = '#' + hex.toString(16); |
||||
|
||||
return color; |
||||
} |
||||
render() { |
||||
const { setPage, chatInfo, messageList: msaster, userId } = this.props; //last
|
||||
const messageList = msaster.slice(0, this.state.counter); |
||||
const last = messageList.slice(-1)[0] || {}; |
||||
const userIdx = this.userIdx || {}; |
||||
if (Object.keys(userIdx).length == 0) { |
||||
chatInfo.users.map((e) => (userIdx[e.id] = { ...e, color: this.randomColor() })); |
||||
this.userIdx = userIdx; |
||||
} |
||||
|
||||
return ( |
||||
<div |
||||
className="chat-body d-flex flex-column nobar " |
||||
onClick={() => this.addcounter('parent')} |
||||
> |
||||
<div |
||||
className="chat-body__content d-flex flex-column nobar" |
||||
id="chatroom" |
||||
style={{ |
||||
paddingTop: 10, |
||||
overflowX: 'hidden', |
||||
overflowY: this.state.file !== null ? 'hidden' : 'scroll', |
||||
paddingBottom: last.action === 'keyboard' ? 150 : 10, |
||||
}} |
||||
> |
||||
{chatInfo.users && |
||||
messageList.map((message, i) => |
||||
this.state.image === null && |
||||
this.state.music === null && |
||||
this.state.video === null ? ( |
||||
<Message |
||||
addcounter={() => this.addcounter()} |
||||
isActive={message.id == last.id} |
||||
prevUserId={i > 0 ? messageList[i - 1].userId : 0} |
||||
isSelf={new Set([101, 103, 106, 108, 110]).has(message.userId * 1)} |
||||
key={message.id} |
||||
file={this.state.image} |
||||
document={this.state.document} |
||||
message={message} |
||||
user={userIdx[message.userId] || { id: 0, fileId: 0, name: 'null' }} |
||||
/> |
||||
) : null |
||||
)} |
||||
{this.state.image !== null ? ( |
||||
<div className="chat-body__content--preview d-flex"> |
||||
<img src={this.state.image} alt="" /> |
||||
</div> |
||||
) : null} |
||||
{this.state.video !== null ? ( |
||||
<div className="chat-body__content--preview d-flex"> |
||||
<ReactPlayer url={this.state.video} controls /> |
||||
</div> |
||||
) : null} |
||||
{this.state.music !== null ? ( |
||||
<div className="chat-body__content--preview d-flex"> |
||||
<ReactAudioPlayer src={this.state.music} controls /> |
||||
</div> |
||||
) : null} |
||||
{this.state.document !== null ? ( |
||||
<div className="chat-body__content--confirm d-flex"> |
||||
<div className="chat-body__content--confirm___box d-flex flex-column"> |
||||
<p>{`ارسال (${this.state.document.name}) به ${this.props.info.title}؟`}</p> |
||||
<div className="chat-body__content--confirm___box---options d-flex"> |
||||
<button style={{ color: '#00ff00' }} onClick={() => this.sendFile()}> |
||||
ارسال |
||||
</button> |
||||
<button style={{ color: '#ffffff' }} onClick={() => this.cancelSendFile()}> |
||||
کنسل |
||||
</button> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
) : null} |
||||
</div> |
||||
<div |
||||
className="chat-body__footer d-flex nobar d-flex flex-column" |
||||
onClick={() => this.addcounter('nothing')} |
||||
> |
||||
{last.action == 'text' ? ( |
||||
<ChatInputs |
||||
addcounter={() => this.addcounter()} |
||||
onStop={this.onStop} |
||||
stop={this.stop} |
||||
start={this.start} |
||||
cancel={this.cancel} |
||||
message={this.state.message} |
||||
recordState={this.state.voice} |
||||
typeText={this.typeText} |
||||
sendMessage={this.sendMessage} |
||||
addFile={this.addFile} |
||||
file={this.state.file} |
||||
parent={this} |
||||
/> |
||||
) : null} |
||||
{last.action === 'keyboard' ? ( |
||||
<Keyboard options={last.options} addcounter={() => this.addcounter()} /> |
||||
) : null} |
||||
{this.state.emoji ? ( |
||||
<div className="chat-body__footer--emoji d-flex flex-column"> |
||||
<Emoji typeText={this.typeText} /> |
||||
</div> |
||||
) : null} |
||||
</div> |
||||
<ToastContainer |
||||
position="top-center" |
||||
autoClose={1500} |
||||
hideProgressBar={true} |
||||
newestOnTop={true} |
||||
closeOnClick |
||||
rtl={true} |
||||
pauseOnFocusLoss |
||||
draggable |
||||
pauseOnHover |
||||
/> |
||||
</div> |
||||
); |
||||
} |
||||
} |
||||
export default connect( |
||||
(state) => { |
||||
return { |
||||
chatInfo: state.chat.info, |
||||
userId: state.user.isLogin.id, |
||||
messageList: state.message.list || [], |
||||
last: state.message.list.slice(-1)[0] || {}, |
||||
}; |
||||
}, |
||||
{ list, send, sendKeys, info, bio } |
||||
)(List); |
@ -0,0 +1,139 @@ |
||||
@media screen and (max-width: 4500px) { |
||||
.chat-body .MuiAvatar-circle { |
||||
box-shadow: 0px 0px 0px 2px #fff; |
||||
border: 3px solid #ffffff66; |
||||
width: 60px; |
||||
height: 60px; |
||||
// top: 20px; |
||||
z-index: 30; |
||||
// font-family: 'IRANSansNumeralBold'; |
||||
// & div { |
||||
// font-family: 'IRANSansNumeralBold'; |
||||
// } |
||||
} |
||||
|
||||
.chat-body { |
||||
background-color: rgb(210, 196, 214); |
||||
// justify-content: flex-start; |
||||
flex: 1; |
||||
height: 100%; |
||||
overflow-x: hidden; |
||||
font-family: numeralLight; |
||||
&__content { |
||||
background: url(../../../assets/images/whatsapp.png); |
||||
background-size: cover; |
||||
padding: 0px 10px 0px; |
||||
margin-bottom: 55px; |
||||
height: 100%; |
||||
min-height: 200px; |
||||
position: relative; |
||||
&--preview { |
||||
position: absolute; |
||||
bottom: 0px; |
||||
left: 0px; |
||||
width: 100%; |
||||
height: 100%; |
||||
justify-content: center; |
||||
align-items: center; |
||||
background-color: rgb(210, 196, 214); |
||||
z-index: 1000; |
||||
& img { |
||||
width: 100%; |
||||
max-height: 400px; |
||||
object-fit: scale-down; |
||||
} |
||||
} |
||||
&--confirm { |
||||
width: 100vw; |
||||
height: 100vh; |
||||
position: fixed; |
||||
top: 0px; |
||||
left: 0px; |
||||
justify-content: center; |
||||
align-items: center; |
||||
z-index: 300; |
||||
&___box { |
||||
position: relative; |
||||
bottom: 50px; |
||||
width: 100%; |
||||
max-width: 250px; |
||||
padding: 10px; |
||||
// background: rgb(139, 73, 186); |
||||
// background: linear-gradient(180deg, rgb(144, 73, 186) 0%, #64cd82 95%); |
||||
text-align: right; |
||||
direction: rtl; |
||||
color: white; |
||||
border-radius: 5px; |
||||
box-shadow: 0px 0px 20px rgba(0, 0, 0, 1); |
||||
font-size: 16px; |
||||
&---options { |
||||
justify-self: flex-end; |
||||
align-self: flex-start; |
||||
width: 110px; |
||||
height: 40px; |
||||
justify-content: space-between; |
||||
& button { |
||||
background-color: inherit; |
||||
border: none; |
||||
font-size: 14px; |
||||
&:focus { |
||||
outline: none; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
&__footer { |
||||
width: 100%; |
||||
z-index: 1000; |
||||
background-color: rgb(210, 196, 214); |
||||
background: url(../../../assets/images/whatsapp.png); |
||||
// box-shadow: 0px 0px 5px 2px #9d9595ad; |
||||
background-size: cover; |
||||
overflow-y: scroll; |
||||
overflow-x: hidden; |
||||
position: fixed; |
||||
bottom: 0px; |
||||
left: 0; |
||||
padding-bottom: 5px; |
||||
&--emoji{ |
||||
margin-top: 5px; |
||||
width: 100%; |
||||
background-color: red; |
||||
height: 250px; |
||||
// justify-self: flex-end; |
||||
// position: absolute; |
||||
// bottom: 0px; |
||||
// left: 0px; |
||||
.MuiBox-root{ |
||||
max-height : 202px; |
||||
min-height: 160px; |
||||
overflow-y: scroll; |
||||
padding: 10px 10px 0px !important; |
||||
} |
||||
|
||||
.MuiTab-root{ |
||||
padding: 2px !important; |
||||
min-height: 22px !important; |
||||
min-width: 34px !important; |
||||
font-size: 24px !important; |
||||
margin-right: 10px !important; |
||||
} |
||||
|
||||
.MuiTabs-root{ |
||||
min-height: 28px !important; |
||||
} |
||||
|
||||
.PrivateTabIndicator-colorPrimary-7{ |
||||
width : 34px !important; |
||||
background-color: green !important; |
||||
} |
||||
|
||||
.MuiPaper-elevation4{ |
||||
box-shadow: none !important; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,54 @@ |
||||
import React from 'react'; |
||||
import Badge from '@material-ui/core/Badge'; |
||||
import Avatar from '@material-ui/core/Avatar'; |
||||
import { makeStyles, withStyles } from '@material-ui/core/styles'; |
||||
|
||||
const StyledBadge = withStyles((theme) => ({ |
||||
badge: { |
||||
backgroundColor: '#44b700', |
||||
color: '#44b700', |
||||
boxShadow: `0 0 0 2px ${theme.palette.background.paper}`, |
||||
'&::after': { |
||||
position: 'absolute', |
||||
top: 0, |
||||
left: 0, |
||||
width: '100%', |
||||
height: '100%', |
||||
borderRadius: '50%', |
||||
animation: '$ripple 1.2s infinite ease-in-out', |
||||
border: '1px solid currentColor', |
||||
content: '""', |
||||
}, |
||||
}, |
||||
'@keyframes ripple': { |
||||
'0%': { |
||||
transform: 'scale(.8)', |
||||
opacity: 1, |
||||
}, |
||||
'100%': { |
||||
transform: 'scale(2.4)', |
||||
opacity: 0, |
||||
}, |
||||
}, |
||||
}))(Badge); |
||||
|
||||
const useStyles = makeStyles((theme) => ({ |
||||
root: { |
||||
display: 'flex', |
||||
'& > *': { |
||||
margin: 0, |
||||
}, |
||||
}, |
||||
})); |
||||
|
||||
export default function StatusAvatar(props) { |
||||
const classes = useStyles(); |
||||
|
||||
return ( |
||||
<div className={classes.root}> |
||||
<StyledBadge> |
||||
<Avatar style={{ width: 40, height: 40 }} alt="Remy Sharp" src={props.src} /> |
||||
</StyledBadge> |
||||
</div> |
||||
); |
||||
} |
@ -0,0 +1,102 @@ |
||||
import React, { Component } from "react"; |
||||
import MoreVertIcon from "@material-ui/icons/MoreVert"; |
||||
import ArrowForwardIcon from "@material-ui/icons/ArrowForward"; |
||||
import Menu from "@material-ui/core/Menu"; |
||||
import MenuItem from "@material-ui/core/MenuItem"; |
||||
import VideocamIcon from "@material-ui/icons/Videocam"; |
||||
import StatusAvatar from "./Avatar/index.js"; |
||||
import CallIcon from "@material-ui/icons/Call"; |
||||
import { connect } from "react-redux"; |
||||
|
||||
import "./index.scss"; |
||||
class Header extends Component { |
||||
constructor(props) { |
||||
super(props); |
||||
this.state = { |
||||
anchorEl: null, |
||||
}; |
||||
} |
||||
|
||||
handleClick = (event) => { |
||||
this.setState({ |
||||
anchorEl: event.currentTarget, |
||||
}); |
||||
}; |
||||
|
||||
handleClose = () => { |
||||
this.setState({ |
||||
anchorEl: null, |
||||
}); |
||||
}; |
||||
|
||||
back = () => { |
||||
window.history.back(); |
||||
}; |
||||
render() { |
||||
const { page, setPage, chatInfo } = this.props; |
||||
const { anchorEl } = this.state; |
||||
return ( |
||||
<div className="chat-header d-flex align-items-center"> |
||||
<div className="d-flex align-items-center"> |
||||
<ArrowForwardIcon |
||||
onClick={() => |
||||
page === "chatroom" ? this.back() : setPage("chatroom") |
||||
} |
||||
style={{ color: "white", width: 29, height: 29, marginTop: 4 }} |
||||
/> |
||||
<div |
||||
onClick={() => setPage("info")} |
||||
className="chat-header__info d-flex mr-1" |
||||
> |
||||
<StatusAvatar /> |
||||
<div className="d-flex flex-column mr-1"> |
||||
<h1>{chatInfo ? chatInfo.title : null}</h1> |
||||
<span>{chatInfo ? chatInfo.subTitle : null}</span> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div> |
||||
<div className="d-flex align-items-center"> |
||||
<MoreVertIcon |
||||
aria-controls="simple-menu" |
||||
aria-haspopup="true" |
||||
onClick={this.handleClick} |
||||
style={{ color: "white", width: 32, height: 32 }} |
||||
/> |
||||
{/* <VideocamIcon style={{ color: 'white', width: 30, height: 30, marginLeft: 8 }} /> |
||||
<CallIcon style={{ color: 'white', width: 27, height: 27, marginLeft: 17 }} /> */} |
||||
</div> |
||||
<Menu |
||||
id="simple-menu" |
||||
anchorEl={anchorEl} |
||||
keepMounted |
||||
open={Boolean(anchorEl)} |
||||
onClose={() => this.handleClose()} |
||||
className="fontLight rtl" |
||||
> |
||||
<MenuItem |
||||
className="fontLight menu" |
||||
onClick={() => this.handleClose()} |
||||
> |
||||
پروفایل |
||||
</MenuItem> |
||||
<MenuItem |
||||
className="fontLight menu" |
||||
onClick={() => this.handleClose()} |
||||
> |
||||
حساب کاربری |
||||
</MenuItem> |
||||
<MenuItem |
||||
className="fontLight menu" |
||||
onClick={() => this.handleClose()} |
||||
> |
||||
خروج |
||||
</MenuItem> |
||||
</Menu> |
||||
</div> |
||||
</div> |
||||
); |
||||
} |
||||
} |
||||
|
||||
export default connect((state) => ({ chatInfo: state.chat.info }), {})(Header); |
@ -0,0 +1,31 @@ |
||||
@media screen and (max-width: 4500px) { |
||||
.chat-header { |
||||
height: 60px; |
||||
width: 100%; |
||||
justify-content: space-between; |
||||
align-items: flex-end; |
||||
padding: 5px 5px 5px; |
||||
position: relative; |
||||
background-color: #128c7e; |
||||
&__info { |
||||
text-align: right; |
||||
|
||||
& h1 { |
||||
font-size: 16px; |
||||
color: white; |
||||
margin: 0px; |
||||
margin-bottom: 5px; |
||||
font-weight: 600; |
||||
} |
||||
& span { |
||||
color: white; |
||||
font-size: 11px; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
.MuiMenu-paper { |
||||
background: #00cba2; //rgb(63, 71, 67) !important; |
||||
color: black !important; |
||||
} |
@ -0,0 +1,90 @@ |
||||
import React, { Component } from 'react'; |
||||
|
||||
import './index.scss'; |
||||
export default class FileTabs extends Component { |
||||
constructor(props) { |
||||
super(props); |
||||
this.state = { |
||||
type: 'media', |
||||
}; |
||||
} |
||||
|
||||
setType = (val) => { |
||||
this.setState({ |
||||
type: val, |
||||
}); |
||||
}; |
||||
render() { |
||||
return ( |
||||
<div className="file-tabs d-flex flex-column"> |
||||
<div className="file-tabs__types d-flex"> |
||||
<li |
||||
style={{ |
||||
borderBottom: this.state.type === 'media' ? '2px solid rgb(13, 84, 150)' : 'none', |
||||
}} |
||||
onClick={() => this.setType('media')} |
||||
> |
||||
Media |
||||
</li> |
||||
<li |
||||
style={{ |
||||
borderBottom: this.state.type === 'file' ? '2px solid rgb(13, 84, 150)' : 'none', |
||||
}} |
||||
onClick={() => this.setType('file')} |
||||
> |
||||
Files |
||||
</li> |
||||
<li |
||||
style={{ |
||||
borderBottom: this.state.type === 'link' ? '2px solid rgb(13, 84, 150)' : 'none', |
||||
}} |
||||
onClick={() => this.setType('link')} |
||||
> |
||||
Links |
||||
</li> |
||||
<li |
||||
style={{ |
||||
borderBottom: this.state.type === 'music' ? '2px solid rgb(13, 84, 150)' : 'none', |
||||
}} |
||||
onClick={() => this.setType('music')} |
||||
> |
||||
Music |
||||
</li> |
||||
<li |
||||
style={{ |
||||
borderBottom: this.state.type === 'voice' ? '2px solid rgb(13, 84, 150)' : 'none', |
||||
}} |
||||
onClick={() => this.setType('voice')} |
||||
> |
||||
Voice |
||||
</li> |
||||
</div> |
||||
{this.state.type === 'media' ? ( |
||||
<div className="file-tabs__media d-flex flex-wrap"> |
||||
<div className="file-tabs__media--item"></div> |
||||
<div className="file-tabs__media--item"></div> |
||||
<div className="file-tabs__media--item"></div> |
||||
<div className="file-tabs__media--item"></div> |
||||
<div className="file-tabs__media--item"></div> |
||||
<div className="file-tabs__media--item"></div> |
||||
<div className="file-tabs__media--item"></div> |
||||
<div className="file-tabs__media--item"></div> |
||||
<div className="file-tabs__media--item"></div> |
||||
<div className="file-tabs__media--item"></div> |
||||
<div className="file-tabs__media--item"></div> |
||||
<div className="file-tabs__media--item"></div> |
||||
<div className="file-tabs__media--item"></div> |
||||
<div className="file-tabs__media--item"></div> |
||||
<div className="file-tabs__media--item"></div> |
||||
<div className="file-tabs__media--item"></div> |
||||
<div className="file-tabs__media--item"></div> |
||||
<div className="file-tabs__media--item"></div> |
||||
<div className="file-tabs__media--item"></div> |
||||
<div className="file-tabs__media--item"></div> |
||||
<div className="file-tabs__media--item"></div> |
||||
</div> |
||||
) : null} |
||||
</div> |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,36 @@ |
||||
@media screen and (max-width: 4500px) { |
||||
.file-tabs { |
||||
width: 100%; |
||||
height: 100%; |
||||
position: relative; |
||||
direction: rtl; |
||||
position: sticky; |
||||
top: 0-5px; |
||||
&__types { |
||||
width: 100%; |
||||
justify-content: space-between; |
||||
align-items: center; |
||||
padding: 3px calc((100vw - 310px) / 2) 3px calc((100vw - 310px) / 2); |
||||
direction: ltr; |
||||
border-top: 8px solid rgb(13, 84, 150); |
||||
margin-top: 20px; |
||||
background-color: #128c7e; |
||||
& li { |
||||
font-size: 16px; |
||||
list-style: none; |
||||
font-weight: 600; |
||||
} |
||||
} |
||||
&__media { |
||||
width: 100%; |
||||
overflow-y: scroll; |
||||
padding-bottom: 52px; |
||||
&--item { |
||||
width: calc(100% / 3); |
||||
height: calc(100vw / 3); |
||||
border: 2px solid rgb(200, 183, 232); |
||||
background-color: red; |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,29 @@ |
||||
import React from 'react'; |
||||
import Carousel from 'react-elastic-carousel'; |
||||
import CloseIcon from '@material-ui/icons/Close'; |
||||
|
||||
import './index.scss'; |
||||
|
||||
export default function Gallery(props) { |
||||
// const [state,setState] = useState({
|
||||
|
||||
// });
|
||||
return ( |
||||
<div className="gallery-modal d-flex justify-content align-items-center"> |
||||
<div onClick={() => props.onClick(null)}> |
||||
<CloseIcon |
||||
style={{ color: 'red', width: 46, height: 46, position: 'absolute', right: 5, top: 20 }} |
||||
/> |
||||
</div> |
||||
<Carousel itemsToShow={1} isRTL={true} enableTilt={false} showArrows={true}> |
||||
{props.data.map((item, i) => ( |
||||
<Image item={item} key={i} /> |
||||
))} |
||||
</Carousel> |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
const Image = (props) => { |
||||
return <img style={{ maxHeight: '50%', maxWidth: '100vw' }} src={props.item.src} alt="" />; |
||||
}; |
@ -0,0 +1,64 @@ |
||||
@media screen and (max-width: 4500px) { |
||||
.gallery-modal { |
||||
width: 100%; |
||||
height: 100%; |
||||
background-color: #383737f1; |
||||
position: absolute; |
||||
left: 0; |
||||
top: 0; |
||||
z-index: 700; |
||||
.rec-carousel-wrapper { |
||||
position: relative; |
||||
bottom: 70px; |
||||
} |
||||
.rec-arrow { |
||||
position: absolute; |
||||
background: none; |
||||
box-shadow: none; |
||||
&:hover:enabled { |
||||
background: none; |
||||
box-shadow: none; |
||||
color: black; |
||||
} |
||||
} |
||||
.rec-arrow-right { |
||||
top: calc(50% - 20px); |
||||
left: 0px; |
||||
z-index: 800; |
||||
} |
||||
.rec-arrow-left { |
||||
top: calc(50% - 20px); |
||||
right: 0px; |
||||
z-index: 800; |
||||
} |
||||
.rec-pagination { |
||||
// display:none; |
||||
z-index: 600; |
||||
position: absolute; |
||||
top: auto; |
||||
bottom: -30px !important; |
||||
// left: calc(50% - 20px) !important; |
||||
& button { |
||||
background-color: grey; |
||||
margin: 2px !important; |
||||
box-shadow: 0px 0px 0px rgba(255, 255, 255, 1); |
||||
} |
||||
} |
||||
} |
||||
.rec-dot_active { |
||||
background-color: white !important; |
||||
color: white !important; |
||||
// box-shadow: 0 0 1px rgba(255, 255, 255, 1) !important; |
||||
} |
||||
|
||||
.Carousel-button-10 { |
||||
background-color: white !important; |
||||
} |
||||
.rec-pagination { |
||||
img { |
||||
// width: 100vw; |
||||
// max-height: 200px; |
||||
// height: 100vh; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,60 @@ |
||||
import React, { Component } from 'react'; |
||||
import ChatIcon from '@material-ui/icons/Chat'; |
||||
import FileTabs from './FileTabs'; |
||||
import Gallery from './Gallery'; |
||||
import user2 from '../../../assets/images/user2.png'; |
||||
|
||||
import './index.scss'; |
||||
export default class ChatInfo extends Component { |
||||
constructor(props) { |
||||
super(props); |
||||
this.state = { |
||||
gallery: null, |
||||
user: { |
||||
profileImages: [ |
||||
{ id: 0, src: user2 }, |
||||
{ id: 1, src: user2 }, |
||||
{ id: 2, src: user2 }, |
||||
{ id: 3, src: user2 }, |
||||
], |
||||
name: 'محسن محمدی', |
||||
lastAct: '2 دقیقه پیش آنلاین بوده', |
||||
bio: 'لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است. چاپگرها و متون بلکه روزنامه و مجله در ستون و سطرآنچنان که لازم است و برای شرایط فعلی تکنولوژی مورد نیاز و کاربردهای متنوع با هدف بهبود ابزارهای کاربردی می باشد. کتابهای زیادی در شصت و سه درصد گذشته، حال و آینده شناخت فراوان جامعه و متخصصان را می طلبد ', |
||||
}, |
||||
}; |
||||
} |
||||
|
||||
setGallery = (val) => { |
||||
this.setState({ |
||||
gallery: val, |
||||
}); |
||||
}; |
||||
|
||||
render() { |
||||
const { user, gallery } = this.state; |
||||
return ( |
||||
<div className="chat-info d-flex flex-column"> |
||||
<div className="chat-info__image d-flex"> |
||||
<img src={user.profileImages[0].src} onClick={() => this.setGallery('profile')} alt="" /> |
||||
<div className="chat-info__image--float d-flex"> |
||||
<ChatIcon style={{ color: 'white', width: 46, height: 46 }} /> |
||||
</div> |
||||
</div> |
||||
<div className="chat-info__box d-flex flex-column"> |
||||
<div className="chat-info__box--status d-flex flex-column"> |
||||
<h1>{user.name}</h1> |
||||
<p>{user.lastAct}</p> |
||||
</div> |
||||
<div className="chat-info__box--description d-flex"> |
||||
<p>{user.bio}</p> |
||||
</div> |
||||
<FileTabs onClick={this.setGallery} /> |
||||
</div> |
||||
{gallery === 'profile' ? ( |
||||
<Gallery data={user.profileImages} onClick={this.setGallery} /> |
||||
) : null} |
||||
{gallery === 'media' ? <Gallery data={null} /> : null} |
||||
</div> |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,82 @@ |
||||
@media screen and (max-width: 4500px) { |
||||
.chat-info { |
||||
width: 100%; |
||||
min-height: 100vh; |
||||
position: relative; |
||||
direction: rtl; |
||||
&__image { |
||||
width: 100%; |
||||
background-color: rgb(82, 82, 82); |
||||
justify-content: center; |
||||
align-items: center; |
||||
height: 290px; |
||||
position: relative; |
||||
& img { |
||||
width: 100%; |
||||
height: 100%; |
||||
object-fit: cover; |
||||
} |
||||
&--float { |
||||
position: absolute; |
||||
width: 65px; |
||||
height: 65px; |
||||
bottom: -30px; |
||||
right: 40px; |
||||
background : #128c7e; |
||||
padding: 5px; |
||||
border-radius: 100px; |
||||
z-index: 400; |
||||
justify-content: center; |
||||
align-items: center; |
||||
box-shadow: 0px 0px 50px rgba(36, 0, 72, 0.8); |
||||
} |
||||
} |
||||
&__box { |
||||
width: 100%; |
||||
z-index: 100; |
||||
height: calc(100% - 250px); |
||||
background-color: #0ab8a3; |
||||
direction: rtl; |
||||
padding: 20px 0px 0px 0px; |
||||
position: sticky; |
||||
// transform: translateY(-40px); |
||||
//top: -150px; |
||||
overflow-y: scroll; |
||||
overflow-x: hidden; |
||||
z-index: 200; |
||||
&::-webkit-scrollbar { |
||||
display: none; |
||||
} |
||||
&--status { |
||||
width: 100%; |
||||
align-items: flex-end; |
||||
padding: 0px 30px; |
||||
h1 { |
||||
font-size: 19px; |
||||
margin: 0; |
||||
letter-spacing: -1px; |
||||
font-family: IRANSansNumeralLight; |
||||
color: black; |
||||
font-weight: 100; |
||||
} |
||||
p { |
||||
font-size: 14px; |
||||
color: rgb(61, 61, 61); |
||||
margin: 0px; |
||||
} |
||||
} |
||||
&--description { |
||||
width: 100%; |
||||
justify-content: center; |
||||
align-items: center; |
||||
margin-top: 20px; |
||||
& p { |
||||
width: 100%; |
||||
max-width: 310px; |
||||
text-align: justify; |
||||
font-size: 12px; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,38 @@ |
||||
import React, { Component } from 'react'; |
||||
import Header from './Header'; |
||||
import Body from './Body'; |
||||
import Info from './Info'; |
||||
import './index.scss'; |
||||
export default class ChatRoom extends Component { |
||||
constructor(props) { |
||||
super(props); |
||||
window.nav = this.nav.bind(this); |
||||
this.state = { |
||||
page: 'chatroom', |
||||
}; |
||||
} |
||||
nav = (url) => { |
||||
this.props.history.push(url); |
||||
}; |
||||
setPage = (val) => { |
||||
this.setState({ |
||||
page: val, |
||||
}); |
||||
}; |
||||
render() { |
||||
return ( |
||||
<div className="chat-room d-flex flex-column"> |
||||
<Header page={this.state.page} setPage={this.setPage} /> |
||||
{this.state.page === 'chatroom' ? ( |
||||
<> |
||||
<Body setPage={this.setPage} /> |
||||
</> |
||||
) : ( |
||||
<> |
||||
<Info /> |
||||
</> |
||||
)} |
||||
</div> |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,33 @@ |
||||
@media screen and (max-width: 4500px) { |
||||
.chat-room { |
||||
flex: 1; |
||||
background: rgb(139, 73, 186); |
||||
background: linear-gradient(180deg, rgb(144, 73, 186) 0%, #64cd82 22%); |
||||
height: 100vh; |
||||
overflow: hidden; |
||||
width: 100vw; |
||||
box-shadow: 0px 0px 40px rgba(145, 145, 145, 0.8); |
||||
&__status { |
||||
border-top-left-radius: 40px; |
||||
border-top-right-radius: 40px; |
||||
height: 150px; |
||||
background: rgb(106, 103, 250); |
||||
position: relative; |
||||
align-items: center; |
||||
justify-content: flex-start; |
||||
h1 { |
||||
font-size: 18px; |
||||
margin: 0; |
||||
letter-spacing: -1px; |
||||
font-family: IRANSansNumeralLight; |
||||
color: white; |
||||
font-weight: 100; |
||||
} |
||||
p { |
||||
font-size: 12px; |
||||
color: white; |
||||
margin: 0px; |
||||
} |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue