audio recorder mostly OK
This commit is contained in:
+3
-7
@@ -1450,7 +1450,8 @@ img.channel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#send-message-panel .material-icons {
|
#send-message-panel .material-icons {
|
||||||
min-width: 1.75rem;
|
min-width: 2rem;
|
||||||
|
font-size: 1.6rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
#send-message-panel .material-icons.secondary, .material-icons.disabled {
|
#send-message-panel .material-icons.secondary, .material-icons.disabled {
|
||||||
@@ -2641,6 +2642,7 @@ ul.tabbar li.active > a {
|
|||||||
|
|
||||||
.audio {
|
.audio {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
margin-left: auto;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex: none;
|
flex: none;
|
||||||
background-color: #eee;
|
background-color: #eee;
|
||||||
@@ -2649,7 +2651,6 @@ ul.tabbar li.active > a {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.audio canvas.visualiser {
|
.audio canvas.visualiser {
|
||||||
padding-left: 1.3rem;
|
|
||||||
width: 12rem;
|
width: 12rem;
|
||||||
height: 2.6rem;
|
height: 2.6rem;
|
||||||
}
|
}
|
||||||
@@ -2658,11 +2659,6 @@ ul.tabbar li.active > a {
|
|||||||
font-size: large;
|
font-size: large;
|
||||||
}
|
}
|
||||||
|
|
||||||
.audio .material-icons {
|
|
||||||
font-size: 2rem;
|
|
||||||
padding: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Mobile styles.
|
Mobile styles.
|
||||||
*/
|
*/
|
||||||
|
|||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -4,14 +4,10 @@ import { FormattedMessage } from 'react-intl';
|
|||||||
|
|
||||||
import Tinode from 'tinode-sdk';
|
import Tinode from 'tinode-sdk';
|
||||||
|
|
||||||
import AudioRecorder from '../widgets/audio-recorder.jsx';
|
|
||||||
|
|
||||||
import { APP_NAME } from '../config.js';
|
import { APP_NAME } from '../config.js';
|
||||||
|
|
||||||
export default class LogoView extends React.PureComponent {
|
export default class LogoView extends React.PureComponent {
|
||||||
render() {
|
render() {
|
||||||
return <AudioRecorder />
|
|
||||||
|
|
||||||
const version = APP_NAME + ' (' + Tinode.getLibrary() + ')';
|
const version = APP_NAME + ' (' + Tinode.getLibrary() + ')';
|
||||||
return (
|
return (
|
||||||
<div id="dummy-view">
|
<div id="dummy-view">
|
||||||
|
|||||||
@@ -190,7 +190,7 @@ class MessagesView extends React.Component {
|
|||||||
if (topic && ((this.state.topic != prevState.topic) || !prevProps.ready)) {
|
if (topic && ((this.state.topic != prevState.topic) || !prevProps.ready)) {
|
||||||
if (topic._new) {
|
if (topic._new) {
|
||||||
console.log('Fetching new topic description');
|
console.log('Fetching new topic description');
|
||||||
topic.getMeta(topic.startMetaQuery().withDesc().build());
|
// topic.getMeta(topic.startMetaQuery().withDesc().build());
|
||||||
} else {
|
} else {
|
||||||
this.subscribe(topic);
|
this.subscribe(topic);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,45 +7,48 @@ import { secondsToTime } from '../lib/strformat';
|
|||||||
// FFT resolution.
|
// FFT resolution.
|
||||||
const BUFFER_SIZE = 256;
|
const BUFFER_SIZE = 256;
|
||||||
// Thickness of a visualization bar.
|
// Thickness of a visualization bar.
|
||||||
const LINE_WIDTH = 5;
|
const LINE_WIDTH = 4;
|
||||||
// Spacing between two visualization bars.
|
// Spacing between two visualization bars.
|
||||||
const SPACING = 2;
|
const SPACING = 2;
|
||||||
// Duration represented by one visualization bar.
|
// Duration represented by one visualization bar.
|
||||||
const MILLIS_PER_BAR = 100;
|
const MILLIS_PER_BAR = 100;
|
||||||
// Color of histogram bars
|
// Color of histogram bars
|
||||||
const BAR_COLOR = '#8fbed6';
|
const BAR_COLOR = '#8fbed6';
|
||||||
|
// Scaling for visualization bars.
|
||||||
|
const BAR_SCALE = 96.0;
|
||||||
// Background color
|
// Background color
|
||||||
const BKG_COLOR = '#eeeeee';
|
const BKG_COLOR = '#eeeeee';
|
||||||
|
// Minimum duration of a recording in milliseconds.
|
||||||
|
const MIN_DURATION = 200;
|
||||||
|
// Maximum duration of a recording in milliseconds (10 min).
|
||||||
|
const MAX_DURATION = 600000;
|
||||||
|
|
||||||
export default class AudioRecorder extends React.PureComponent {
|
export default class AudioRecorder extends React.PureComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
const enabled = !!navigator.mediaDevices.getUserMedia;
|
|
||||||
this.state = {
|
this.state = {
|
||||||
enabled: enabled,
|
enabled: true,
|
||||||
audioRecord: null,
|
audioRecord: null,
|
||||||
recording: false,
|
recording: true,
|
||||||
paused: false,
|
paused: false,
|
||||||
duration: '0:00'
|
duration: '0:00'
|
||||||
};
|
};
|
||||||
|
|
||||||
this.visualize = this.visualize.bind(this);
|
this.visualize = this.visualize.bind(this);
|
||||||
this.initMediaRecording = this.initMediaRecording.bind(this);
|
this.initMediaRecording = this.initMediaRecording.bind(this);
|
||||||
this.handleStart = this.handleStart.bind(this);
|
this.handleResume = this.handleResume.bind(this);
|
||||||
this.handleStop = this.handleStop.bind(this);
|
this.handlePause = this.handlePause.bind(this);
|
||||||
this.handleDelete = this.handleDelete.bind(this);
|
this.handleDelete = this.handleDelete.bind(this);
|
||||||
|
this.handleDone = this.handleDone.bind(this);
|
||||||
|
|
||||||
this.durationMillis = 0;
|
this.durationMillis = 0;
|
||||||
this.startedOn = null;
|
this.startedOn = null;
|
||||||
|
this.viewBuffer = [];
|
||||||
this.canvasRef = React.createRef();
|
this.canvasRef = React.createRef();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
if (!this.state.enabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.canvasContext = this.canvasRef.current.getContext('2d');
|
this.canvasContext = this.canvasRef.current.getContext('2d');
|
||||||
this.canvasContext.lineCap = 'round';
|
this.canvasContext.lineCap = 'round';
|
||||||
// To reduce line blurring.
|
// To reduce line blurring.
|
||||||
@@ -59,6 +62,13 @@ export default class AudioRecorder extends React.PureComponent {
|
|||||||
this.analyser = null;
|
this.analyser = null;
|
||||||
|
|
||||||
this.audioChunks = [];
|
this.audioChunks = [];
|
||||||
|
|
||||||
|
// Start recorder right away.
|
||||||
|
try {
|
||||||
|
navigator.mediaDevices.getUserMedia({audio: true, video: false}).then(this.initMediaRecording, this.props.onError);
|
||||||
|
} catch (err) {
|
||||||
|
this.props.onError(err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw amplitude of sound.
|
// Draw amplitude of sound.
|
||||||
@@ -66,25 +76,30 @@ export default class AudioRecorder extends React.PureComponent {
|
|||||||
const pcmData = new Uint8Array(this.analyser.frequencyBinCount);
|
const pcmData = new Uint8Array(this.analyser.frequencyBinCount);
|
||||||
const width = this.canvasRef.current.width;
|
const width = this.canvasRef.current.width;
|
||||||
const height = this.canvasRef.current.height;
|
const height = this.canvasRef.current.height;
|
||||||
const viewBuffer = [];
|
|
||||||
const viewLength = (width / (LINE_WIDTH + SPACING)) | 0;
|
const viewLength = (width / (LINE_WIDTH + SPACING)) | 0;
|
||||||
const viewDuration = MILLIS_PER_BAR * viewLength;
|
const viewDuration = MILLIS_PER_BAR * viewLength;
|
||||||
|
|
||||||
// Take each N-th sample:
|
|
||||||
const samples = (0.1 * this.sampleRate) | 0;
|
|
||||||
|
|
||||||
this.canvasContext.fillStyle = BKG_COLOR;
|
this.canvasContext.fillStyle = BKG_COLOR;
|
||||||
this.canvasContext.lineWidth = LINE_WIDTH;
|
this.canvasContext.lineWidth = LINE_WIDTH;
|
||||||
this.canvasContext.strokeStyle = BAR_COLOR;
|
this.canvasContext.strokeStyle = BAR_COLOR;
|
||||||
|
|
||||||
let prevBarCount = 0;
|
let prevBarCount = 0;
|
||||||
const drawFrame = () => {
|
const drawFrame = () => {
|
||||||
const duration = this.durationMillis + Date.now() - this.startedOn;
|
if (!this.startedOn) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.requestAnimationFrame(drawFrame);
|
||||||
|
|
||||||
|
const duration = this.durationMillis + (Date.now() - this.startedOn);
|
||||||
// Update record length timer.
|
// Update record length timer.
|
||||||
this.setState({duration: secondsToTime(duration / 1000)});
|
this.setState({duration: secondsToTime(duration / 1000)});
|
||||||
|
|
||||||
if (this.state.recording) {
|
// Check if record is too long.
|
||||||
window.requestAnimationFrame(drawFrame);
|
if (duration > MAX_DURATION) {
|
||||||
|
this.mediaRecorder.pause();
|
||||||
|
this.durationMillis += Date.now() - this.startedOn;
|
||||||
|
this.startedOn = null;
|
||||||
|
this.setState({enabled: false, recording: false, duration: secondsToTime(this.durationMillis / 1000)});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw histogram.
|
// Draw histogram.
|
||||||
@@ -105,10 +120,10 @@ export default class AudioRecorder extends React.PureComponent {
|
|||||||
if (prevBarCount != barCount) {
|
if (prevBarCount != barCount) {
|
||||||
prevBarCount = barCount;
|
prevBarCount = barCount;
|
||||||
// Add new amplitude visualization bar.
|
// Add new amplitude visualization bar.
|
||||||
viewBuffer.push(volume);
|
this.viewBuffer.push(volume);
|
||||||
if (viewBuffer.length > viewLength) {
|
if (this.viewBuffer.length > viewLength) {
|
||||||
// Keep at most 'viewLength' amplitude bars.
|
// Keep at most 'viewLength' amplitude bars.
|
||||||
viewBuffer.shift();
|
this.viewBuffer.shift();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,9 +132,9 @@ export default class AudioRecorder extends React.PureComponent {
|
|||||||
|
|
||||||
// Draw amplitude bars.
|
// Draw amplitude bars.
|
||||||
this.canvasContext.beginPath();
|
this.canvasContext.beginPath();
|
||||||
for (let i = 0; i < viewBuffer.length; i++) {
|
for (let i = 0; i < this.viewBuffer.length; i++) {
|
||||||
let x = i * (LINE_WIDTH + SPACING) - dx;
|
let x = i * (LINE_WIDTH + SPACING) - dx;
|
||||||
let y = viewBuffer[i] / 64 * height;
|
let y = this.viewBuffer[i] / BAR_SCALE * height;
|
||||||
|
|
||||||
this.canvasContext.moveTo(x, (height - y) * 0.5);
|
this.canvasContext.moveTo(x, (height - y) * 0.5);
|
||||||
this.canvasContext.lineTo(x, height * 0.5 + y * 0.5);
|
this.canvasContext.lineTo(x, height * 0.5 + y * 0.5);
|
||||||
@@ -131,29 +146,43 @@ export default class AudioRecorder extends React.PureComponent {
|
|||||||
drawFrame();
|
drawFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
handleStop() {
|
handlePause(e) {
|
||||||
|
e.preventDefault();
|
||||||
this.mediaRecorder.pause();
|
this.mediaRecorder.pause();
|
||||||
this.durationMillis += Date.now() - this.startedOn;
|
this.durationMillis += Date.now() - this.startedOn;
|
||||||
|
this.startedOn = null;
|
||||||
this.setState({recording: false, duration: secondsToTime(this.durationMillis / 1000)});
|
this.setState({recording: false, duration: secondsToTime(this.durationMillis / 1000)});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleStart() {
|
handleResume(e) {
|
||||||
if (this.mediaRecorder) {
|
e.preventDefault();
|
||||||
|
if (this.state.enabled) {
|
||||||
this.startedOn = Date.now();
|
this.startedOn = Date.now();
|
||||||
this.mediaRecorder.resume();
|
this.mediaRecorder.resume();
|
||||||
this.setState({recording: true}, this.visualize);
|
this.setState({recording: true}, this.visualize);
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
navigator.mediaDevices.getUserMedia({audio: true, video: false}).then(this.initMediaRecording, this.props.onError);
|
|
||||||
} catch (err) {
|
|
||||||
this.props.onError(err);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDelete() {
|
handleDelete(e) {
|
||||||
// Delete current recording.
|
e.preventDefault();
|
||||||
console.log("Delete current cached recording");
|
this.durationMillis = 0;
|
||||||
|
this.startedOn = null;
|
||||||
|
this.mediaRecorder.stop();
|
||||||
|
this.mediaRecorder = null;
|
||||||
|
this.setState({recording: false});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDone(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
this.setState({recording: false});
|
||||||
|
if (this.startedOn) {
|
||||||
|
this.durationMillis += Date.now() - this.startedOn;
|
||||||
|
this.startedOn = null;
|
||||||
|
}
|
||||||
|
// Stop recording and return data.
|
||||||
|
if (this.mediaRecorder) {
|
||||||
|
this.mediaRecorder.stop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
initMediaRecording(stream) {
|
initMediaRecording(stream) {
|
||||||
@@ -169,12 +198,14 @@ export default class AudioRecorder extends React.PureComponent {
|
|||||||
this.audioInput.connect(this.analyser);
|
this.audioInput.connect(this.analyser);
|
||||||
|
|
||||||
this.mediaRecorder.onstop = _ => {
|
this.mediaRecorder.onstop = _ => {
|
||||||
console.log("Data available after MediaRecorder.stop() called.");
|
|
||||||
const blob = new Blob(this.audioChunks, { 'type' : 'audio/mp3; codecs=mp3' });
|
const blob = new Blob(this.audioChunks, { 'type' : 'audio/mp3; codecs=mp3' });
|
||||||
this.audioChunks = [];
|
this.audioChunks = [];
|
||||||
const url = window.URL.createObjectURL(blob);
|
const url = window.URL.createObjectURL(blob);
|
||||||
this.setState({audioRecord: url});
|
if (this.durationMillis > MIN_DURATION) {
|
||||||
console.log("Audio URL:", url);
|
this.props.onFinished(url, this.durationMillis);
|
||||||
|
} else {
|
||||||
|
this.props.onDeleted();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.mediaRecorder.ondataavailable = (e) => {
|
this.mediaRecorder.ondataavailable = (e) => {
|
||||||
@@ -183,25 +214,33 @@ export default class AudioRecorder extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.durationMillis = 0;
|
||||||
this.startedOn = Date.now();
|
this.startedOn = Date.now();
|
||||||
this.mediaRecorder.start();
|
this.mediaRecorder.start();
|
||||||
this.setState({recording: true}, this.visualize);
|
this.visualize();
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (this.state.enabled ?
|
const resumeClass = 'material-icons ' + (this.state.enabled ? 'red' : 'gray');
|
||||||
|
return (
|
||||||
<div className="audio">
|
<div className="audio">
|
||||||
|
<a href="#" onClick={this.handleDelete} title="Delete">
|
||||||
|
<i className="material-icons">delete_outline</i>
|
||||||
|
</a>
|
||||||
<canvas className="visualiser" width="200" height="60" ref={this.canvasRef} />
|
<canvas className="visualiser" width="200" height="60" ref={this.canvasRef} />
|
||||||
<div className="duration">{this.state.duration}</div>
|
<div className="duration">{this.state.duration}</div>
|
||||||
{this.state.recording ?
|
{this.state.recording ?
|
||||||
<a href="#" onClick={this.handleStop} title="Pause">
|
<a href="#" onClick={this.handlePause} title="Pause">
|
||||||
<i className="material-icons">pause_circle_outline</i>
|
<i className="material-icons">pause_circle_outline</i>
|
||||||
</a> :
|
</a> :
|
||||||
<a href="#" onClick={this.handleStart} title="Resume">
|
<a href="#" onClick={this.handleResume} title="Resume">
|
||||||
<i className="material-icons">mic</i>
|
<i className={resumeClass}>fiber_manual_record</i>
|
||||||
</a>
|
</a>
|
||||||
}
|
}
|
||||||
</div> :
|
<a href="#" onClick={this.handleDone} title="Send">
|
||||||
<div>Audio not available</div>);
|
<i className="material-icons">send</i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import React from 'react';
|
|||||||
import { defineMessages, injectIntl } from 'react-intl';
|
import { defineMessages, injectIntl } from 'react-intl';
|
||||||
import { Drafty } from 'tinode-sdk';
|
import { Drafty } from 'tinode-sdk';
|
||||||
|
|
||||||
|
import AudioRecorder from './audio-recorder.jsx';
|
||||||
|
|
||||||
import { KEYPRESS_DELAY } from '../config.js';
|
import { KEYPRESS_DELAY } from '../config.js';
|
||||||
import { filePasted } from '../lib/blob-helpers.js';
|
import { filePasted } from '../lib/blob-helpers.js';
|
||||||
import { replyFormatter } from '../lib/formatters.js';
|
import { replyFormatter } from '../lib/formatters.js';
|
||||||
@@ -42,6 +44,8 @@ class SendMessage extends React.PureComponent {
|
|||||||
this.state = {
|
this.state = {
|
||||||
quote: null,
|
quote: null,
|
||||||
message: '',
|
message: '',
|
||||||
|
audioRec: false,
|
||||||
|
audioEnabled: !!navigator.mediaDevices.getUserMedia,
|
||||||
// Make initial keypress time as if it happened 5001 milliseconds in the past.
|
// Make initial keypress time as if it happened 5001 milliseconds in the past.
|
||||||
keypressTimestamp: new Date().getTime() - KEYPRESS_DELAY - 1
|
keypressTimestamp: new Date().getTime() - KEYPRESS_DELAY - 1
|
||||||
};
|
};
|
||||||
@@ -49,6 +53,7 @@ class SendMessage extends React.PureComponent {
|
|||||||
this.handlePasteEvent = this.handlePasteEvent.bind(this);
|
this.handlePasteEvent = this.handlePasteEvent.bind(this);
|
||||||
this.handleAttachImage = this.handleAttachImage.bind(this);
|
this.handleAttachImage = this.handleAttachImage.bind(this);
|
||||||
this.handleAttachFile = this.handleAttachFile.bind(this);
|
this.handleAttachFile = this.handleAttachFile.bind(this);
|
||||||
|
this.handleAttachAudio = this.handleAttachAudio.bind(this);
|
||||||
this.handleSend = this.handleSend.bind(this);
|
this.handleSend = this.handleSend.bind(this);
|
||||||
this.handleKeyPress = this.handleKeyPress.bind(this);
|
this.handleKeyPress = this.handleKeyPress.bind(this);
|
||||||
this.handleMessageTyping = this.handleMessageTyping.bind(this);
|
this.handleMessageTyping = this.handleMessageTyping.bind(this);
|
||||||
@@ -118,7 +123,6 @@ class SendMessage extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleAttachFile(e) {
|
handleAttachFile(e) {
|
||||||
const {formatMessage} = this.props.intl;
|
|
||||||
if (e.target.files && e.target.files.length > 0) {
|
if (e.target.files && e.target.files.length > 0) {
|
||||||
this.props.onAttachFile(e.target.files[0]);
|
this.props.onAttachFile(e.target.files[0]);
|
||||||
}
|
}
|
||||||
@@ -126,6 +130,12 @@ class SendMessage extends React.PureComponent {
|
|||||||
e.target.value = '';
|
e.target.value = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleAttachAudio(url, duration) {
|
||||||
|
this.setState({audioRec: false});
|
||||||
|
console.log("this.props.onAttachAudio", url, duration);
|
||||||
|
// this.props.onAttachAudio(url, duration);
|
||||||
|
}
|
||||||
|
|
||||||
handleSend(e) {
|
handleSend(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const message = this.state.message.trim();
|
const message = this.state.message.trim();
|
||||||
@@ -137,6 +147,13 @@ class SendMessage extends React.PureComponent {
|
|||||||
|
|
||||||
/* Send on Enter key */
|
/* Send on Enter key */
|
||||||
handleKeyPress(e) {
|
handleKeyPress(e) {
|
||||||
|
if (this.state.audioRec) {
|
||||||
|
// Ignore key presses while audio is being recorded.
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Remove this if you don't want Enter to trigger send
|
// Remove this if you don't want Enter to trigger send
|
||||||
if (e.key === 'Enter') {
|
if (e.key === 'Enter') {
|
||||||
// Have Shift-Enter insert a line break instead
|
// Have Shift-Enter insert a line break instead
|
||||||
@@ -192,7 +209,7 @@ class SendMessage extends React.PureComponent {
|
|||||||
<div id="send-message-panel">
|
<div id="send-message-panel">
|
||||||
{!this.props.disabled ?
|
{!this.props.disabled ?
|
||||||
<>
|
<>
|
||||||
{this.props.onAttachFile ?
|
{this.props.onAttachFile && !this.state.audioRec ?
|
||||||
<>
|
<>
|
||||||
<a href="#" onClick={(e) => {e.preventDefault(); this.attachImage.click();}} title="Add image">
|
<a href="#" onClick={(e) => {e.preventDefault(); this.attachImage.click();}} title="Add image">
|
||||||
<i className="material-icons secondary">photo</i>
|
<i className="material-icons secondary">photo</i>
|
||||||
@@ -205,14 +222,25 @@ class SendMessage extends React.PureComponent {
|
|||||||
null}
|
null}
|
||||||
{this.props.noInput ?
|
{this.props.noInput ?
|
||||||
(this.state.quote ? quote : <div className="hr thin" />) :
|
(this.state.quote ? quote : <div className="hr thin" />) :
|
||||||
<textarea id="sendMessage" placeholder={prompt}
|
(this.state.audioRec ?
|
||||||
value={this.state.message} onChange={this.handleMessageTyping}
|
<AudioRecorder
|
||||||
onKeyPress={this.handleKeyPress}
|
onDeleted={_ => this.setState({audioRec: false})}
|
||||||
ref={(ref) => {this.messageEditArea = ref;}}
|
onFinished={this.handleAttachAudio}/> :
|
||||||
autoFocus />}
|
<textarea id="sendMessage" placeholder={prompt}
|
||||||
<a href="#" onClick={this.handleSend} title="Send">
|
value={this.state.message} onChange={this.handleMessageTyping}
|
||||||
<i className="material-icons">send</i>
|
onKeyPress={this.handleKeyPress}
|
||||||
</a>
|
ref={(ref) => {this.messageEditArea = ref;}}
|
||||||
|
autoFocus />)}
|
||||||
|
{this.state.message || !this.state.audioEnabled ?
|
||||||
|
<a href="#" onClick={this.handleSend} title="Send">
|
||||||
|
<i className="material-icons">send</i>
|
||||||
|
</a> :
|
||||||
|
!this.state.audioRec ?
|
||||||
|
<a href="#" onClick={e => {e.preventDefault(); this.setState({audioRec: true})}} title="Voice">
|
||||||
|
<i className="material-icons">mic</i>
|
||||||
|
</a> :
|
||||||
|
null
|
||||||
|
}
|
||||||
<input type="file" ref={(ref) => {this.attachFile = ref;}}
|
<input type="file" ref={(ref) => {this.attachFile = ref;}}
|
||||||
onChange={this.handleAttachFile} style={{display: 'none'}} />
|
onChange={this.handleAttachFile} style={{display: 'none'}} />
|
||||||
<input type="file" ref={(ref) => {this.attachImage = ref;}} accept="image/*"
|
<input type="file" ref={(ref) => {this.attachImage = ref;}} accept="image/*"
|
||||||
|
|||||||
+143
-67
@@ -5227,17 +5227,14 @@ __webpack_require__.r(__webpack_exports__);
|
|||||||
/* harmony import */ var react_intl__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react_intl__WEBPACK_IMPORTED_MODULE_1__);
|
/* harmony import */ var react_intl__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react_intl__WEBPACK_IMPORTED_MODULE_1__);
|
||||||
/* harmony import */ var tinode_sdk__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! tinode-sdk */ "tinode-sdk");
|
/* harmony import */ var tinode_sdk__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! tinode-sdk */ "tinode-sdk");
|
||||||
/* harmony import */ var tinode_sdk__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(tinode_sdk__WEBPACK_IMPORTED_MODULE_2__);
|
/* harmony import */ var tinode_sdk__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(tinode_sdk__WEBPACK_IMPORTED_MODULE_2__);
|
||||||
/* harmony import */ var _widgets_audio_recorder_jsx__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../widgets/audio-recorder.jsx */ "./src/widgets/audio-recorder.jsx");
|
/* harmony import */ var _config_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../config.js */ "./src/config.js");
|
||||||
/* harmony import */ var _config_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../config.js */ "./src/config.js");
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class LogoView extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureComponent) {
|
class LogoView extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureComponent) {
|
||||||
render() {
|
render() {
|
||||||
return react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_widgets_audio_recorder_jsx__WEBPACK_IMPORTED_MODULE_3__["default"], null);
|
const version = _config_js__WEBPACK_IMPORTED_MODULE_3__.APP_NAME + ' (' + tinode_sdk__WEBPACK_IMPORTED_MODULE_2___default().getLibrary() + ')';
|
||||||
const version = _config_js__WEBPACK_IMPORTED_MODULE_4__.APP_NAME + ' (' + tinode_sdk__WEBPACK_IMPORTED_MODULE_2___default().getLibrary() + ')';
|
|
||||||
return react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
|
return react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
|
||||||
id: "dummy-view"
|
id: "dummy-view"
|
||||||
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", null, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("a", {
|
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", null, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("a", {
|
||||||
@@ -5493,7 +5490,6 @@ class MessagesView extends (react__WEBPACK_IMPORTED_MODULE_0___default().Compone
|
|||||||
if (topic && (this.state.topic != prevState.topic || !prevProps.ready)) {
|
if (topic && (this.state.topic != prevState.topic || !prevProps.ready)) {
|
||||||
if (topic._new) {
|
if (topic._new) {
|
||||||
console.log('Fetching new topic description');
|
console.log('Fetching new topic description');
|
||||||
topic.getMeta(topic.startMetaQuery().withDesc().build());
|
|
||||||
} else {
|
} else {
|
||||||
this.subscribe(topic);
|
this.subscribe(topic);
|
||||||
}
|
}
|
||||||
@@ -9593,37 +9589,37 @@ __webpack_require__.r(__webpack_exports__);
|
|||||||
|
|
||||||
|
|
||||||
const BUFFER_SIZE = 256;
|
const BUFFER_SIZE = 256;
|
||||||
const LINE_WIDTH = 5;
|
const LINE_WIDTH = 4;
|
||||||
const SPACING = 2;
|
const SPACING = 2;
|
||||||
const MILLIS_PER_BAR = 100;
|
const MILLIS_PER_BAR = 100;
|
||||||
const BAR_COLOR = '#8fbed6';
|
const BAR_COLOR = '#8fbed6';
|
||||||
|
const BAR_SCALE = 96.0;
|
||||||
const BKG_COLOR = '#eeeeee';
|
const BKG_COLOR = '#eeeeee';
|
||||||
|
const MIN_DURATION = 200;
|
||||||
|
const MAX_DURATION = 5000;
|
||||||
class AudioRecorder extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureComponent) {
|
class AudioRecorder extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureComponent) {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
const enabled = !!navigator.mediaDevices.getUserMedia;
|
|
||||||
this.state = {
|
this.state = {
|
||||||
enabled: enabled,
|
enabled: true,
|
||||||
audioRecord: null,
|
audioRecord: null,
|
||||||
recording: false,
|
recording: true,
|
||||||
paused: false,
|
paused: false,
|
||||||
duration: '0:00'
|
duration: '0:00'
|
||||||
};
|
};
|
||||||
this.visualize = this.visualize.bind(this);
|
this.visualize = this.visualize.bind(this);
|
||||||
this.initMediaRecording = this.initMediaRecording.bind(this);
|
this.initMediaRecording = this.initMediaRecording.bind(this);
|
||||||
this.handleStart = this.handleStart.bind(this);
|
this.handleResume = this.handleResume.bind(this);
|
||||||
this.handleStop = this.handleStop.bind(this);
|
this.handlePause = this.handlePause.bind(this);
|
||||||
this.handleDelete = this.handleDelete.bind(this);
|
this.handleDelete = this.handleDelete.bind(this);
|
||||||
|
this.handleDone = this.handleDone.bind(this);
|
||||||
this.durationMillis = 0;
|
this.durationMillis = 0;
|
||||||
this.startedOn = null;
|
this.startedOn = null;
|
||||||
|
this.viewBuffer = [];
|
||||||
this.canvasRef = react__WEBPACK_IMPORTED_MODULE_0___default().createRef();
|
this.canvasRef = react__WEBPACK_IMPORTED_MODULE_0___default().createRef();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
if (!this.state.enabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.canvasContext = this.canvasRef.current.getContext('2d');
|
this.canvasContext = this.canvasRef.current.getContext('2d');
|
||||||
this.canvasContext.lineCap = 'round';
|
this.canvasContext.lineCap = 'round';
|
||||||
this.canvasContext.translate(0.5, 0.5);
|
this.canvasContext.translate(0.5, 0.5);
|
||||||
@@ -9634,29 +9630,48 @@ class AudioRecorder extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureCo
|
|||||||
this.audioInput = null;
|
this.audioInput = null;
|
||||||
this.analyser = null;
|
this.analyser = null;
|
||||||
this.audioChunks = [];
|
this.audioChunks = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
navigator.mediaDevices.getUserMedia({
|
||||||
|
audio: true,
|
||||||
|
video: false
|
||||||
|
}).then(this.initMediaRecording, this.props.onError);
|
||||||
|
} catch (err) {
|
||||||
|
this.props.onError(err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
visualize() {
|
visualize() {
|
||||||
const pcmData = new Uint8Array(this.analyser.frequencyBinCount);
|
const pcmData = new Uint8Array(this.analyser.frequencyBinCount);
|
||||||
const width = this.canvasRef.current.width;
|
const width = this.canvasRef.current.width;
|
||||||
const height = this.canvasRef.current.height;
|
const height = this.canvasRef.current.height;
|
||||||
const viewBuffer = [];
|
|
||||||
const viewLength = width / (LINE_WIDTH + SPACING) | 0;
|
const viewLength = width / (LINE_WIDTH + SPACING) | 0;
|
||||||
const viewDuration = MILLIS_PER_BAR * viewLength;
|
const viewDuration = MILLIS_PER_BAR * viewLength;
|
||||||
const samples = 0.1 * this.sampleRate | 0;
|
|
||||||
this.canvasContext.fillStyle = BKG_COLOR;
|
this.canvasContext.fillStyle = BKG_COLOR;
|
||||||
this.canvasContext.lineWidth = LINE_WIDTH;
|
this.canvasContext.lineWidth = LINE_WIDTH;
|
||||||
this.canvasContext.strokeStyle = BAR_COLOR;
|
this.canvasContext.strokeStyle = BAR_COLOR;
|
||||||
let prevBarCount = 0;
|
let prevBarCount = 0;
|
||||||
|
|
||||||
const drawFrame = () => {
|
const drawFrame = () => {
|
||||||
const duration = this.durationMillis + Date.now() - this.startedOn;
|
if (!this.startedOn) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.requestAnimationFrame(drawFrame);
|
||||||
|
const duration = this.durationMillis + (Date.now() - this.startedOn);
|
||||||
this.setState({
|
this.setState({
|
||||||
duration: (0,_lib_strformat__WEBPACK_IMPORTED_MODULE_1__.secondsToTime)(duration / 1000)
|
duration: (0,_lib_strformat__WEBPACK_IMPORTED_MODULE_1__.secondsToTime)(duration / 1000)
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.state.recording) {
|
if (duration > MAX_DURATION) {
|
||||||
window.requestAnimationFrame(drawFrame);
|
this.mediaRecorder.pause();
|
||||||
|
this.durationMillis += Date.now() - this.startedOn;
|
||||||
|
this.startedOn = null;
|
||||||
|
this.setState({
|
||||||
|
enabled: false,
|
||||||
|
recording: false,
|
||||||
|
duration: (0,_lib_strformat__WEBPACK_IMPORTED_MODULE_1__.secondsToTime)(this.durationMillis / 1000)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.analyser.getByteTimeDomainData(pcmData);
|
this.analyser.getByteTimeDomainData(pcmData);
|
||||||
@@ -9672,19 +9687,19 @@ class AudioRecorder extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureCo
|
|||||||
|
|
||||||
if (prevBarCount != barCount) {
|
if (prevBarCount != barCount) {
|
||||||
prevBarCount = barCount;
|
prevBarCount = barCount;
|
||||||
viewBuffer.push(volume);
|
this.viewBuffer.push(volume);
|
||||||
|
|
||||||
if (viewBuffer.length > viewLength) {
|
if (this.viewBuffer.length > viewLength) {
|
||||||
viewBuffer.shift();
|
this.viewBuffer.shift();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.canvasContext.fillRect(0, 0, width, height);
|
this.canvasContext.fillRect(0, 0, width, height);
|
||||||
this.canvasContext.beginPath();
|
this.canvasContext.beginPath();
|
||||||
|
|
||||||
for (let i = 0; i < viewBuffer.length; i++) {
|
for (let i = 0; i < this.viewBuffer.length; i++) {
|
||||||
let x = i * (LINE_WIDTH + SPACING) - dx;
|
let x = i * (LINE_WIDTH + SPACING) - dx;
|
||||||
let y = viewBuffer[i] / 64 * height;
|
let y = this.viewBuffer[i] / BAR_SCALE * height;
|
||||||
this.canvasContext.moveTo(x, (height - y) * 0.5);
|
this.canvasContext.moveTo(x, (height - y) * 0.5);
|
||||||
this.canvasContext.lineTo(x, height * 0.5 + y * 0.5);
|
this.canvasContext.lineTo(x, height * 0.5 + y * 0.5);
|
||||||
}
|
}
|
||||||
@@ -9695,36 +9710,54 @@ class AudioRecorder extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureCo
|
|||||||
drawFrame();
|
drawFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
handleStop() {
|
handlePause(e) {
|
||||||
|
e.preventDefault();
|
||||||
this.mediaRecorder.pause();
|
this.mediaRecorder.pause();
|
||||||
this.durationMillis += Date.now() - this.startedOn;
|
this.durationMillis += Date.now() - this.startedOn;
|
||||||
|
this.startedOn = null;
|
||||||
this.setState({
|
this.setState({
|
||||||
recording: false,
|
recording: false,
|
||||||
duration: (0,_lib_strformat__WEBPACK_IMPORTED_MODULE_1__.secondsToTime)(this.durationMillis / 1000)
|
duration: (0,_lib_strformat__WEBPACK_IMPORTED_MODULE_1__.secondsToTime)(this.durationMillis / 1000)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleStart() {
|
handleResume(e) {
|
||||||
if (this.mediaRecorder) {
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (this.state.enabled) {
|
||||||
this.startedOn = Date.now();
|
this.startedOn = Date.now();
|
||||||
this.mediaRecorder.resume();
|
this.mediaRecorder.resume();
|
||||||
this.setState({
|
this.setState({
|
||||||
recording: true
|
recording: true
|
||||||
}, this.visualize);
|
}, this.visualize);
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
navigator.mediaDevices.getUserMedia({
|
|
||||||
audio: true,
|
|
||||||
video: false
|
|
||||||
}).then(this.initMediaRecording, this.props.onError);
|
|
||||||
} catch (err) {
|
|
||||||
this.props.onError(err);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDelete() {
|
handleDelete(e) {
|
||||||
console.log("Delete current cached recording");
|
e.preventDefault();
|
||||||
|
this.durationMillis = 0;
|
||||||
|
this.startedOn = null;
|
||||||
|
this.mediaRecorder.stop();
|
||||||
|
this.mediaRecorder = null;
|
||||||
|
this.setState({
|
||||||
|
recording: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDone(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
this.setState({
|
||||||
|
recording: false
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.startedOn) {
|
||||||
|
this.durationMillis += Date.now() - this.startedOn;
|
||||||
|
this.startedOn = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.mediaRecorder) {
|
||||||
|
this.mediaRecorder.stop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
initMediaRecording(stream) {
|
initMediaRecording(stream) {
|
||||||
@@ -9738,16 +9771,17 @@ class AudioRecorder extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureCo
|
|||||||
this.audioInput.connect(this.analyser);
|
this.audioInput.connect(this.analyser);
|
||||||
|
|
||||||
this.mediaRecorder.onstop = _ => {
|
this.mediaRecorder.onstop = _ => {
|
||||||
console.log("Data available after MediaRecorder.stop() called.");
|
|
||||||
const blob = new Blob(this.audioChunks, {
|
const blob = new Blob(this.audioChunks, {
|
||||||
'type': 'audio/mp3; codecs=mp3'
|
'type': 'audio/mp3; codecs=mp3'
|
||||||
});
|
});
|
||||||
this.audioChunks = [];
|
this.audioChunks = [];
|
||||||
const url = window.URL.createObjectURL(blob);
|
const url = window.URL.createObjectURL(blob);
|
||||||
this.setState({
|
|
||||||
audioRecord: url
|
if (this.durationMillis > MIN_DURATION) {
|
||||||
});
|
this.props.onFinished(url, this.durationMillis);
|
||||||
console.log("Audio URL:", url);
|
} else {
|
||||||
|
this.props.onDeleted();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.mediaRecorder.ondataavailable = e => {
|
this.mediaRecorder.ondataavailable = e => {
|
||||||
@@ -9756,17 +9790,23 @@ class AudioRecorder extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureCo
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.durationMillis = 0;
|
||||||
this.startedOn = Date.now();
|
this.startedOn = Date.now();
|
||||||
this.mediaRecorder.start();
|
this.mediaRecorder.start();
|
||||||
this.setState({
|
this.visualize();
|
||||||
recording: true
|
|
||||||
}, this.visualize);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return this.state.enabled ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
|
const resumeClass = 'material-icons ' + (this.state.enabled ? 'red' : 'gray');
|
||||||
|
return react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
|
||||||
className: "audio"
|
className: "audio"
|
||||||
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("canvas", {
|
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("a", {
|
||||||
|
href: "#",
|
||||||
|
onClick: this.handleDelete,
|
||||||
|
title: "Delete"
|
||||||
|
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("i", {
|
||||||
|
className: "material-icons"
|
||||||
|
}, "delete_outline")), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("canvas", {
|
||||||
className: "visualiser",
|
className: "visualiser",
|
||||||
width: "200",
|
width: "200",
|
||||||
height: "60",
|
height: "60",
|
||||||
@@ -9775,17 +9815,23 @@ class AudioRecorder extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureCo
|
|||||||
className: "duration"
|
className: "duration"
|
||||||
}, this.state.duration), this.state.recording ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement("a", {
|
}, this.state.duration), this.state.recording ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement("a", {
|
||||||
href: "#",
|
href: "#",
|
||||||
onClick: this.handleStop,
|
onClick: this.handlePause,
|
||||||
title: "Pause"
|
title: "Pause"
|
||||||
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("i", {
|
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("i", {
|
||||||
className: "material-icons"
|
className: "material-icons"
|
||||||
}, "pause_circle_outline")) : react__WEBPACK_IMPORTED_MODULE_0___default().createElement("a", {
|
}, "pause_circle_outline")) : react__WEBPACK_IMPORTED_MODULE_0___default().createElement("a", {
|
||||||
href: "#",
|
href: "#",
|
||||||
onClick: this.handleStart,
|
onClick: this.handleResume,
|
||||||
title: "Resume"
|
title: "Resume"
|
||||||
|
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("i", {
|
||||||
|
className: resumeClass
|
||||||
|
}, "fiber_manual_record")), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("a", {
|
||||||
|
href: "#",
|
||||||
|
onClick: this.handleDone,
|
||||||
|
title: "Send"
|
||||||
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("i", {
|
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("i", {
|
||||||
className: "material-icons"
|
className: "material-icons"
|
||||||
}, "mic"))) : react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", null, "Audio not available");
|
}, "send")));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -14091,9 +14137,11 @@ __webpack_require__.r(__webpack_exports__);
|
|||||||
/* harmony import */ var react_intl__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react_intl__WEBPACK_IMPORTED_MODULE_1__);
|
/* harmony import */ var react_intl__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react_intl__WEBPACK_IMPORTED_MODULE_1__);
|
||||||
/* harmony import */ var tinode_sdk__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! tinode-sdk */ "tinode-sdk");
|
/* harmony import */ var tinode_sdk__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! tinode-sdk */ "tinode-sdk");
|
||||||
/* harmony import */ var tinode_sdk__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(tinode_sdk__WEBPACK_IMPORTED_MODULE_2__);
|
/* harmony import */ var tinode_sdk__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(tinode_sdk__WEBPACK_IMPORTED_MODULE_2__);
|
||||||
/* harmony import */ var _config_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../config.js */ "./src/config.js");
|
/* harmony import */ var _audio_recorder_jsx__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./audio-recorder.jsx */ "./src/widgets/audio-recorder.jsx");
|
||||||
/* harmony import */ var _lib_blob_helpers_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../lib/blob-helpers.js */ "./src/lib/blob-helpers.js");
|
/* harmony import */ var _config_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../config.js */ "./src/config.js");
|
||||||
/* harmony import */ var _lib_formatters_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../lib/formatters.js */ "./src/lib/formatters.js");
|
/* harmony import */ var _lib_blob_helpers_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../lib/blob-helpers.js */ "./src/lib/blob-helpers.js");
|
||||||
|
/* harmony import */ var _lib_formatters_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ../lib/formatters.js */ "./src/lib/formatters.js");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -14156,11 +14204,14 @@ class SendMessage extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureComp
|
|||||||
this.state = {
|
this.state = {
|
||||||
quote: null,
|
quote: null,
|
||||||
message: '',
|
message: '',
|
||||||
keypressTimestamp: new Date().getTime() - _config_js__WEBPACK_IMPORTED_MODULE_3__.KEYPRESS_DELAY - 1
|
audioRec: false,
|
||||||
|
audioEnabled: !!navigator.mediaDevices.getUserMedia,
|
||||||
|
keypressTimestamp: new Date().getTime() - _config_js__WEBPACK_IMPORTED_MODULE_4__.KEYPRESS_DELAY - 1
|
||||||
};
|
};
|
||||||
this.handlePasteEvent = this.handlePasteEvent.bind(this);
|
this.handlePasteEvent = this.handlePasteEvent.bind(this);
|
||||||
this.handleAttachImage = this.handleAttachImage.bind(this);
|
this.handleAttachImage = this.handleAttachImage.bind(this);
|
||||||
this.handleAttachFile = this.handleAttachFile.bind(this);
|
this.handleAttachFile = this.handleAttachFile.bind(this);
|
||||||
|
this.handleAttachAudio = this.handleAttachAudio.bind(this);
|
||||||
this.handleSend = this.handleSend.bind(this);
|
this.handleSend = this.handleSend.bind(this);
|
||||||
this.handleKeyPress = this.handleKeyPress.bind(this);
|
this.handleKeyPress = this.handleKeyPress.bind(this);
|
||||||
this.handleMessageTyping = this.handleMessageTyping.bind(this);
|
this.handleMessageTyping = this.handleMessageTyping.bind(this);
|
||||||
@@ -14197,7 +14248,7 @@ class SendMessage extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureComp
|
|||||||
}
|
}
|
||||||
|
|
||||||
formatReply() {
|
formatReply() {
|
||||||
return this.props.reply ? tinode_sdk__WEBPACK_IMPORTED_MODULE_2__.Drafty.format(this.props.reply.content, _lib_formatters_js__WEBPACK_IMPORTED_MODULE_5__.replyFormatter, {
|
return this.props.reply ? tinode_sdk__WEBPACK_IMPORTED_MODULE_2__.Drafty.format(this.props.reply.content, _lib_formatters_js__WEBPACK_IMPORTED_MODULE_6__.replyFormatter, {
|
||||||
formatMessage: this.props.intl.formatMessage.bind(this.props.intl),
|
formatMessage: this.props.intl.formatMessage.bind(this.props.intl),
|
||||||
authorizeURL: this.props.tinode.authorizeURL.bind(this.props.tinode)
|
authorizeURL: this.props.tinode.authorizeURL.bind(this.props.tinode)
|
||||||
}) : null;
|
}) : null;
|
||||||
@@ -14208,7 +14259,7 @@ class SendMessage extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureComp
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((0,_lib_blob_helpers_js__WEBPACK_IMPORTED_MODULE_4__.filePasted)(e, (bits, mime, width, height, fname) => {
|
if ((0,_lib_blob_helpers_js__WEBPACK_IMPORTED_MODULE_5__.filePasted)(e, (bits, mime, width, height, fname) => {
|
||||||
this.props.onAttachImage(mime, bits, width, height, fname);
|
this.props.onAttachImage(mime, bits, width, height, fname);
|
||||||
}, (mime, bits, fname) => {
|
}, (mime, bits, fname) => {
|
||||||
this.props.onAttachFile(mime, bits, fname);
|
this.props.onAttachFile(mime, bits, fname);
|
||||||
@@ -14226,10 +14277,6 @@ class SendMessage extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureComp
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleAttachFile(e) {
|
handleAttachFile(e) {
|
||||||
const {
|
|
||||||
formatMessage
|
|
||||||
} = this.props.intl;
|
|
||||||
|
|
||||||
if (e.target.files && e.target.files.length > 0) {
|
if (e.target.files && e.target.files.length > 0) {
|
||||||
this.props.onAttachFile(e.target.files[0]);
|
this.props.onAttachFile(e.target.files[0]);
|
||||||
}
|
}
|
||||||
@@ -14237,6 +14284,13 @@ class SendMessage extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureComp
|
|||||||
e.target.value = '';
|
e.target.value = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleAttachAudio(url, duration) {
|
||||||
|
this.setState({
|
||||||
|
audioRec: false
|
||||||
|
});
|
||||||
|
console.log("this.props.onAttachAudio", url, duration);
|
||||||
|
}
|
||||||
|
|
||||||
handleSend(e) {
|
handleSend(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const message = this.state.message.trim();
|
const message = this.state.message.trim();
|
||||||
@@ -14250,6 +14304,12 @@ class SendMessage extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureComp
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleKeyPress(e) {
|
handleKeyPress(e) {
|
||||||
|
if (this.state.audioRec) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (e.key === 'Enter') {
|
if (e.key === 'Enter') {
|
||||||
if (!e.shiftKey) {
|
if (!e.shiftKey) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -14267,7 +14327,7 @@ class SendMessage extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureComp
|
|||||||
if (this.props.onKeyPress) {
|
if (this.props.onKeyPress) {
|
||||||
const now = new Date().getTime();
|
const now = new Date().getTime();
|
||||||
|
|
||||||
if (now - this.state.keypressTimestamp > _config_js__WEBPACK_IMPORTED_MODULE_3__.KEYPRESS_DELAY) {
|
if (now - this.state.keypressTimestamp > _config_js__WEBPACK_IMPORTED_MODULE_4__.KEYPRESS_DELAY) {
|
||||||
this.props.onKeyPress();
|
this.props.onKeyPress();
|
||||||
newState.keypressTimestamp = now;
|
newState.keypressTimestamp = now;
|
||||||
}
|
}
|
||||||
@@ -14308,7 +14368,7 @@ class SendMessage extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureComp
|
|||||||
id: "send-message-wrapper"
|
id: "send-message-wrapper"
|
||||||
}, this.state.quote && !this.props.noInput ? quote : null, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
|
}, this.state.quote && !this.props.noInput ? quote : null, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
|
||||||
id: "send-message-panel"
|
id: "send-message-panel"
|
||||||
}, !this.props.disabled ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement((react__WEBPACK_IMPORTED_MODULE_0___default().Fragment), null, this.props.onAttachFile ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement((react__WEBPACK_IMPORTED_MODULE_0___default().Fragment), null, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("a", {
|
}, !this.props.disabled ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement((react__WEBPACK_IMPORTED_MODULE_0___default().Fragment), null, this.props.onAttachFile && !this.state.audioRec ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement((react__WEBPACK_IMPORTED_MODULE_0___default().Fragment), null, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("a", {
|
||||||
href: "#",
|
href: "#",
|
||||||
onClick: e => {
|
onClick: e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -14328,6 +14388,11 @@ class SendMessage extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureComp
|
|||||||
className: "material-icons secondary"
|
className: "material-icons secondary"
|
||||||
}, "attach_file"))) : null, this.props.noInput ? this.state.quote ? quote : react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
|
}, "attach_file"))) : null, this.props.noInput ? this.state.quote ? quote : react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
|
||||||
className: "hr thin"
|
className: "hr thin"
|
||||||
|
}) : this.state.audioRec ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_audio_recorder_jsx__WEBPACK_IMPORTED_MODULE_3__["default"], {
|
||||||
|
onDeleted: _ => this.setState({
|
||||||
|
audioRec: false
|
||||||
|
}),
|
||||||
|
onFinished: this.handleAttachAudio
|
||||||
}) : react__WEBPACK_IMPORTED_MODULE_0___default().createElement("textarea", {
|
}) : react__WEBPACK_IMPORTED_MODULE_0___default().createElement("textarea", {
|
||||||
id: "sendMessage",
|
id: "sendMessage",
|
||||||
placeholder: prompt,
|
placeholder: prompt,
|
||||||
@@ -14338,13 +14403,24 @@ class SendMessage extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureComp
|
|||||||
this.messageEditArea = ref;
|
this.messageEditArea = ref;
|
||||||
},
|
},
|
||||||
autoFocus: true
|
autoFocus: true
|
||||||
}), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("a", {
|
}), this.state.message || !this.state.audioEnabled ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement("a", {
|
||||||
href: "#",
|
href: "#",
|
||||||
onClick: this.handleSend,
|
onClick: this.handleSend,
|
||||||
title: "Send"
|
title: "Send"
|
||||||
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("i", {
|
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("i", {
|
||||||
className: "material-icons"
|
className: "material-icons"
|
||||||
}, "send")), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("input", {
|
}, "send")) : !this.state.audioRec ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement("a", {
|
||||||
|
href: "#",
|
||||||
|
onClick: e => {
|
||||||
|
e.preventDefault();
|
||||||
|
this.setState({
|
||||||
|
audioRec: true
|
||||||
|
});
|
||||||
|
},
|
||||||
|
title: "Voice"
|
||||||
|
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("i", {
|
||||||
|
className: "material-icons"
|
||||||
|
}, "mic")) : null, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("input", {
|
||||||
type: "file",
|
type: "file",
|
||||||
ref: ref => {
|
ref: ref => {
|
||||||
this.attachFile = ref;
|
this.attachFile = ref;
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user