TT#43554 Call: Refactor CSC call vue.js component

Change-Id: Icefd79362d414bca9ffb857857af461ad3564c24
changes/96/23696/2
Hans-Peter Herzog 7 years ago
parent 68ee7088ba
commit 82a98ed539

@ -3,176 +3,187 @@
<audio <audio
ref="incomingSound" ref="incomingSound"
loop loop
playsinline
preload="auto" preload="auto"
src="statics/ring.mp3" src="statics/ring.mp3"
></audio> />
<q-card <div class="csc-call-title absolute-top-left">
flat <q-icon
color="secondary" v-if="isPreparing"
name="call"
color="primary"
size="26px"
/>
<q-icon
v-if="isEstablished && isCaller"
name="call made"
color="primary"
size="26px"
/>
<q-icon
v-if="isEstablished && isCallee"
name="call received"
color="primary"
size="26px"
/>
<q-icon
v-if="isEnded"
name="error"
color="primary"
size="26px"
/>
<span
v-if="isPreparing"
class="text"
>
{{ $t('call.startNew') }}
</span>
<span
v-else-if="isInitiating"
class="text"
>
{{ $t('call.initiating') }}
</span>
<span
v-else-if="isRinging"
class="text"
>
{{ $t('call.ringing') }}
</span>
<span
v-else-if="isEnded"
class="text"
>
{{ $t('call.ended') }}
</span>
<span
v-else-if="isIncoming"
class="text"
>
{{ $t('call.incoming') }}
</span>
<span
v-else
class="text"
>
{{ $t('call.call') }}
</span>
</div>
<div
class="csc-call-top-actions absolute-top-right"
> >
<q-card-title> <q-btn
<span v-if="isRinging || isInitiating || isIncoming"> v-if="isFullscreenEnabled && !isMobile"
<q-spinner-rings round
color="primary" :small="!isFullscreenEnabled"
:size="50" slot="right"
/> class="no-shadow"
</span> @click="toggleFullscreen()"
icon="fullscreen exit"
<q-icon color="default"
v-if="isPreparing" />
name="call" <q-btn
color="primary" v-else-if="!isMobile"
size="26px" round
/> :small="!isFullscreenEnabled"
<q-icon slot="right"
v-if="isEstablished && isCaller" class="no-shadow"
name="call made" @click="toggleFullscreen()"
color="primary" icon="fullscreen"
size="26px" color="default"
/> />
<q-icon <q-btn
v-if="isEstablished && isCallee" round
name="call received" :small="!isFullscreenEnabled"
color="primary" slot="right"
size="26px" class="no-shadow"
/> @click="close()"
<q-icon icon="clear"
v-if="isEnded" color="default"
name="error" />
color="primary" </div>
size="26px" <div
/> class="csc-call-spinner row justify-center"
v-if="isRinging || isInitiating || isIncoming"
<span >
v-if="isPreparing" <q-spinner-rings
class="text" color="primary"
> :size="50"
{{ $t('call.startNew') }} />
</span> </div>
<span <q-alert
v-else-if="isInitiating" v-if="desktopSharingInstall"
class="text" v-model="desktopSharingInstall"
> color="warning"
{{ $t('call.initiating') }} :actions="desktopSharingAlertActions"
</span> >
<span {{ $t('call.desktopSharingNotInstalled') }}
v-else-if="isRinging" </q-alert>
class="text" <csc-phone-number-input
> v-if="isPreparing"
{{ $t('call.ringing') }} class="csc-phone-number-input"
</span> ref="phoneNumberInput"
<span :enabled="isPhoneNumberInputEnabled"
v-else-if="isEnded" :dialpad-button="true"
class="text" @toggle-dialpad="toggleDialpad()"
> @key-return="call('audioOnly')"
{{ $t('call.ended') }} />
</span> <div
<span v-if="!isPreparing"
v-else-if="isIncoming" class="csc-phone-number"
class="text" >
> <q-icon
{{ $t('call.incoming') }} v-if="isCalling && (localMediaType == 'audioVideo' || remoteMediaType == 'audioVideo')"
</span> name="videocam"
<span color="primary"
v-else size="26px"
class="text" />
> <q-icon
{{ $t('call.call') }} v-else-if="isCalling && (localMediaType == 'audioOnly' || remoteMediaType == 'audioVideo')"
</span> name="mic"
color="primary"
<q-btn size="26px"
v-if="isFullscreenEnabled && !isMobile" />
round {{ getNumber | destinationFormat }}
:small="!isFullscreenEnabled" </div>
slot="right" <div
class="no-shadow" v-if="isEnded"
@click="toggleFullscreen()" class="csc-call-ended-reason"
icon="fullscreen exit" >
/> {{ getEndedReason | startCase }}
<q-btn </div>
v-else-if="!isMobile" <div
round :class="callMediaClasses"
:small="!isFullscreenEnabled" v-show="isCalling || isEstablished"
slot="right" >
class="no-shadow" <csc-media
@click="toggleFullscreen()" class="csc-media-local"
icon="fullscreen" id="local-media"
/> :muted="true"
<q-btn v-show="isCalling"
round :stream="localMediaStream"
:small="!isFullscreenEnabled" />
slot="right" <csc-media
class="no-shadow" class="csc-media-remote"
@click="close()" id="remote-media"
icon="clear" :muted="isMuted"
/> v-show="isEstablished"
</q-card-title> :stream="remoteMediaStream"
<q-card-main> />
<q-alert </div>
v-if="desktopSharingInstall" <div
v-model="desktopSharingInstall" :class="callControlClasses"
color="warning" >
:actions="desktopSharingAlertActions" <csc-call-dialpad
> class="csc-call-dialpad"
{{ $t('call.desktopSharingNotInstalled') }} v-if="isDialpadEnabled"
</q-alert> @inserted="dialpadInserted"
@remove="dialpadRemove"
<div class="csc-call-info"> @remove-all="dialpadRemoveAll"
<csc-phone-number-input />
ref="phoneNumberInput" <div
v-if="isPreparing" class="csc-call-actions"
:enabled="isPhoneNumberInputEnabled" >
/>
<div
v-if="!isPreparing"
class="phone-number"
>
<q-icon
v-if="isCalling && (localMediaType == 'audioVideo' || remoteMediaType == 'audioVideo')"
name="videocam"
color="primary"
size="26px"
/>
<q-icon
v-else-if="isCalling && (localMediaType == 'audioOnly' || remoteMediaType == 'audioVideo')"
name="mic"
color="primary"
size="26px"
/>
{{ getNumber | destinationFormat }}
</div>
<div
v-if="isEnded"
class="ended-reason"
>
{{ getEndedReason }}
</div>
<csc-call-dialpad
class="csc-call-dialpad"
v-if="isDialpadEnabled"
@inserted="dialpadInserted"
/>
</div>
<div class="csc-call-media">
<csc-media
:class="mediaPreviewClasses"
id="local-media"
:muted="true"
v-show="isCalling"
:stream="localMediaStream"
/>
<csc-media
class="csc-media-remote"
id="remote-media"
:muted="isMuted"
v-show="isEstablished"
:stream="remoteMediaStream"
/>
</div>
</q-card-main>
<q-card-actions align="center">
<q-btn <q-btn
v-if="isEstablished" v-if="isEstablished"
round round
@ -261,14 +272,6 @@
@click="accept('audioScreen')" @click="accept('audioScreen')"
icon="computer" icon="computer"
/> />
<q-btn
v-if="isDialpadButtonEnabled"
round
:small="!isFullscreenEnabled"
color="primary"
@click="toggleDialpad()"
icon="dialpad"
/>
<q-btn <q-btn
v-if="isIncoming" v-if="isIncoming"
round round
@ -277,8 +280,8 @@
@click="decline()" @click="decline()"
icon="call end" icon="call end"
/> />
</q-card-actions> </div>
</q-card> </div>
</div> </div>
</template> </template>
@ -350,6 +353,16 @@
this.$refs.phoneNumberInput.concat(value); this.$refs.phoneNumberInput.concat(value);
} }
}, },
dialpadRemove() {
if(!this.isEstablished) {
this.$refs.phoneNumberInput.remove();
}
},
dialpadRemoveAll() {
if(!this.isEstablished) {
this.$refs.phoneNumberInput.removeAll();
}
},
focusNumberInput() { focusNumberInput() {
this.$refs.phoneNumberInput.focusInput(); this.$refs.phoneNumberInput.focusInput();
}, },
@ -474,13 +487,6 @@
return 'volume up'; return 'volume up';
} }
}, },
mediaPreviewClasses() {
var classes = [];
if(this.isEstablished && this.hasRemoteVideo) {
classes.push('csc-media-preview');
}
return classes;
},
localMediaStream() { localMediaStream() {
if(this.$store.state.call.localMediaStream !== null) { if(this.$store.state.call.localMediaStream !== null) {
return this.$store.state.call.localMediaStream.getStream(); return this.$store.state.call.localMediaStream.getStream();
@ -544,18 +550,34 @@
isDialpadEnabled() { isDialpadEnabled() {
return this.dialpadEnabled && (this.isPreparing || this.isEstablished); return this.dialpadEnabled && (this.isPreparing || this.isEstablished);
}, },
isDialpadButtonEnabled() {
return this.isPreparing || this.isEstablished;
},
callClasses() { callClasses() {
let callClasses = ['csc-call']; let classes = ['csc-call', 'call-state-' + this.callState];
if(this.isEstablished) { if (this.isFullscreenEnabled) {
callClasses.push('csc-call-established'); classes.push('csc-call-fullscreen');
}
if (this.isMobile) {
classes.push('csc-call-mobile');
}
return classes;
},
callMediaClasses() {
let classes = ['csc-call-media'];
if (this.hasVideo) {
classes.push('csc-call-media-video');
} }
return callClasses; return classes;
},
callControlClasses() {
let classes = ['csc-call-controls'];
return classes;
} }
}, },
watch: { watch: {
isEstablished(established) {
if(established) {
this.dialpadEnabled = false;
}
},
callState(state) { callState(state) {
if(state === 'incoming') { if(state === 'incoming') {
showCallNotification(numberFormat(this.getNumber)); showCallNotification(numberFormat(this.getNumber));
@ -566,6 +588,9 @@
} }
else if (state === 'input') { else if (state === 'input') {
this.stopIncomingSound(); this.stopIncomingSound();
if(this.isMobile) {
this.dialpadEnabled = true;
}
} }
else { else {
this.stopIncomingSound(); this.stopIncomingSound();
@ -577,77 +602,165 @@
<style lang="stylus" rel="stylesheet/stylus"> <style lang="stylus" rel="stylesheet/stylus">
@import '../themes/quasar.variables.styl'; @import '../themes/quasar.variables.styl';
.csc-call
.csc-call { position relative
width: inherit; z-index 2
} padding-top 64px
height 100%
.csc-call .q-card { .csc-call-title
margin: 0; z-index 11
} padding 16px
color white
.csc-call .q-card-main { font-size 16px
padding: 0; line-height: 40px
} .csc-call-top-actions
z-index 10
.csc-call .q-field { padding 16px
margin: 0px; .csc-phone-number-input
padding-left: 16px; position relative
padding-right: 16px; z-index 9
} margin 0
padding 16px
.csc-call .q-card-actions {
padding: 16px;
}
.csc-spinner {
text-align: center;
margin-bottom: 16px;
}
.q-card-title .text {
color: #adb3b8;
}
.csc-call-fullscreen .csc-call .q-card-title .text {
color: white;
}
.csc-call .phone-number {
font-size: 18px;
text-align: center;
color: #adb3b8;
margin-bottom: 16px;
}
.csc-call-fullscreen .csc-call .phone-number {
color: white;
}
.csc-call .ended-reason {
font-size: 18px;
text-align: center;
color: #adb3b8;
}
.csc-call-media {
position: relative;
}
.csc-media.csc-media-preview {
position: absolute;
bottom: 0;
left: 0;
width: 25%;
z-index: 10;
}
.csc-call .q-alert-container {
margin-bottom: 16px;
}
.csc-call.csc-call-established
.csc-call-dialpad .csc-call-dialpad
position relative
z-index 8
padding 16px
.csc-call-actions
display flex
flex-direction row
position relative
z-index 7
padding 16px
padding-top 8px
justify-content center
.q-btn
margin-right 16px
display flex
.q-btn:last-child
margin-right 0
.csc-call-ended-reason
color indianred
text-align center
padding 16px
padding-top 0 padding-top 0
position relative
z-index 6
font-size 16px
.csc-phone-number
text-align center
padding 16px
color white
position relative
z-index 6
font-size 16px
.q-icon
vertical-align middle
.csc-media-local
position relative
.csc-call-media.csc-call-media-video
padding-top 16px
padding-bottom 16px padding-bottom 16px
position relative
z-index 2
.csc-call-spinner
position relative
padding 16px
z-index 3
.csc-call.csc-call-fullscreen
.csc-call-title
font-size 20px
line-height: 56px
.csc-call-controls
padding: 32px
.csc-call-actions
.q-btn
margin-right 16px
.q-btn:last-child
margin-right 0
.csc-call-media
padding 0
position absolute
top 0
left 0
right 0
bottom 0
.csc-media-local
position relative
padding 0
width: 100%
height: 100%
.csc-phone-number
font-size: 20px
.csc-call-ended-reason
font-size: 20px
.csc-call.csc-call-fullscreen.call-state-initiating,
.csc-call.csc-call-fullscreen.call-state-ringing
.csc-call-controls
position absolute
bottom 0
right 0
left 0
background-color alpha($secondary, 0.4)
.csc-call.call-state-established
.csc-call-media
.csc-media-local
position absolute
width 30%
height auto
z-index 2
bottom 24px
left 8px
-webkit-box-shadow: 0px 0px 29px -4px rgba(0,0,0,0.75);
-moz-box-shadow: 0px 0px 29px -4px rgba(0,0,0,0.75);
box-shadow: 0px 0px 29px -4px rgba(0,0,0,0.75);
.csc-media-remote
position relative
width 100%
height 100%
z-index 1
.csc-call.csc-call-fullscreen.call-state-established
.csc-call-media
.csc-media-local
bottom 16px
left 16px
width 20%
-webkit-box-shadow: 0px 0px 29px -4px rgba(0,0,0,0.75);
-moz-box-shadow: 0px 0px 29px -4px rgba(0,0,0,0.75);
box-shadow: 0px 0px 29px -4px rgba(0,0,0,0.75);
.csc-call-controls
position absolute
bottom 0
right 0
left 0
background-color alpha($secondary, 0.4)
.csc-call.csc-call-fullscreen.csc-call-mobile
padding-top 72px
.csc-call-title
padding 8px
padding-left 16px
font-size 20 px
line-height 56px
.csc-call-top-actions
padding 8px
.csc-phone-number-input
margin 0
padding 16px
.csc-call-dialpad
padding 4px
.csc-call-actions
padding 4px
.q-btn
margin-right 16px
.q-btn:last-child
margin-right 0
.csc-call.csc-call-fullscreen.csc-call-mobile.call-state-established
.csc-media-local
bottom 120px
left: 32px
.csc-call-controls
.csc-call-actions
padding-bottom 0
</style> </style>

@ -3,6 +3,34 @@
<div <div
class="column" class="column"
> >
<div
class="csc-dialpad-btn-group csc-dialpad-btn-group-special"
>
<div
class="csc-dialpad-btn"
>
<q-btn
color="primary"
round
outline
small
@click="remove()"
icon="backspace"
/>
</div>
<div
class="csc-dialpad-btn"
>
<q-btn
color="primary"
round
outline
small
@click="removeAll()"
icon="cancel"
/>
</div>
</div>
<div <div
class="csc-dialpad-btn-group" class="csc-dialpad-btn-group"
v-for="(keyRow, rowIndex) in keys" v-for="(keyRow, rowIndex) in keys"
@ -81,6 +109,12 @@
if(this.isEstablished) { if(this.isEstablished) {
this.$store.dispatch('call/sendDTMF', value); this.$store.dispatch('call/sendDTMF', value);
} }
},
remove() {
this.$emit('remove');
},
removeAll() {
this.$emit('remove-all');
} }
} }
} }
@ -94,7 +128,8 @@
padding-bottom 0 padding-bottom 0
.csc-dialpad-btn .csc-dialpad-btn
display inline-block display flex
flex-direction column
margin-left 16px margin-left 16px
.q-btn-inner .q-btn-inner
font-size 22px font-size 22px
@ -106,8 +141,15 @@
margin-left 0 margin-left 0
.csc-dialpad-btn-group .csc-dialpad-btn-group
display: block display: flex
flex-direction row
margin-bottom 8px margin-bottom 8px
justify-content: flex-end
.csc-dialpad-btn-group.csc-dialpad-btn-group-special
justify-content: center
.q-btn
font-size 14px
.csc-dialpad-btn-group:last-child .csc-dialpad-btn-group:last-child
margin-bottom 0 margin-bottom 0

@ -1,9 +1,23 @@
<template> <template>
<div class="csc-media"> <div
<div v-show="loading" class="csc-spinner"> class="csc-media"
<q-spinner-mat color="primary" :size="60" /> >
<div
v-show="loading"
class="csc-spinner"
>
<q-spinner-mat
color="primary"
:size="60"
/>
</div> </div>
<video v-show="!loading && hasVideo" ref="media" autoplay :muted="muted"></video> <video
v-show="!loading && hasVideo"
ref="media"
autoplay
:muted="muted"
playsinline
/>
</div> </div>
</template> </template>
@ -70,11 +84,20 @@
</script> </script>
<style lang="stylus" rel="stylesheet/stylus"> <style lang="stylus" rel="stylesheet/stylus">
.csc-media {
position: relative; .csc-media
}
.csc-media video {
position: relative; position: relative;
width: 100%; font-size 0
}
video
position: relative;
width: 100%;
.csc-spinner
display flex
flex-direction row
justify-content center
.q-spinner-mat
display flex
flex-direction column
</style> </style>

@ -1,5 +1,6 @@
<template> <template>
<q-field <q-field
v-if="!isMobile"
dark dark
:count="maxLength" :count="maxLength"
:helper="helperMessage" :helper="helperMessage"
@ -19,6 +20,32 @@
@input="inputPhoneNumber" @input="inputPhoneNumber"
@keypress.space.prevent @keypress.space.prevent
@keydown.space.prevent @keydown.space.prevent
@keyup.space.prevent
:after="inputButtons"
@keyup.enter="keyReturn()"
/>
</q-field>
<q-field
v-else
dark
:error="$v.phoneNumber.$error"
:error-label="errorMessage"
>
<q-input
ref="inputField"
dark
clearable
type="text"
:placeholder="$t('call.number')"
:value="phoneNumber"
:error="$v.phoneNumber.$error"
:max-length="maxLength"
:readonly="!enabled"
@input="inputPhoneNumber"
@keypress.space.prevent
@keydown.space.prevent
@keyup.space.prevent
:after="inputButtons"
/> />
</q-field> </q-field>
</template> </template>
@ -64,6 +91,10 @@
enabled: { enabled: {
type: Boolean, type: Boolean,
default: true default: true
},
dialpadButton: {
type: Boolean,
default: false
} }
}, },
mixins: [ mixins: [
@ -78,9 +109,27 @@
}, },
inputReadonly() { inputReadonly() {
return this.isMobile; return this.isMobile;
},
inputButtons() {
let self = this;
let buttons = [];
if (this.dialpadButton) {
buttons.push({
icon: 'dialpad',
error: false,
handler (event) {
event.stopPropagation();
self.toggleDialpad();
}
});
}
return buttons;
} }
}, },
methods: { methods: {
keyReturn() {
this.$emit('key-return');
},
inputPhoneNumber(value) { inputPhoneNumber(value) {
this.phoneNumber = normalizeNumber(value, this.isMobile); this.phoneNumber = normalizeNumber(value, this.isMobile);
this.$v.phoneNumber.$touch(); this.$v.phoneNumber.$touch();
@ -106,6 +155,18 @@
Vue.nextTick(() => { Vue.nextTick(() => {
this.$refs.inputField.blur(); this.$refs.inputField.blur();
}); });
},
remove() {
this.phoneNumber = _.trim(this.phoneNumber.substring(0, this.phoneNumber.length - 1));
if (this.phoneNumber === '+') {
this.phoneNumber = '';
}
},
removeAll() {
this.phoneNumber = '';
},
toggleDialpad() {
this.$emit('toggle-dialpad');
} }
} }
} }

@ -581,11 +581,7 @@
} }
.csc-call-fullscreen .csc-call .q-card-actions { .csc-call-fullscreen .csc-call .q-card-actions {
position: absolute;
bottom: 0;
right: 0;
left: 0;
z-index: 6001;
} }
.csc-call-fullscreen .csc-call .q-card-main { .csc-call-fullscreen .csc-call .q-card-main {

@ -5,9 +5,11 @@ import NumberFormatFilter from './number-format'
import { normalizeDestination } from './number-format' import { normalizeDestination } from './number-format'
import DateFilter from './date' import DateFilter from './date'
import { smartTime } from './date' import { smartTime } from './date'
import { startCase } from './string'
Vue.filter('number', NumberFilter); Vue.filter('number', NumberFilter);
Vue.filter('readableDate', DateFilter); Vue.filter('readableDate', DateFilter);
Vue.filter('numberFormat', NumberFormatFilter); Vue.filter('numberFormat', NumberFormatFilter);
Vue.filter('destinationFormat', normalizeDestination); Vue.filter('destinationFormat', normalizeDestination);
Vue.filter('smartTime', smartTime); Vue.filter('smartTime', smartTime);
Vue.filter('startCase', startCase);

@ -0,0 +1,6 @@
import _ from 'lodash'
export function startCase(str) {
return _.startCase(str);
}
Loading…
Cancel
Save