Voice messages
This commit is contained in:
parent
fd86e753fe
commit
3146a0b35a
4 changed files with 303 additions and 99 deletions
|
|
@ -137,7 +137,7 @@ $admin-fg: white;
|
||||||
max-height: 30vh;
|
max-height: 30vh;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding: 0 0 0 0px;
|
padding: 0 0 0px 20px;
|
||||||
margin: 6px 0;
|
margin: 6px 0;
|
||||||
font-family: 'Inter', sans-serif;
|
font-family: 'Inter', sans-serif;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
|
|
@ -606,10 +606,45 @@ $admin-fg: white;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
background-color: black;
|
background-color: black;
|
||||||
&.will-cancel {
|
.will-cancel {
|
||||||
background-color: grey;
|
background-color: #ff3300;
|
||||||
}
|
}
|
||||||
.recording-time {
|
.recording-time {
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
.swipe-info {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.locked {
|
||||||
|
background-color: black;
|
||||||
|
}
|
||||||
|
.error {
|
||||||
|
background-color: orange;
|
||||||
|
}
|
||||||
|
.voice-recorder-lock {
|
||||||
|
position: relative;
|
||||||
|
margin-left: 10px;
|
||||||
|
margin-right: 10px;
|
||||||
|
width: 20px;
|
||||||
|
height: 100%;
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 10px;
|
||||||
|
background-color: rgba(white, 0.3);
|
||||||
|
&.locked {
|
||||||
|
background-color: rgba(white, 0.8);
|
||||||
|
}
|
||||||
|
.thumb {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
background-color: black;
|
||||||
|
border-radius: 8px;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 1px;
|
||||||
|
left: 1px;
|
||||||
|
&.locked {
|
||||||
|
top: 1px;
|
||||||
|
bottom: unset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -111,6 +111,78 @@
|
||||||
</div>
|
</div>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row class="input-area-inner align-center">
|
<v-row class="input-area-inner align-center">
|
||||||
|
<v-col class="flex-grow-1 flex-shrink-1 ma-0 pa-0">
|
||||||
|
<v-textarea
|
||||||
|
height="undefined"
|
||||||
|
ref="messageInput"
|
||||||
|
full-width
|
||||||
|
auto-grow
|
||||||
|
rows="1"
|
||||||
|
v-model="currentInput"
|
||||||
|
no-resize
|
||||||
|
class="input-area-text"
|
||||||
|
placeholder="Your message..."
|
||||||
|
hide-details
|
||||||
|
background-color="white"
|
||||||
|
v-on:keydown.enter.prevent="
|
||||||
|
() => {
|
||||||
|
sendMessage();
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
|
||||||
|
<v-col
|
||||||
|
class="input-area-button text-center flex-grow-0 flex-shrink-1"
|
||||||
|
v-if="editedEvent || replyToEvent"
|
||||||
|
>
|
||||||
|
<v-btn
|
||||||
|
fab
|
||||||
|
small
|
||||||
|
elevation="0"
|
||||||
|
color="black"
|
||||||
|
@click.stop="cancelEditReply"
|
||||||
|
>
|
||||||
|
<v-icon color="white">cancel</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-col>
|
||||||
|
|
||||||
|
<v-col
|
||||||
|
class="input-area-button text-center flex-grow-0 flex-shrink-1"
|
||||||
|
v-if="!currentInput || currentInput.length == 0"
|
||||||
|
>
|
||||||
|
<v-btn
|
||||||
|
ref="mic_button"
|
||||||
|
fab
|
||||||
|
small
|
||||||
|
elevation="0"
|
||||||
|
color="transparent"
|
||||||
|
v-blur
|
||||||
|
style="z-index:10"
|
||||||
|
@touchstart.native.stop="startRecording"
|
||||||
|
@mousedown.native.stop="startRecording"
|
||||||
|
>
|
||||||
|
<v-icon :color="showRecorder ? 'white' : 'black'">mic</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-col>
|
||||||
|
|
||||||
|
<v-col class="input-area-button text-center flex-grow-0 flex-shrink-1"
|
||||||
|
v-else
|
||||||
|
>
|
||||||
|
<v-btn
|
||||||
|
fab
|
||||||
|
small
|
||||||
|
elevation="0"
|
||||||
|
color="black"
|
||||||
|
@click.stop="sendMessage"
|
||||||
|
:disabled="sendButtonDisabled"
|
||||||
|
>
|
||||||
|
<v-icon color="white">{{
|
||||||
|
editedEvent ? "save" : "arrow_upward"
|
||||||
|
}}</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-col>
|
||||||
|
|
||||||
<v-col class="input-area-button text-center flex-grow-0 flex-shrink-1">
|
<v-col class="input-area-button text-center flex-grow-0 flex-shrink-1">
|
||||||
<label icon flat ref="attachmentLabel">
|
<label icon flat ref="attachmentLabel">
|
||||||
<v-btn
|
<v-btn
|
||||||
|
|
@ -133,72 +205,8 @@
|
||||||
</label>
|
</label>
|
||||||
</v-col>
|
</v-col>
|
||||||
|
|
||||||
<v-col class="flex-grow-1 flex-shrink-1 ma-0 pa-0">
|
|
||||||
<v-textarea
|
|
||||||
height="undefined"
|
|
||||||
ref="messageInput"
|
|
||||||
full-width
|
|
||||||
auto-grow
|
|
||||||
rows="1"
|
|
||||||
v-model="currentInput"
|
|
||||||
no-resize
|
|
||||||
class="input-area-text"
|
|
||||||
placeholder="Send message"
|
|
||||||
hide-details
|
|
||||||
background-color="white"
|
|
||||||
v-on:keydown.enter.prevent="
|
|
||||||
() => {
|
|
||||||
sendMessage();
|
|
||||||
}
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
</v-col>
|
|
||||||
|
|
||||||
<v-col
|
|
||||||
class="input-area-button text-center flex-grow-0 flex-shrink-1"
|
|
||||||
>
|
|
||||||
<v-btn
|
|
||||||
fab
|
|
||||||
small
|
|
||||||
elevation="0"
|
|
||||||
color="transparent"
|
|
||||||
v-blur
|
|
||||||
style="z-index:10"
|
|
||||||
@mousedown.stop="startRecording"
|
|
||||||
>
|
|
||||||
<v-icon :color="showRecorder ? 'white' : 'black'">mic</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</v-col>
|
|
||||||
<v-col
|
|
||||||
class="input-area-button text-center flex-grow-0 flex-shrink-1"
|
|
||||||
v-if="editedEvent || replyToEvent"
|
|
||||||
>
|
|
||||||
<v-btn
|
|
||||||
fab
|
|
||||||
small
|
|
||||||
elevation="0"
|
|
||||||
color="black"
|
|
||||||
@click.stop="cancelEditReply"
|
|
||||||
>
|
|
||||||
<v-icon color="white">cancel</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</v-col>
|
|
||||||
<v-col class="input-area-button text-center flex-grow-0 flex-shrink-1">
|
|
||||||
<v-btn
|
|
||||||
fab
|
|
||||||
small
|
|
||||||
elevation="0"
|
|
||||||
color="black"
|
|
||||||
@click.stop="sendMessage"
|
|
||||||
:disabled="sendButtonDisabled"
|
|
||||||
>
|
|
||||||
<v-icon color="white">{{
|
|
||||||
editedEvent ? "save" : "arrow_upward"
|
|
||||||
}}</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
</v-row>
|
||||||
<VoiceRecorder :show="showRecorder" v-on:close="showRecorder = false" v-on:file="onVoiceRecording" />
|
<VoiceRecorder :micButtonRef="$refs.mic_button" :show="showRecorder" v-on:close="showRecorder = false" v-on:file="onVoiceRecording" />
|
||||||
</v-container>
|
</v-container>
|
||||||
|
|
||||||
<div v-if="currentImageInput">
|
<div v-if="currentImageInput">
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,79 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div v-show="show" class="voice-recorder" ref="vrroot">
|
||||||
v-show="show"
|
|
||||||
:class="{ 'voice-recorder': true, 'will-cancel': willCancel }"
|
|
||||||
ref="vr_root"
|
|
||||||
>
|
|
||||||
<v-container fluid fill-height>
|
<v-container fluid fill-height>
|
||||||
<v-row align="center" justify="center">
|
<v-row align="center">
|
||||||
<v-col class="text-center">
|
<v-col cols="3">
|
||||||
<div class="recording-time">
|
<div class="recording-time">
|
||||||
{{ recordingTime }}
|
{{ recordingTime }}
|
||||||
</div>
|
</div>
|
||||||
</v-col>
|
</v-col>
|
||||||
|
<v-col cols="6">
|
||||||
|
<div class="swipe-info"><< Swipe to cancel</div>
|
||||||
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
</v-container>
|
</v-container>
|
||||||
|
<transition name="fade" mode="out-in">
|
||||||
|
<div
|
||||||
|
v-if="willCancel"
|
||||||
|
class="will-cancel"
|
||||||
|
style="position: absolute; top: 0; left: 0; right: 0; bottom: 0"
|
||||||
|
>
|
||||||
|
<v-container fluid fill-height>
|
||||||
|
<v-row align="center">
|
||||||
|
<v-col cols="3">
|
||||||
|
<v-icon color="white">delete_outline</v-icon>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="6">
|
||||||
|
<div class="swipe-info">Release to cancel</div>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-container>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
|
||||||
|
<transition name="fade" mode="out-in">
|
||||||
|
<div
|
||||||
|
v-if="recordingLocked"
|
||||||
|
class="locked"
|
||||||
|
style="position: absolute; top: 0; left: 0; right: 0; bottom: 0"
|
||||||
|
>
|
||||||
|
<v-container fluid fill-height>
|
||||||
|
<v-row align="center">
|
||||||
|
<v-col cols="3">
|
||||||
|
<div class="recording-time">
|
||||||
|
{{ recordingTime }}
|
||||||
|
</div>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="3">
|
||||||
|
<v-btn @click.stop="cancelRecording" text class="swipe-info"
|
||||||
|
>Cancel</v-btn
|
||||||
|
>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="3">
|
||||||
|
<v-btn @click.stop="stopRecording" icon class="swipe-info"
|
||||||
|
><v-icon color="white">stop</v-icon></v-btn
|
||||||
|
>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-container>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="state == states.ERROR"
|
||||||
|
class="error"
|
||||||
|
style="position: absolute; top: 0; left: 0; right: 0; bottom: 0"
|
||||||
|
>
|
||||||
|
<v-container fluid fill-height>
|
||||||
|
<v-row align="center">
|
||||||
|
<v-col>
|
||||||
|
<div class="swipe-info">Failed to record audio</div>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-container>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<VoiceRecorderLock v-show="state == states.RECORDING" :style="lockButtonStyle" :isLocked="recordingLocked" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
|
|
@ -23,9 +84,13 @@ const State = {
|
||||||
ERROR: "error",
|
ERROR: "error",
|
||||||
};
|
};
|
||||||
import util from "../plugins/utils";
|
import util from "../plugins/utils";
|
||||||
|
import VoiceRecorderLock from "./VoiceRecorderLock";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "VoiceRecorder",
|
name: "VoiceRecorder",
|
||||||
|
components: {
|
||||||
|
VoiceRecorderLock,
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
show: {
|
show: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
|
@ -33,31 +98,74 @@ export default {
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
micButtonRef: {
|
||||||
|
type: Object,
|
||||||
|
default: function () {
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
willCancel: false,
|
willCancel: false,
|
||||||
|
/** Starting X coordinate of dragging operations */
|
||||||
|
startCoordinateX: null,
|
||||||
|
startCoordinateY: null,
|
||||||
states: State,
|
states: State,
|
||||||
state: State.INITIAL,
|
state: State.INITIAL,
|
||||||
recordStartedAt: null,
|
recordStartedAt: null,
|
||||||
recordingTime: null,
|
recordingTime: null,
|
||||||
recordTimer: null,
|
recordTimer: null,
|
||||||
|
recordingLocked: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
show(val) {
|
show(val) {
|
||||||
if (val) {
|
if (val) {
|
||||||
// Add listeners
|
// Add listeners
|
||||||
|
this.state = State.INITIAL;
|
||||||
document.addEventListener("mouseup", this.mouseUp, false);
|
document.addEventListener("mouseup", this.mouseUp, false);
|
||||||
document.addEventListener("mousemove", this.mouseMove, false);
|
document.addEventListener("mousemove", this.mouseMove, false);
|
||||||
|
document.addEventListener("touchend", this.mouseUp, false);
|
||||||
|
document.addEventListener("touchmove", this.mouseMove, false);
|
||||||
this.startRecording();
|
this.startRecording();
|
||||||
} else {
|
} else {
|
||||||
// Remove listeners
|
// Remove listeners
|
||||||
document.removeEventListener("mouseup", this.mouseUp, false);
|
document.removeEventListener("mouseup", this.mouseUp, false);
|
||||||
document.removeEventListener("mousemove", this.mouseMove, false);
|
document.removeEventListener("mousemove", this.mouseMove, false);
|
||||||
|
document.removeEventListener("touchend", this.mouseUp, false);
|
||||||
|
document.removeEventListener("touchmove", this.mouseMove, false);
|
||||||
|
this.startCoordinateX = null;
|
||||||
|
this.startCoordinateY = null;
|
||||||
|
this.willCancel = false;
|
||||||
|
this.startCoordinateX = null;
|
||||||
|
this.startCoordinateY = null;
|
||||||
|
this.recordingLocked = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
lockButtonStyle() {
|
||||||
|
/**
|
||||||
|
Calculate where to show the lock button (it should be at the same X-coord as the)
|
||||||
|
mic button (given as a reference!)
|
||||||
|
*/
|
||||||
|
var left = 0;
|
||||||
|
var width = 20;
|
||||||
|
if (this.micButtonRef) {
|
||||||
|
var r = this.micButtonRef.$el.getBoundingClientRect();
|
||||||
|
left = r.left;
|
||||||
|
width = r.right - r.left;
|
||||||
|
}
|
||||||
|
const s =
|
||||||
|
"position:absolute;top:-50px;left:" +
|
||||||
|
left +
|
||||||
|
"px;width:" +
|
||||||
|
width +
|
||||||
|
"px;height:40px";
|
||||||
|
return s;
|
||||||
|
},
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
close() {
|
close() {
|
||||||
this.stopRecordTimer();
|
this.stopRecordTimer();
|
||||||
|
|
@ -66,29 +174,38 @@ export default {
|
||||||
mouseUp(ignoredEvent) {
|
mouseUp(ignoredEvent) {
|
||||||
document.removeEventListener("mouseup", this.mouseUp, false);
|
document.removeEventListener("mouseup", this.mouseUp, false);
|
||||||
document.removeEventListener("mousemove", this.mouseMove, false);
|
document.removeEventListener("mousemove", this.mouseMove, false);
|
||||||
document.body.style.cursor = "";
|
document.removeEventListener("touchend", this.mouseUp, false);
|
||||||
if (this.willCancel) {
|
document.removeEventListener("touchmove", this.mouseMove, false);
|
||||||
this.cancelRecording();
|
//document.body.style.cursor = "";
|
||||||
|
if (this.state == State.RECORDING) {
|
||||||
|
if (!this.recordingLocked) {
|
||||||
|
if (this.willCancel) {
|
||||||
|
this.cancelRecording();
|
||||||
|
} else {
|
||||||
|
this.stopRecording();
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.stopRecording();
|
this.cancelRecording();
|
||||||
}
|
}
|
||||||
this.close();
|
|
||||||
},
|
},
|
||||||
mouseMove(event) {
|
mouseMove(event) {
|
||||||
document.body.style.cursor = "ns-resize";
|
var x = event.clientX;
|
||||||
let rect = this.$refs.vr_root.getBoundingClientRect();
|
var y = event.clientY;
|
||||||
this.willCancel = event.clientY < rect.top;
|
if (event.touches && event.touches.length > 0) {
|
||||||
console.log(
|
x = event.touches[0].clientX;
|
||||||
"Cancel: " + this.willCancel + " " + event.clientY + " " + rect.top
|
y = event.touches[0].clientY;
|
||||||
);
|
}
|
||||||
|
if (!this.startCoordinateX) {
|
||||||
// let bottom = rect.bottom;
|
// First move, set it!
|
||||||
// let mouseBottom = event.clientY;
|
this.startCoordinateX = x;
|
||||||
// let newHeight = Math.max(
|
this.startCoordinateY = y;
|
||||||
// 200,
|
}
|
||||||
// Math.min(0.7 * window.innerHeight, bottom - mouseBottom)
|
//document.body.style.cursor = "ns-resize";
|
||||||
// );
|
this.willCancel = x < this.startCoordinateX - 30;
|
||||||
// this.$refs.chatPane.style.height = newHeight + "px";
|
if (y < this.startCoordinateY - 30 && !this.willCancel) {
|
||||||
|
this.recordingLocked = true;
|
||||||
|
}
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
},
|
},
|
||||||
|
|
@ -108,27 +225,34 @@ export default {
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
//this.state = State.ERROR;
|
this.state = State.ERROR;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
cancelRecording() {
|
cancelRecording() {
|
||||||
this.state = State.INITIAL;
|
this.state = State.INITIAL;
|
||||||
this.recorder.stop();
|
this.recorder.stop();
|
||||||
this.stopRecordTimer();
|
this.stopRecordTimer();
|
||||||
|
this.close();
|
||||||
},
|
},
|
||||||
stopRecording() {
|
stopRecording() {
|
||||||
this.state = State.RECORDED;
|
this.state = State.RECORDED;
|
||||||
this.stopRecordTimer();
|
this.stopRecordTimer();
|
||||||
|
this.close();
|
||||||
this.recorder
|
this.recorder
|
||||||
.stop()
|
.stop()
|
||||||
.getMp3()
|
.getMp3()
|
||||||
.then(([buffer, blob]) => {
|
.then(([buffer, blob]) => {
|
||||||
// do what ever you want with buffer and blob
|
// do what ever you want with buffer and blob
|
||||||
// Example: Create a mp3 file and play
|
// Example: Create a mp3 file and play
|
||||||
const file = new File(buffer, util.formatRecordStartTime(this.recordStartedAt) + ".mp3", {
|
const file = new File(
|
||||||
type: blob.type,
|
buffer,
|
||||||
lastModified: Date.now(),
|
util.formatRecordStartTime(this.recordStartedAt) + ".mp3",
|
||||||
});
|
{
|
||||||
|
type: blob.type,
|
||||||
|
lastModified: Date.now(),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
//console.log(file);
|
||||||
this.$emit("file", {file: file});
|
this.$emit("file", {file: file});
|
||||||
// const player = new Audio(URL.createObjectURL(file));
|
// const player = new Audio(URL.createObjectURL(file));
|
||||||
// player.play();
|
// player.play();
|
||||||
|
|
@ -161,3 +285,12 @@ export default {
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import "@/assets/css/chat.scss";
|
@import "@/assets/css/chat.scss";
|
||||||
</style>
|
</style>
|
||||||
|
<style>
|
||||||
|
.fade-enter-active,
|
||||||
|
.fade-leave-active {
|
||||||
|
transition: opacity 0.5s;
|
||||||
|
}
|
||||||
|
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
28
src/components/VoiceRecorderLock.vue
Normal file
28
src/components/VoiceRecorderLock.vue
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
:class="{ 'voice-recorder-lock': true, 'locked': isLocked }"
|
||||||
|
ref="vr_lock"
|
||||||
|
>
|
||||||
|
<div :class="{ 'thumb': true, 'locked': isLocked }" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "VoiceRecorderLock",
|
||||||
|
props: {
|
||||||
|
isLocked: {
|
||||||
|
type: Boolean,
|
||||||
|
default: function () {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@import "@/assets/css/chat.scss";
|
||||||
|
</style>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue