TT#45712 Implement Nuwings CSC 1:1 call wire frames

- TT#45658 NuCall: As a Customer, I want to be able to initiate a call from the start page
- TT#45662 NuCall: As a Customer, I want to see the initiating call state
- TT#45669 NuCall: As a Customer, I want to see the call in ringing mode on caller side
- TT#45659 NuCall: As a Customer, I want to be able to see an incoming call
- TT#45660 NuCall: As a Customer, I want to be able to see established call
- TT#45665 NuCall: As a Customer, I want to be able see the call minimised after leaving the start page

Change-Id: I0c46359e5668cee9ef6313c5e3fe49cccc3d66ee
changes/08/24408/12
Hans-Peter Herzog 7 years ago
parent 2fe7281f73
commit 8c4375583c

@ -0,0 +1,46 @@
<template>
<div
class="csc-alert-error row no-vert-gutter no-wrap"
>
<div
class="col col-2"
>
<q-icon
name="error"
size="32px"
color="white"
/>
</div>
<div
class="csc-alert-error-text"
>
<slot />
</div>
</div>
</template>
<script>
import {
QIcon
} from 'quasar-framework'
export default {
name: 'csc-alert-error',
data () {
return {}
},
components: {
QIcon
}
}
</script>
<style lang="stylus" rel="stylesheet/stylus">
@import '../themes/quasar.variables'
.csc-alert-error
background-color $negative
padding $flex-gutter-md
margin-bottom $flex-gutter-lg
.csc-alert-error-text
line-height 1.4em
color $white
</style>

@ -0,0 +1,48 @@
<template>
<div
class="csc-alert-info row no-vert-gutter no-wrap"
>
<div
class="csc-alert-icon ol col-2"
>
<q-icon
name="info"
size="32px"
color="white"
/>
</div>
<div
class="csc-alert-info-text col-10"
>
<slot />
</div>
</div>
</template>
<script>
import {
QIcon
} from 'quasar-framework'
export default {
name: 'csc-alert-info',
data () {
return {}
},
components: {
QIcon
}
}
</script>
<style lang="stylus" rel="stylesheet/stylus">
@import '../themes/quasar.variables'
.csc-alert-info
background-color $info
padding $flex-gutter-md
margin-bottom $flex-gutter-lg
.csc-alert-icon
text-align left
.csc-alert-info-text
line-height 1.4em
color $white
</style>

@ -1,770 +0,0 @@
<template>
<div :class="callClasses">
<audio
ref="incomingSound"
loop
playsinline
preload="auto"
src="statics/ring.mp3"
/>
<div class="csc-call-title absolute-top-left">
<q-icon
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-btn
v-if="isFullscreenEnabled && !isMobile"
round
:small="!isFullscreenEnabled"
slot="right"
class="no-shadow"
@click="toggleFullscreen()"
icon="fullscreen exit"
color="default"
/>
<q-btn
v-else-if="!isMobile"
round
:small="!isFullscreenEnabled"
slot="right"
class="no-shadow"
@click="toggleFullscreen()"
icon="fullscreen"
color="default"
/>
<q-btn
round
:small="!isFullscreenEnabled"
slot="right"
class="no-shadow"
@click="close()"
icon="clear"
color="default"
/>
</div>
<div
class="csc-call-spinner row justify-center"
v-if="isRinging || isInitiating || isIncoming"
>
<q-spinner-rings
color="primary"
:size="50"
/>
</div>
<q-alert
v-if="desktopSharingInstall"
v-model="desktopSharingInstall"
color="warning"
:actions="desktopSharingAlertActions"
>
{{ $t('call.desktopSharingNotInstalled') }}
</q-alert>
<csc-phone-number-input
v-if="isPreparing"
class="csc-phone-number-input"
ref="phoneNumberInput"
:enabled="isPhoneNumberInputEnabled"
:dialpad-button="true"
@toggle-dialpad="toggleDialpad()"
@key-return="call('audioOnly')"
/>
<div
v-if="!isPreparing"
class="csc-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="csc-call-ended-reason"
>
{{ getEndedReason | startCase }}
</div>
<div
:class="callMediaClasses"
v-show="isCalling || isEstablished"
>
<csc-media
class="csc-media-local"
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>
<div
:class="callControlClasses"
>
<csc-call-dialpad
class="csc-call-dialpad"
v-if="isDialpadEnabled"
@inserted="dialpadInserted"
@remove="dialpadRemove"
@remove-all="dialpadRemoveAll"
/>
<div
class="csc-call-actions"
>
<q-btn
v-if="isEstablished"
round
:small="!isFullscreenEnabled"
color="primary"
@click="toggleAudio()"
:icon="toggleAudioIcon"
/>
<q-btn
v-if="isEstablished && localMediaType == 'audioVideo'"
round
:small="!isFullscreenEnabled"
color="primary"
@click="toggleVideo()"
:icon="toggleVideoIcon"
/>
<q-btn
v-if="isEstablished"
round
:small="!isFullscreenEnabled"
color="primary"
@click="toggleMute()"
:icon="toggleMuteIcon"
/>
<q-btn
v-if="isPreparing"
round
:small="!isFullscreenEnabled"
color="primary"
@click="call('audioOnly')"
icon="mic"
/>
<q-btn
v-if="isPreparing"
round
:small="!isFullscreenEnabled"
color="primary"
@click="call('audioVideo')"
icon="videocam"
/>
<q-btn
v-if="isPreparing && !isMobile"
round
:small="!isFullscreenEnabled"
color="primary"
@click="call('audioScreen')"
icon="computer"
/>
<q-btn
v-if="isCalling"
round
:small="!isFullscreenEnabled"
color="negative"
@click="hangUp()"
icon="call end"
/>
<q-btn
v-if="isEnded"
round
:small="!isFullscreenEnabled"
color="negative"
@click="init()"
icon="clear"
/>
<q-btn
v-if="isIncoming"
round
:small="!isFullscreenEnabled"
color="primary"
@click="accept('audioOnly')"
icon="mic"
/>
<q-btn
v-if="isIncoming"
round
:small="!isFullscreenEnabled"
color="primary"
@click="accept('audioVideo')"
icon="videocam"
/>
<q-btn
v-if="isIncoming && !isMobile"
round
:small="!isFullscreenEnabled"
color="primary"
@click="accept('audioScreen')"
icon="computer"
/>
<q-btn
v-if="isIncoming"
round
:small="!isFullscreenEnabled"
color="negative"
@click="decline()"
icon="call end"
/>
</div>
</div>
</div>
</template>
<script>
import platformMixin from '../mixins/platform'
import { mapGetters } from 'vuex'
import CscMedia from './CscMedia'
import numberFormat from '../filters/number-format'
import { showCallNotification } from '../helpers/ui'
import { getChromeExtensionUrl } from '../helpers/cdk-lib'
import CscCallDialpad from './CscCallDialpad'
import CscPhoneNumberInput from './call/CscPhoneNumberInput'
import { showGlobalError } from '../helpers/ui'
import {
QLayout,
QCard,
QCardTitle,
QCardSeparator,
QCardMain,
QField,
QInput,
QCardActions,
QBtn,
QIcon,
QSpinnerRings,
Dialog,
QAlert
} from 'quasar-framework'
export default {
name: 'csc-call',
props: ['region', 'fullscreen'],
data () {
return {
phoneNumber: '',
phoneNumberError: false,
validationEnabled: false,
dialpadEnabled: false
}
},
mixins: [
platformMixin
],
components: {
QLayout,
QCard,
QCardTitle,
QCardSeparator,
QCardMain,
QField,
QInput,
QCardActions,
QBtn,
QIcon,
QSpinnerRings,
CscMedia,
QAlert,
CscCallDialpad,
CscPhoneNumberInput
},
mounted() {
this.dialpadEnabled = this.isMobile;
},
methods: {
dialpadInserted(value) {
if(this.isEstablished) {
this.$store.dispatch('call/sendDTMF', value);
}
else {
this.$refs.phoneNumberInput.concat(value);
}
},
dialpadRemove() {
if(!this.isEstablished) {
this.$refs.phoneNumberInput.remove();
}
},
dialpadRemoveAll() {
if(!this.isEstablished) {
this.$refs.phoneNumberInput.removeAll();
}
},
focusNumberInput() {
this.$refs.phoneNumberInput.focusInput();
},
blurNumberInput() {
this.$refs.phoneNumberInput.blurInput();
},
init() {
this.phoneNumber = '';
this.validationEnabled = false;
this.phoneNumberError = false;
this.$store.commit('call/inputNumber');
},
phoneNumberFocus() {
this.validationEnabled = true;
},
phoneNumberBlur() {
this.validationEnabled = false;
},
call(localMedia) {
if(this.$refs.phoneNumberInput.hasPhoneNumber()) {
this.$store.dispatch('call/start', {
number: this.$refs.phoneNumberInput.getPhoneNumber(),
localMedia: localMedia
});
}
else {
showGlobalError(this.$t('validationErrors.inputValidNumber'));
}
},
accept(localMedia) {
this.$store.dispatch('call/accept', localMedia);
},
decline() {
this.hangUp();
this.$emit('close');
},
hangUp() {
this.$store.dispatch('call/hangUp');
},
close() {
if(this.isPreparing || this.isEnded) {
this.init();
this.$emit('close');
}
else {
Dialog.create({
title: this.$t('call.endCall'),
message: this.$t('call.endCallDialog'),
buttons: [
'Cancel',
{
label: this.$t('call.endCall'),
color: 'negative',
handler: ()=>{
this.hangUp();
this.$emit('close');
}
}
]
});
}
},
playIncomingSound() {
this.$refs.incomingSound.play();
},
stopIncomingSound() {
this.$refs.incomingSound.pause();
},
toggleAudio() {
if(this.isAudioEnabled) {
this.$store.dispatch('call/disableAudio');
}
else {
this.$store.dispatch('call/enableAudio');
}
},
toggleVideo() {
if(this.isVideoEnabled) {
this.$store.dispatch('call/disableVideo');
}
else {
this.$store.dispatch('call/enableVideo');
}
},
toggleMute() {
if(this.isMuted) {
this.$store.commit('call/unmute');
}
else {
this.$store.commit('call/mute');
}
},
toggleFullscreen() {
this.$emit('fullscreen');
},
toggleDialpad() {
this.dialpadEnabled = !this.dialpadEnabled;
}
},
computed: {
isFullscreenEnabled() {
return this.fullscreen;
},
toggleAudioIcon() {
if(this.isAudioEnabled) {
return 'mic'
}
else {
return 'mic off';
}
},
toggleVideoIcon() {
if(this.isVideoEnabled) {
return 'videocam'
}
else {
return 'videocam off';
}
},
toggleMuteIcon() {
if(this.isMuted) {
return 'volume off'
}
else {
return 'volume up';
}
},
localMediaStream() {
if(this.$store.state.call.localMediaStream !== null) {
return this.$store.state.call.localMediaStream.getStream();
}
else {
return null;
}
},
remoteMediaStream() {
if(this.$store.state.call.remoteMediaStream !== null) {
return this.$store.state.call.remoteMediaStream.getStream();
}
else {
return null;
}
},
...mapGetters('call', [
'isPreparing',
'isInitiating',
'isTrying',
'isRinging',
'isCalling',
'isEnded',
'isIncoming',
'isEstablished',
'getNumber',
'getEndedReason',
'hasRemoteVideo',
'hasVideo',
'isAudioEnabled',
'isVideoEnabled',
'isMuted',
'localMediaType',
'remoteMediaType',
'isCaller',
'isCallee',
'callState',
'desktopSharingInstall'
]),
desktopSharingAlertActions() {
var self = this;
return [
{
label: 'Install',
handler () {
self.$store.commit('call/desktopSharingInstallReset');
window.open(getChromeExtensionUrl());
}
},
{
label: 'Cancel',
handler () {
self.$store.commit('call/desktopSharingInstallReset');
}
}
]
},
isPhoneNumberInputEnabled () {
return !(this.isDialpadEnabled && this.isMobile);
},
isDialpadEnabled() {
return this.dialpadEnabled && (this.isPreparing || this.isEstablished);
},
callClasses() {
let classes = ['csc-call', 'call-state-' + this.callState];
if (this.isFullscreenEnabled) {
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 classes;
},
callControlClasses() {
let classes = ['csc-call-controls'];
return classes;
}
},
watch: {
isEstablished(established) {
if(established) {
this.dialpadEnabled = false;
}
},
callState(state) {
if(state === 'incoming') {
showCallNotification(numberFormat(this.getNumber));
this.playIncomingSound();
}
else if (state === 'ringing') {
this.playIncomingSound();
}
else if (state === 'input') {
this.stopIncomingSound();
if(this.isMobile) {
this.dialpadEnabled = true;
}
}
else {
this.stopIncomingSound();
}
}
}
}
</script>
<style lang="stylus" rel="stylesheet/stylus">
@import '../themes/quasar.variables.styl';
.csc-call
position relative
z-index 2
padding-top 64px
height 100%
.csc-call-title
z-index 11
padding 16px
color white
font-size 16px
line-height: 40px
.csc-call-top-actions
z-index 10
padding 16px
.csc-phone-number-input
position relative
z-index 9
margin 0
padding 16px
.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
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
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>

@ -4,27 +4,28 @@
class="column"
>
<div
v-if="showBackspaceButton || showClearButton"
class="csc-dialpad-btn-group csc-dialpad-btn-group-special"
>
<div
v-if="showBackspaceButton"
class="csc-dialpad-btn"
>
<q-btn
color="primary"
round
outline
small
@click="remove()"
icon="backspace"
/>
</div>
<div
v-if="showClearButton"
class="csc-dialpad-btn"
>
<q-btn
color="primary"
round
outline
small
@click="removeAll()"
icon="cancel"
@ -42,11 +43,10 @@
:key="rowIndex + ':' + keyIndex"
>
<q-btn
color="primary"
color="default"
round
outline
:small="!isFullscreenEnabled && !isMobile"
@click="insert(key)"
small
@click="click(key)"
>
{{ key }}
</q-btn>
@ -57,19 +57,20 @@
</template>
<script>
import platformMixin from '../mixins/platform'
import {
QBtn,
QIcon
} from 'quasar-framework'
import { mapGetters } from 'vuex'
export default {
name: 'csc-call-dialpad',
data () {
return {}
},
props: [
'showBackspaceButton',
'showClearButton'
],
components: {
QBtn,
QIcon
@ -78,22 +79,6 @@
platformMixin
],
computed: {
...mapGetters('layout', [
'isFullscreenEnabled'
]),
...mapGetters('call', [
'isEstablished'
]),
isDialpadSmall() {
let classes = ['row', 'justify-center'];
if(!this.isFullscreenEnabled) {
classes.push('small-dialpad');
}
else {
classes.push('csc-dialpad');
}
return classes;
},
keys() {
return [
['1','2','3'],
@ -104,11 +89,8 @@
}
},
methods: {
insert(value) {
this.$emit('inserted', value);
if(this.isEstablished) {
this.$store.dispatch('call/sendDTMF', value);
}
click(value) {
this.$emit('click', value);
},
remove() {
this.$emit('remove');

@ -4,40 +4,45 @@
>
<div
v-show="loading"
class="csc-spinner"
class="csc-media-spinner"
>
<q-spinner-mat
<q-spinner-dots
color="primary"
:size="60"
:size="24"
/>
</div>
<video
v-show="!loading && hasVideo"
ref="media"
autoplay
:muted="muted"
playsinline
:class="videoClasses"
:muted="muted"
/>
</div>
</template>
<script>
import _ from 'lodash';
import { QSpinnerMat, QIcon } from 'quasar-framework'
import {
QSpinnerDots,
QIcon
} from 'quasar-framework'
export default {
name: 'csc-media',
props: ['stream', 'muted'],
props: [
'stream',
'muted',
'fit'
],
data () {
return {
currentStream: this.stream,
loading: true,
}
},
mounted() {},
components: {
QSpinnerMat,
QSpinnerDots,
QIcon
},
methods: {
@ -78,26 +83,45 @@
hasVideo() {
return this.currentStream !== null && _.isArray(this.currentStream.getVideoTracks()) &&
this.currentStream.getVideoTracks().length > 0;
},
videoClasses() {
let classes = [];
if(this.fit === 'full') {
classes.push('fit-full');
}
else if(this.fit === 'width') {
classes.push('fit-width');
}
return classes;
}
}
}
</script>
<style lang="stylus" rel="stylesheet/stylus">
@import '../themes/quasar.variables'
.csc-media
position: relative;
font-size 0
video
position relative
height 100%
width 100%
.csc-media-spinner
position absolute
top 50%
left 50%
margin-top -12px
margin-left -12px
video.fit-full
position: absolute;
top: 0;
left: 0;
min-width: 100%;
min-height: 100%;
width: auto;
height: auto;
background-size: cover;
video.fit-width
position: relative;
width: 100%;
.csc-spinner
display flex
flex-direction row
justify-content center
.q-spinner-mat
display flex
flex-direction column
height: auto;
font-size 0
</style>

@ -101,6 +101,4 @@
margin-right: 10px;
font-size: 24px !important;
}
</style>

@ -0,0 +1,736 @@
<template>
<div
:class="componentClasses"
>
<audio
ref="callSound"
loop
playsinline
preload="auto"
src="statics/ring.mp3"
/>
<div
v-show="isContentVisible"
class="csc-call-content"
>
<div
v-if="isInitiating || isRinging || isIncoming || isEnded"
class="csc-call-info full-height"
>
<div
class="row justify-center items-center full-height"
>
<div
class="csc-call-info-content col col-md-6 text-center"
>
<q-spinner-rings
v-if="isInitiating || isRinging || isIncoming"
class="csc-call-spinner"
color="primary"
:size="64"
/>
<div
v-if="isInitiating || isRinging || isIncoming"
class="csc-phone-number"
>
<q-icon
class="csc-media-icon"
v-if="isVideoCall"
name="videocam"
size="24px"
/>
<q-icon
class="csc-media-icon"
v-else
name="mic"
size="24px"
/>
<span
v-if="isInitiating"
>{{ $t('call.initiating', {number: callNumberFormatted}) }}</span>
<span
v-else-if="isRinging"
>{{ $t('call.ringing', {number: callNumberFormatted}) }}</span>
<span
v-else-if="isIncoming"
>{{ $t('call.incoming', {number: callNumberFormatted}) }}</span>
</div>
<div
v-else-if="isEnded"
class="csc-call-error"
>
{{ endedReason | startCase }} ({{callNumberFormatted}})
</div>
</div>
</div>
</div>
<div
v-else-if="isEstablished"
class="csc-call-info-established"
>
<div
class="row justify-center items-center"
>
<q-icon
class="csc-media-icon"
v-if="isVideoCall"
name="videocam"
size="24px"
/>
<q-icon
class="csc-media-icon"
v-else
name="mic"
size="24px"
/>
<div
>
{{ $t('call.established', {number: callNumberFormatted}) }}
</div>
<q-btn
v-if="!dialpadOpened"
class="csc-dialpad-button"
icon="dialpad"
round
small
@click="toggleDialpad"
color="default"
/>
<q-btn
v-else
class="csc-dialpad-button"
icon="expand_more"
round
small
@click="toggleDialpad"
color="default"
/>
</div>
<csc-call-dialpad
v-if="dialpadOpened"
:show-backspace-button="false"
:show-clear-button="false"
@click="dialpadClick"
/>
</div>
<div
class="csc-call-media-local"
>
<csc-media
v-show="isActive && !minimized && hasLocalVideo"
:muted="true"
:stream="localMediaStream"
fit="width"
/>
</div>
</div>
<div
v-show="isEstablished && !(isMobile && minimized)"
class="csc-call-media-remote transition-generic"
>
<div
v-show="!hasRemoteVideo && !minimized"
class="csc-call-media-icon row justify-center items-center full-height"
>
<q-icon
name="person"
size="128px"
color="white"
/>
</div>
<csc-media
v-show="hasRemoteVideo || minimized"
:muted="!remoteVolumeEnabled"
:stream="remoteMediaStream"
:fit="remoteMediaFit"
/>
</div>
<div
class="csc-call-content-minimized transition-generic"
@click="maximizeMobile"
>
<div
class="csc-call-actions row justify-center"
>
<q-btn
v-if="isEstablished && !(isMobile && minimized)"
class="csc-call-button"
:color="colorToggleMicrophone"
:icon="iconToggleMicrophone"
round
@click="toggleMicrophone()"
/>
<q-btn
v-if="isEstablished && hasLocalVideo && !(isMobile && minimized)"
class="csc-call-button"
:color="colorToggleCamera"
:icon="iconToggleCamera"
round
@click="toggleCamera()"
/>
<q-btn
v-if="isEstablished && !(isMobile && minimized)"
class="csc-call-button"
:color="colorToggleRemoteVolume"
:icon="iconToggleRemoteVolume"
round
@click="toggleRemoteVolume()"
/>
<q-btn
v-if="isActive"
class="csc-call-button"
color="negative"
icon="call_end"
round
@click="endCall"
/>
<q-btn
v-if="canClose"
class="csc-call-button"
color="negative"
icon="clear"
round
@click="closeCall()"
/>
<q-fab
ref="startButton"
v-if="canStart"
class="csc-call-button"
color="primary"
icon="call"
direction="up"
>
<q-fab-action
v-if="!isMobile"
color="primary"
icon="computer"
@click="startCall('audioScreen')"
/>
<q-fab-action
color="primary"
icon="videocam"
@click="startCall('audioVideo')"
/>
<q-fab-action
color="primary"
icon="mic"
@click="startCall('audioOnly')"
/>
</q-fab>
</div>
<div
v-if="minimized"
class="csc-call-info-minimized"
>
<div
v-if="isCalling"
class="csc-call-info-loading"
>
<q-spinner-rings
class="csc-call-spinner"
color="primary"
size="64px"
/>
</div>
<div
class="csc-call-info-icon"
>
<q-icon
class="csc-media-icon"
v-if="isActive && isVideoCall"
name="videocam"
size="24px"
color="white"
/>
<q-icon
class="csc-media-icon"
v-else-if="isActive"
name="mic"
size="24px"
color="white"
/>
<q-icon
class="csc-media-icon"
v-else-if="isEnded"
name="error"
size="24px"
color="white"
/>
</div>
<div
v-if="isActive"
class="csc-call-info-text"
>
<div
v-if="isActive"
>
<div
class="csc-call-info-phrase"
>
{{ $t('call.' + callState + 'Short') }}
</div>
<div
class="csc-call-info-number"
>
{{ callNumberFormatted }}
</div>
</div>
</div>
<div
v-else-if="isEnded"
class="csc-call-info-text csc-call-error"
>
<div
class="csc-call-info-phrase"
>
{{ endedReason | startCase }}
</div>
<div
class="csc-call-info-number"
>
{{ callNumberFormatted }}
</div>
</div>
</div>
<q-btn
class="csc-call-btn-fullscreen"
v-if="isEstablished && minimized && !isMobile"
icon="fullscreen"
round
color="default"
@click="maximize"
/>
<q-btn
class="csc-call-btn-fullscreen-small"
v-else-if="isEstablished && maximizable && !isMobile"
icon="fullscreen_exit"
round
small
color="default"
@click="minimize"
/>
</div>
</div>
</template>
<script>
import platformMixin from '../../mixins/platform'
import numberFormat from '../../filters/number-format'
import {
showCallNotification
} from '../../helpers/ui'
import {
normalizeDestination
} from '../../filters/number-format'
import {
QFab,
QFabAction,
QBtn,
QSpinnerRings,
QIcon
} from 'quasar-framework'
import CscMedia from '../CscMedia'
import CscCallDialpad from "../CscCallDialpad";
export default {
name: 'csc-call',
data() {
return {}
},
mixins: [
platformMixin
],
props: [
'callState',
'callNumber',
'endedReason',
'fullView',
'localMediaStream',
'remoteMediaStream',
'minimized',
'closed',
'isVideoCall',
'hasLocalVideo',
'hasRemoteVideo',
'microphoneEnabled',
'cameraEnabled',
'remoteVolumeEnabled',
'maximizable',
'dialpadOpened'
],
components: {
CscCallDialpad,
QFab,
QFabAction,
QBtn,
CscMedia,
QSpinnerRings,
QIcon
},
computed: {
remoteMediaFit() {
if(this.minimized) {
return 'width';
}
else {
return 'full';
}
},
componentClasses() {
let classes = [
'transition-generic',
'csc-call',
'csc-call-' + this.callState
];
if(this.fullView) {
classes.push('csc-call-full-width');
}
if(this.minimized) {
classes.push('csc-call-minimized');
}
if(this.isVideoCall) {
classes.push('csc-call-video');
}
if(this.isMobile) {
classes.push('csc-call-mobile');
}
return classes;
},
isCalling() {
return this.isInitiating || this.isRinging || this.isIncoming;
},
isActive() {
return this.isCalling || this.isEstablished;
},
isInitiating() {
return this.callState === 'initiating';
},
isRinging() {
return this.callState === 'ringing';
},
isEstablished() {
return this.callState === 'established';
},
isIncoming() {
return this.callState === 'incoming';
},
isEnded() {
return this.callState === 'ended';
},
canStart() {
return this.callState === 'input' || this.callState === 'incoming';
},
canClose() {
return this.callState === 'ended';
},
callNumberFormatted() {
return normalizeDestination(this.callNumber);
},
isContentVisible() {
return (!this.minimized && (this.isInitiating ||
this.isRinging || this.isIncoming ||
this.isEnded || this.isEstablished));
},
iconToggleMicrophone() {
if(this.microphoneEnabled) {
return 'mic';
}
else {
return 'mic_off';
}
},
colorToggleMicrophone() {
if(this.microphoneEnabled) {
return 'primary';
}
else {
return 'faded';
}
},
iconToggleCamera() {
if(this.cameraEnabled) {
return 'videocam';
}
else {
return 'videocam_off';
}
},
colorToggleCamera() {
if(this.cameraEnabled) {
return 'primary';
}
else {
return 'faded';
}
},
iconToggleRemoteVolume() {
if(this.remoteVolumeEnabled) {
return 'volume_up';
}
else {
return 'volume_off';
}
},
colorToggleRemoteVolume() {
if(this.remoteVolumeEnabled) {
return 'primary';
}
else {
return 'faded';
}
}
},
methods: {
startCall(media) {
if(this.callState === 'input') {
this.$emit('start-call', media);
}
else if(this.callState === 'incoming') {
this.$emit('accept-call', media);
}
},
endCall(event) {
event.stopPropagation();
this.$emit('end-call');
},
closeCall() {
this.$emit('close-call');
},
playCallSound() {
this.$refs.callSound.play();
},
stopCallSound() {
this.$refs.callSound.pause();
},
toggleDialpad() {
this.$emit('toggle-dialpad');
},
toggleMicrophone() {
this.$emit('toggle-microphone');
},
toggleCamera() {
this.$emit('toggle-camera');
},
toggleRemoteVolume() {
this.$emit('toggle-remote-volume');
},
dialpadClick(value) {
this.$emit('click-dialpad', value);
},
maximize() {
this.$emit('maximize-call');
},
minimize() {
this.$emit('minimize-call');
},
maximizeMobile() {
if(this.isMobile && this.isEstablished) {
this.maximize();
}
}
},
watch: {
callState(state) {
if(state === 'ringing' || state === 'incoming') {
this.playCallSound();
if(state === 'incoming') {
showCallNotification(numberFormat(this.callNumber));
}
}
else {
this.stopCallSound();
}
},
closed(closed) {
if(closed && this.$refs.startButton) {
this.$refs.startButton.close();
}
}
}
}
</script>
<style lang="stylus" rel="stylesheet/stylus">
@import '../../themes/quasar.variables'
$call-footer-height = $toolbar-min-height
$call-footer-height-big = $call-footer-height * 2
$call-footer-action-margin = 27px
$call-footer-height-mobile = $call-footer-height * 1.4
.csc-call
left $layout-aside-left-width
top $call-footer-height
position fixed
bottom 0
right 0
z-index 40
.q-btn.q-btn-round
box-shadow none
.csc-call-content
position absolute
bottom $call-footer-height
top 0
right 0
left 0
z-index 2
background-color white
.csc-call-info
.csc-call-info-content
margin-top -80px
.csc-call-error
color $negative
.csc-call-spinner
margin-bottom $flex-gutter-sm
.csc-call-media-local
position absolute
bottom $flex-gutter-sm
left $flex-gutter-sm
width 20%
height auto
overflow hidden
z-index 2
font-size 0
.csc-call-info-established
position absolute
bottom $call-footer-action-margin * 2
right 0
left 0
justify-items center
z-index 3
color white
.csc-media-icon
margin-right $flex-gutter-xs
.csc-dialpad-button
vertical-align center
margin-left $flex-gutter-sm
.csc-call-media-remote
position absolute
top 0
bottom $call-footer-height
right 0
left 0
z-index 1
background-color black
font-size 0
.csc-call-media-icon
opacity 0.5
.csc-call-content-minimized
position absolute
bottom 0
left 0
right 0
height $call-footer-height
background-color $secondary
z-index 3
display flex
flex-wrap no-wrap
align-items center
justify-content center
.csc-call-info-minimized
display flex
flex-wrap no-wrap
align-items center
margin-bottom -16px
.csc-call-info-text
color white
padding-left $flex-gutter-xs
.csc-call-info-phrase
margin-bottom 4px
.csc-call-info-number
font-size 14px
.csc-call-info-loading
margin-right $flex-gutter-sm
.csc-call-btn-fullscreen
position absolute
right $flex-gutter-md
top 50%
bottom 50%
margin-top -27px
.csc-call-btn-fullscreen-small
position absolute
right $flex-gutter-sm
top 50%
bottom 50%
margin-top -20px
.csc-call-actions
position absolute
left 0
right 0
top 0
margin-top -27px
.q-btn.q-btn-round
box-shadow none
.csc-call-button
margin-right $flex-gutter-sm
.csc-call-button:last-child
margin-right 0
.csc-phone-number
color white
text-align center
.csc-call.csc-call-input
top auto
.csc-call.csc-call-established
.csc-call-content
background-color transparent
.csc-call.csc-call-minimized
height $call-footer-height-big
top auto
bottom ($call-footer-height-big + $call-footer-action-margin) * -1
.csc-call-media-remote
top auto
bottom $call-footer-height-big + $flex-gutter-sm
right $flex-gutter-sm
left auto
height auto
width 20%
z-index 1
font-size 0
.csc-call-content-minimized
height $call-footer-height-big
.csc-call-actions
margin-bottom $call-footer-action-margin
.csc-call.csc-call-minimized.csc-call-incoming,
.csc-call.csc-call-minimized.csc-call-initiating,
.csc-call.csc-call-minimized.csc-call-ringing
.csc-call-actions
margin-bottom 8px
.csc-call.csc-call-minimized.csc-call-incoming,
.csc-call.csc-call-minimized.csc-call-initiating,
.csc-call.csc-call-minimized.csc-call-ringing,
.csc-call.csc-call-minimized.csc-call-established,
.csc-call.csc-call-minimized.csc-call-ended
bottom 0
.csc-call.csc-call-full-width
left 0
.csc-call.csc-call-mobile
.csc-call-content
.csc-call-media-local
font-size 0
top $flex-gutter-sm
left $flex-gutter-sm
bottom auto
width 30%
height auto
overflow hidden
.csc-call.csc-call-mobile.csc-call-minimized
.csc-call-content-minimized
height $call-footer-height-big
.csc-call.csc-call-mobile.csc-call-minimized.csc-call-ended,
.csc-call.csc-call-mobile.csc-call-minimized.csc-call-established
.csc-call-content-minimized
height $call-footer-height * 1.4
justify-content left
padding-left $flex-gutter-sm
.csc-call-info-minimized
margin-bottom 0
.csc-call-actions
position absolute
margin auto
margin-top -27px
top 50%
right $flex-gutter-sm
left auto
.csc-call-button
margin-right $flex-gutter-sm
.csc-call-button:last-child
margin-right 0
</style>

@ -1,67 +1,41 @@
<template>
<q-field
v-if="!isMobile"
dark
:dark="dark"
:count="maxLength"
:helper="helperMessage"
:error-label="errorMessage"
:disabled="!enabled"
>
<q-input
ref="inputField"
dark
:dark="dark"
clearable
type="text"
type="tel"
:float-label="$t('call.number')"
:readonly="!enabled"
v-model="phoneNumber"
@keypress.space.prevent
@keydown.space.prevent
@keyup.space.prevent
:after="inputButtons"
@keyup.enter="keyReturn()"
@input="$v.phoneNumber.$touch"
@blur="$v.phoneNumber.$touch"
:value="value"
:disable="!enabled"
:error="$v.phoneNumber.$error"
/>
</q-field>
<q-field
v-else
dark
:error-label="errorMessage"
>
<q-input
ref="inputField"
dark
clearable
type="text"
:placeholder="$t('call.number')"
:readonly="!enabled"
v-model="phoneNumber"
@keypress.space.prevent
@keydown.space.prevent
@keyup.space.prevent
:after="inputButtons"
@input="$v.phoneNumber.$touch"
@blur="$v.phoneNumber.$touch"
:error="$v.phoneNumber.$error"
@keyup.enter="keyReturn()"
@input="inputNumber"
/>
</q-field>
</template>
<script>
import Vue from 'vue'
import _ from 'lodash'
import { userInfo } from '../../helpers/validation'
import {
maxLength,
required
userInfoAndEmpty
} from '../../helpers/validation'
import {
maxLength
} from 'vuelidate/lib/validators'
import {
QField,
QInput
} from 'quasar-framework'
import platformMixin from '../../mixins/platform'
export default {
name: 'csc-phone-number-input',
components: {
@ -70,14 +44,13 @@
},
data () {
return {
phoneNumber: ''
phoneNumber: this.value
}
},
validations: {
phoneNumber: {
userInfo,
maxLength: maxLength(64),
required
userInfoAndEmpty,
maxLength: maxLength(64)
}
},
props: {
@ -89,9 +62,13 @@
type: Boolean,
default: true
},
dialpadButton: {
dark: {
type: Boolean,
default: false
default: true
},
value: {
type: String,
default: ''
}
},
mixins: [
@ -116,64 +93,38 @@
},
helperMessage() {
return this.$t('validationErrors.inputNumber');
},
inputReadonly() {
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: {
inputNumber(input) {
this.phoneNumber = input;
this.$v.phoneNumber.$touch();
this.$emit('number-changed', this.phoneNumber);
},
keyReturn() {
this.$emit('key-return');
},
getPhoneNumber() {
return this.phoneNumber;
},
hasPhoneNumber() {
return !this.$v.phoneNumber.$error && !_.isEmpty(this.phoneNumber);
},
concat(str) {
this.phoneNumber = this.phoneNumber + "" + str;
},
focusInput() {
focus() {
Vue.nextTick(() => {
this.$refs.inputField.focus();
if(this.$refs.inputField) {
this.$refs.inputField.focus();
}
});
},
blurInput() {
blur() {
Vue.nextTick(() => {
this.$refs.inputField.blur();
if(this.$refs.inputField) {
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');
}
},
watch: {
value() {
this.phoneNumber = this.value;
}
}
}
</script>
<style lang="stylus" rel="stylesheet/stylus">
</style>

@ -1,10 +1,10 @@
<template>
<q-layout
:class="layoutClasses"
ref="layout"
:view="layoutView"
:right-breakpoint="1100"
@right-breakpoint="rightBreakPoint"
:right-class="callClasses"
view="lHh LpR lFf"
v-model="sideStates"
@left-breakpoint="leftBreakpoint"
>
<q-toolbar slot="header">
<q-btn
@ -14,9 +14,9 @@
<q-icon name="menu" />
</q-btn>
<q-toolbar-title>
{{ pageTitle }}
{{ pageTitleExt }}
<span slot="subtitle">
{{ pageSubtitle }}
{{ pageSubtitleExt }}
</span>
</q-toolbar-title>
<q-btn
@ -31,16 +31,6 @@
link
class="csc-toolbar-btn-popover"
>
<q-item
@click="call();$refs.communicationPopover.close()"
v-if="isCallAvailable"
>
<q-item-side
icon="fa-phone"
color="primary"
/>
<q-item-main :label="$t('startCall')" />
</q-item>
<q-item
@click="showSendFax();$refs.communicationPopover.close()"
v-if="hasFaxCapability && hasSendFaxFeature"
@ -112,10 +102,10 @@
item
to="/user/home"
>
<q-item-side icon="home" />
<q-item-side icon="call" />
<q-item-main
:label="$t('navigation.home.title')"
:sublabel="$t('navigation.home.subTitle')"
:label="callStateTitle"
:sublabel="callStateSubtitle"
/>
</q-side-link>
<q-side-link
@ -244,21 +234,46 @@
</q-side-link>
</q-list>
<router-view />
<csc-send-fax
ref="sendFax"
/>
<csc-call
ref="cscCall"
slot="right"
@close="closeCall()"
@fullscreen="toggleFullscreen()"
:fullscreen="isFullscreenEnabled"
region="DE"
:call-state="callState"
:call-number="callNumber"
:ended-reason="endedReason"
:full-view="isFullView"
:minimized="!isHome && !isMaximized"
:maximizable="!isHome"
:closed="!isCalling && !isHome"
:local-media-stream="localMediaStream"
:remote-media-stream="remoteMediaStream"
:is-video-call="hasVideo"
:has-local-video="hasLocalVideo"
:has-remote-video="hasRemoteVideo"
:microphone-enabled="isMicrophoneEnabled"
:camera-enabled="isCameraEnabled"
:remote-volume-enabled="isRemoteVolumeEnabled"
:dialpad-opened="isDialpadOpened"
@start-call="startCall"s
@accept-call="acceptCall"
@end-call="endCall"
@close-call="closeCall"
@toggle-microphone="toggleMicrophone"
@toggle-camera="toggleCamera"
@toggle-remote-volume="toggleRemoteVolume"
@click-dialpad="clickDialpad"
@toggle-dialpad="toggleDialpad"
@maximize-call="maximizeCall"
@minimize-call="minimizeCall"
/>
<q-window-resize-observable @resize="onWindowResize" />
<csc-send-fax ref="sendFax" />
</q-layout>
</template>
<script>
import _ from 'lodash';
import {
normalizeDestination
} from '../../filters/number-format'
import platformMixin from '../../mixins/platform'
import {
startLoading,
stopLoading,
@ -266,8 +281,10 @@
showGlobalError,
enableIncomingCallNotifications
} from '../../helpers/ui'
import { mapState, mapGetters } from 'vuex'
import CscCall from '../CscCall'
import {
mapGetters
} from 'vuex'
import CscCall from '../call/CscCall'
import CscSendFax from '../CscSendFax'
import {
QLayout,
@ -282,23 +299,25 @@
QItemMain,
QPopover,
QSideLink,
QCollapsible,
Platform,
QWindowResizeObservable
QCollapsible
} from 'quasar-framework'
export default {
name: 'default',
mounted: function() {
if(Platform.is.mobile) {
this.$store.commit('layout/hideLeft');
this.$store.commit('layout/enableFullscreen');
}
else {
this.$store.commit('layout/showLeft');
data() {
return {
sideStates: {
left: true,
right: false
},
mobileMenu: null
}
this.applyLayout();
},
mounted: function() {
this.$store.dispatch('user/initUser');
},
mixins: [
platformMixin
],
components: {
QLayout,
QToolbar,
@ -314,19 +333,38 @@
QSideLink,
QCollapsible,
CscCall,
QWindowResizeObservable,
CscSendFax
},
computed: {
...mapGetters('layout', [
'right',
'left',
'isFullscreenEnabled'
...mapGetters([
'pageTitle',
'pageSubtitle',
'isCallForward',
'isCallBlocking',
'isPbxConfiguration',
'isHome',
'title'
]),
...mapGetters('call', [
'isCallAvailable',
'callState',
'callNumber',
'callNumberInput',
'endedReason',
'isCalling',
'hasCallInitFailure'
'localMediaStream',
'remoteMediaStream',
'isCallAvailable',
'hasCallInitError',
'hasVideo',
'hasLocalVideo',
'hasRemoteVideo',
'isMicrophoneEnabled',
'isCameraEnabled',
'isRemoteVolumeEnabled',
'isMaximized',
'isDialpadOpened',
'callStateTitle',
'callStateSubtitle'
]),
...mapGetters('user', [
'isLogged',
@ -344,50 +382,41 @@
'createFaxState',
'createFaxError'
]),
...mapState({
isCallForward: state => _.startsWith(state.route.path, '/user/call-forward'),
isCallBlocking: state => _.startsWith(state.route.path, '/user/call-blocking'),
isPbxConfiguration: state => _.startsWith(state.route.path, '/user/pbx-configuration')
}),
hasCommunicationCapabilities() {
return this.isCallAvailable ||
(this.hasSmsCapability && this.hasSendSmsFeature) ||
return (this.hasSmsCapability && this.hasSendSmsFeature) ||
(this.hasFaxCapability && this.hasSendFaxFeature);
},
callClasses() {
let classes = {};
if(this.isFullscreenEnabled) {
classes['csc-call-fullscreen'] = true;
}
isMenuClosed() {
return !this.sideStates.left;
},
isFullView() {
return this.isMenuClosed || this.isMobile || this.mobileMenu;
},
layoutClasses() {
let classes = [];
if(this.isCalling) {
classes['csc-call-calling'] = true;
classes.push('csc-layout-call-active');
}
return classes;
},
layoutView() {
if(this.isFullscreenEnabled) {
return 'lHr LpR lFr';
callNumberFormatted() {
return normalizeDestination(this.callNumber);
},
pageTitleExt() {
if(this.isHome) {
return this.callStateTitle;
}
else {
return 'lHh LpR lFf';
return this.pageTitle;
}
},
fabOffset() {
if(Platform.is.mobile) {
return [16, 16];
pageSubtitleExt() {
if(this.isHome) {
return this.callStateSubtitle;
}
else {
return [32, 32];
return this.pageSubtitle;
}
},
isDesktop() {
return Platform.is.desktop;
},
pageTitle() {
return this.$store.getters['pageTitle'];
},
pageSubtitle() {
return this.$store.getters['pageSubtitle'];
}
},
methods: {
@ -397,14 +426,45 @@
hideSendFax() {
this.$refs.sendFax.hideModal();
},
onWindowResize() {
startCall(localMedia) {
if(this.callNumberInput !== '' && this.callNumberInput !== null) {
this.$store.dispatch('call/start', localMedia);
}
},
acceptCall(localMedia) {
this.$store.dispatch('call/accept', localMedia);
},
closeCall() {
this.$store.commit('call/inputNumber');
},
endCall() {
this.$store.dispatch('call/end');
},
toggleMicrophone() {
this.$store.dispatch('call/toggleMicrophone');
},
toggleFullscreen() {
this.$store.commit('layout/toggleFullscreen');
toggleCamera() {
this.$store.dispatch('call/toggleCamera');
},
call() {
this.$refs.layout.showRight();
this.$store.dispatch('call/showCall');
toggleRemoteVolume() {
this.$store.dispatch('call/toggleRemoteVolume');
},
clickDialpad(value) {
this.$store.dispatch('call/sendDTMF', value);
},
toggleDialpad() {
this.$store.commit('call/toggleDialpad');
},
maximizeCall() {
if(this.isMobile) {
this.$router.push('home');
}
else {
this.$store.commit('call/maximize');
}
},
minimizeCall() {
this.$store.commit('call/minimize');
},
logout() {
startLoading();
@ -413,53 +473,30 @@
this.$router.push({path: '/login'});
})
},
rightBreakPoint() {
if(this.right) {
this.$store.commit('layout/showRight');
this.$store.commit('layout/hideLeft');
}
else {
this.$store.commit('layout/hideRight');
}
leftBreakpoint(enabled) {
this.mobileMenu = !enabled;
},
closeCall() {
this.$refs.layout.hideRight();
this.$store.commit('layout/hideRight');
},
applyLayout() {
if(this.right) {
this.$refs.layout.showRight();
this.$refs.cscCall.focusNumberInput();
}
else {
this.$refs.layout.hideRight();
this.$refs.cscCall.blurNumberInput();
}
if(this.left) {
this.$refs.layout.showLeft();
}
else {
this.$refs.layout.hideLeft();
setCallStateTitle() {
let title = this.callStateTitle;
if(this.callStateSubtitle !== '') {
title = title + " (" + this.callStateSubtitle + ")";
}
document.title = this.title + " - " + title;
}
},
watch: {
right(value) {
if(value) {
this.$refs.layout.showRight();
this.$refs.cscCall.focusNumberInput();
callState(state) {
if(state === 'incoming' && this.isMobile) {
this.$refs.layout.hideLeft();
}
else {
this.$refs.layout.hideRight();
this.$refs.cscCall.blurNumberInput();
if(this.isHome) {
this.setCallStateTitle();
}
},
left(value) {
if(value) {
this.$refs.layout.showLeft();
}
else {
this.$refs.layout.hideLeft();
isHome(isHome) {
if(isHome) {
this.$store.commit('call/minimize');
this.setCallStateTitle();
}
},
userDataRequesting(value) {
@ -478,7 +515,7 @@
showToast(this.$i18n.t('toasts.callAvailable'));
}
},
hasCallInitFailure(value) {
hasCallInitError(value) {
if(value) {
showToast(this.$i18n.t('toasts.callNotAvailable'));
}
@ -496,176 +533,68 @@
showToast(this.$t('communication.createFaxSuccessMessage'));
this.hideSendFax();
}
},
$route () {
if(!this.isHome) {
this.$store.commit('call/minimize');
}
}
}
}
</script>
<style lang="stylus" rel="stylesheet/stylus">
@import '../../themes/quasar.variables';
@import '../../themes/app.common';
#main-menu {
padding-top:60px;
}
#main-menu .q-item-side {
min-width: 30px;
}
#main-menu .q-item {
padding: 12px 24px;
}
#main-menu .router-link-active,
#main-menu .q-item:hover {
background-color: #475360;
}
#main-menu .q-item .q-item-sublabel {
color: #5b7086;
}
#main-menu .q-item .q-item-main,
#main-menu .q-item .q-item-side {
color: #ADB3B8;
}
#main-menu .q-collapsible-sub-item {
padding: 0;
}
#main-menu .q-collapsible-sub-item .q-item {
padding-left: 60px;
}
#user-login-as {
display: inline-block;
text-transform: none;
color: #c5eab4;
}
#user-login-as:after {
content: " ";
white-space: pre;
}
#user-name {
font-weight: bold;
}
.q-card {
margin: 15px;
margin-left: 0px;
margin-right: 0px;
}
.q-card.page {
padding: 0px;
margin: 0;
}
.layout-aside.fixed.csc-call-fullscreen {
left: 0;
right: 0;
top: 0;
bottom: 0;
width: auto;
z-index: 5000;
}
.csc-call-fullscreen .csc-call,
.csc-call-fullscreen .csc-call .q-card {
}
.csc-call-fullscreen .csc-call .q-card .q-card-primary {
position: absolute;
top: 0;
right: 0;
left: 0;
height: 72px;
line-height: 72px;
z-index: 6001;
background: -moz-linear-gradient(top, rgba(51,64,77,1) 0%, rgba(235,236,237,0) 90%, rgba(255,255,255,0) 100%);
background: -webkit-linear-gradient(top, rgba(51,64,77,1) 0%,rgba(235,236,237,0) 90%,rgba(255,255,255,0) 100%);
background: linear-gradient(to bottom, rgba(51,64,77,1) 0%,rgba(235,236,237,0) 90%,rgba(255,255,255,0) 100%);
}
.csc-call-fullscreen .csc-call .q-card-actions {
}
.csc-call-fullscreen .csc-call .q-card-main {
position: absolute;
top: 0;
right: 0;
left: 0;
bottom: 0;
z-index: 6000;
font-size: 0;
}
.csc-call-fullscreen .csc-call-media {
position: absolute;
top: 0;
bottom: 0;
right: 0;
left: 0;
z-index: 1;
}
.csc-media-remote {
z-index: 9;
}
.csc-call-fullscreen .csc-media-preview {
position: absolute;
bottom: 0;
left: 0;
width: 20%;
}
.csc-call-fullscreen .csc-media-preview video {
position: relative;
height: 100%;
}
.csc-call-fullscreen .csc-media-remote {
position: absolute;
top: 0;
bottom: 0;
right: 0;
left: 0;
}
.csc-call-fullscreen .csc-media-remote video {
position: absolute;
height: 100%;
bottom: 0;
}
.csc-call-fullscreen .csc-call-info {
position: relative;
top: 73px;
z-index: 2;
}
@import '../../themes/app.common'
.page.page-call-active
padding-bottom 120px
#main-menu
padding-top 60px
.q-item-side
min-width 30px
.q-item
padding 12px 24px
.q-item-sublabel
color #5b7086
.q-item-main,
.q-item-side
color #ADB3B8
.router-link-active,
.q-item:hover
background-color #475360
.q-collapsible-sub-item
padding 0
.q-item
padding-left 60px
#user-login-as
display inline-block
text-transform none
color #c5eab4
#user-login-as:after
content " "
white-space pre
#user-name
font-weight bold
.q-card
margin 15px
margin-left 0
margin-right 0
.q-card.page
padding 0
margin 0
.q-if-control.q-if-control-before.q-icon,
.q-if-control.q-if-control-before.q-icon:before {
font-size:24px;
}
.q-if-control.q-if-control-before.q-icon:before
font-size 24px
.csc-toolbar-btn-popover
.q-item-main.q-item-section
margin-left 0
.q-toolbar
.csc-toolbar-btn.q-btn
padding-left 8px
padding-right 8px
.csc-toolbar-btn-right
.csc-toolbar-btn-icon
margin-right 8px
.csc-layout-call-active
padding-bottom 152px
</style>

@ -53,7 +53,7 @@
:key="item._id"
:item="item"
:call-available="isCallAvailable"
@init-call="initCall"
@start-call="startCall"
@download-fax="downloadFax"
@download-voice-mail="downloadVoiceMail"
@play-voice-mail="playVoiceMail"
@ -208,12 +208,9 @@
}
this.$store.dispatch('conversations/nextPage', type);
},
initCall(call) {
this.layout.showRight();
this.$store.dispatch('call/start', {
number: call.number,
localMedia: call.media
});
startCall(number) {
this.$store.commit('call/numberInputChanged', number);
this.$router.push('home');
},
downloadFax(fax) {
this.$store.dispatch('conversations/downloadFax', fax.id);

@ -50,17 +50,32 @@
>
<q-item-tile>
<q-btn
v-if="callAvailable"
icon="call"
color="primary"
icon="more_vert"
color="default"
slot="right"
flat
>
<q-popover ref="callPopover" anchor="bottom right" self="top right">
<csc-call-option-list
ref="callOptionPopover"
@init-call="initCall"
/>
<q-popover
ref="callPopover"
anchor="bottom right"
self="top right">
<q-list
item-separator
link
class="csc-toolbar-btn-popover">
<q-item
v-if="callAvailable"
@click="startCall"
>
<q-item-side
icon="call"
color="primary"
/>
<q-item-main
:label="$t('pages.conversations.buttons.call')"
/>
</q-item>
</q-list>
</q-popover>
</q-btn>
</q-item-tile>
@ -72,6 +87,7 @@
import _ from 'lodash'
import CscCallOptionList from './CscCallOptionList'
import {
QList,
QItem,
QItemSide,
QItemMain,
@ -87,6 +103,7 @@
'callAvailable'
],
components: {
QList,
QItem,
QItemSide,
QItemMain,
@ -170,12 +187,9 @@
}
},
methods: {
initCall(media) {
startCall() {
this.$refs.callPopover.close();
this.$emit('init-call', {
media: media,
number: this.numberDialBack
});
this.$emit('start-call', this.numberDialBack);
}
}
}

@ -3,22 +3,22 @@
v-if="item.type == 'call'"
:call="item"
:call-available="callAvailable"
@init-call="initCall"
@start-call="startCall"
/>
<csc-fax-item
v-else-if="item.type == 'fax'"
:fax="item"
:call-available="callAvailable"
@init-call="initCall"
@download-fax="downloadFax"
@start-call="startCall"
/>
<csc-voice-mail-item
v-else-if="item.type == 'voicemail'"
:voice-mail="item"
:call-available="callAvailable"
@init-call="initCall"
@download-voice-mail="downloadVoiceMail"
@play-voice-mail="playVoiceMail"
@start-call="startCall"
/>
</template>
@ -41,8 +41,8 @@
return {}
},
methods: {
initCall(call) {
this.$emit('init-call', call);
startCall(number) {
this.$emit('start-call', number);
},
downloadFax(fax) {
this.$emit('download-fax', fax);

@ -45,25 +45,43 @@
>
<q-item-tile>
<q-btn
icon="file_download"
color="primary"
icon="more_vert"
color="default"
slot="right"
flat
@click="downloadFax"
>
</q-btn>
<q-btn
v-if="callAvailable"
icon="call"
color="primary"
slot="right"
flat
>
<q-popover ref="callPopover" anchor="bottom right" self="top right">
<csc-call-option-list
ref="callOptionPopover"
@init-call="initCall"
/>
<q-popover
ref="callPopover"
anchor="bottom right"
self="top right">
<q-list
item-separator
link
class="csc-toolbar-btn-popover">
<q-item
@click="downloadFax"
>
<q-item-side
icon="file_download"
color="primary"
/>
<q-item-main
:label="$t('pages.conversations.buttons.downloadFax')"
/>
</q-item>
<q-item
v-if="callAvailable"
@click="startCall"
>
<q-item-side
icon="call"
color="primary"
/>
<q-item-main
:label="$t('pages.conversations.buttons.call')"
/>
</q-item>
</q-list>
</q-popover>
</q-btn>
</q-item-tile>
@ -74,6 +92,7 @@
<script>
import CscCallOptionList from './CscCallOptionList'
import {
QList,
QItem,
QItemSide,
QItemMain,
@ -88,6 +107,7 @@
'callAvailable'
],
components: {
QList,
QItem,
QItemSide,
QItemMain,
@ -113,15 +133,12 @@
}
},
methods: {
initCall(media) {
this.$refs.callPopover.close();
this.$emit('init-call', {
media: media,
number: this.fax.caller
});
},
downloadFax() {
this.$emit('download-fax', this.fax);
},
startCall() {
this.$refs.callPopover.close();
this.$emit('start-call', this.fax.caller);
}
}
}

@ -48,25 +48,44 @@
>
<q-item-tile>
<q-btn
icon="file_download"
color="primary"
icon="more_vert"
color="default"
slot="right"
flat
@click="downloadVoiceMail"
>
</q-btn>
<q-btn
v-if="callAvailable"
icon="call"
color="primary"
slot="right"
flat
>
<q-popover ref="callPopover" anchor="bottom right" self="top right">
<csc-call-option-list
ref="callOptionPopover"
@init-call="initCall"
/>
<q-popover
ref="callPopover"
anchor="bottom right"
self="top right"
>
<q-list
item-separator
link
class="csc-toolbar-btn-popover">
<q-item
@click="downloadVoiceMail"
>
<q-item-side
icon="file_download"
color="primary"
/>
<q-item-main
:label="$t('pages.conversations.buttons.downloadVoicemail')"
/>
</q-item>
<q-item
v-if="callAvailable"
@click="startCall"
>
<q-item-side
icon="call"
color="primary"
/>
<q-item-main
:label="$t('pages.conversations.buttons.call')"
/>
</q-item>
</q-list>
</q-popover>
</q-btn>
</q-item-tile>
@ -75,10 +94,13 @@
</template>
<script>
import { mapGetters } from 'vuex'
import {
mapGetters
} from 'vuex'
import CscCallOptionList from './CscCallOptionList'
import CscAudioPlayer from '../../CscAudioPlayer'
import {
QList,
QItem,
QItemSide,
QItemMain,
@ -93,6 +115,7 @@
'callAvailable'
],
components: {
QList,
QItem,
QItemSide,
QItemMain,
@ -132,13 +155,6 @@
}
},
methods: {
initCall(media) {
this.$refs.callPopover.close();
this.$emit('init-call', {
media: media,
number: this.voiceMail.callee
});
},
playVoiceMail() {
this.$emit('play-voice-mail', {
id: this.voiceMail.id,
@ -152,6 +168,10 @@
this.playVoiceMail();
this.$refs.voicemailPlayer.setPlayingTrue();
this.$refs.voicemailPlayer.setPausedFalse();
},
startCall() {
this.$refs.callPopover.close();
this.$emit('start-call', this.voiceMail.callee);
}
}
}

@ -1,176 +1,99 @@
<template>
<csc-page :title="$t('pages.home.title')" id="csc-page-home">
<div :class="gridClasses">
<div class="col-lg-4 col-md-6 col-sm-6 col-xs-6">
<div @click="call()">
<q-card :class="{ 'home-card-inactive': !isCallAvailable,
'home-card-active': isCallAvailable }"
class="no-margin"
flat>
<q-card-main align="center">
<q-icon name="call" class="home-icons" />
</q-card-main>
<q-card-actions align="center">
{{ $t('pages.home.cards.voiceCall') }}
</q-card-actions>
</q-card>
</div>
</div>
<div class="col-lg-4 col-md-6 col-sm-6 col-xs-6">
<div @click="call()">
<q-card :class="{ 'home-card-inactive': !isCallAvailable,
'home-card-active': isCallAvailable }"
class="no-margin"
flat>
<q-card-main align="center">
<q-icon name="video call" class="home-icons" />
</q-card-main>
<q-card-actions align="center">
{{ $t('pages.home.cards.videoCall') }}
</q-card-actions>
</q-card>
</div>
</div>
<div class="col-lg-4 col-md-6 col-sm-6 col-xs-6">
<div @click="screenShare()">
<q-card :class="{ 'home-card-inactive': !isCallAvailable || isMobile,
'home-card-active': isCallAvailable && !isMobile }"
class="no-margin"
flat>
<q-card-main align="center">
<q-icon name="screen share" class="home-icons" />
</q-card-main>
<q-card-actions align="center">
{{ $t('pages.home.cards.screenShare') }}
</q-card-actions>
</q-card>
</div>
</div>
<div class="col-lg-4 col-md-6 col-sm-6 col-xs-6">
<router-link to="/user/call-forward/always">
<q-card class="home-card-active no-margin" flat>
<q-card-main align="center">
<q-icon name="phone forwarded" class="home-icons" />
</q-card-main>
<q-card-actions align="center">
{{ $t('pages.home.cards.callForward') }}
</q-card-actions>
</q-card>
</router-link>
</div>
<div class="col-lg-4 col-md-6 col-sm-6 col-xs-6">
<router-link to="/user/call-blocking/incoming">
<q-card class="home-card-active no-margin" flat>
<q-card-main align="center">
<q-icon name="block" class="home-icons" />
</q-card-main>
<q-card-actions align="center">
{{ $t('pages.home.cards.callBlocking') }}
</q-card-actions>
</q-card>
</router-link>
</div>
<div class="col-lg-4 col-md-6 col-sm-6 col-xs-6">
<router-link to="/user/reminder">
<q-card class="home-card-active no-margin" flat>
<q-card-main align="center">
<q-icon name="fa-bell" class="home-icons" />
</q-card-main>
<q-card-actions align="center">
{{ $t('pages.home.cards.reminder') }}
</q-card-actions>
</q-card>
</router-link>
</div>
<div class="col-lg-4 col-md-6 col-sm-6 col-xs-6">
<router-link to="/user/speeddial">
<q-card class="home-card-active no-margin" flat>
<q-card-main align="center">
<q-icon name="touch app" class="home-icons" />
</q-card-main>
<q-card-actions align="center">
{{ $t('pages.home.cards.speeddial') }}
</q-card-actions>
</q-card>
</router-link>
</div>
<div class="col-lg-4 col-md-6 col-sm-6 col-xs-6">
<q-card @click="buddyList()" class="home-card-inactive no-margin" flat>
<q-card-main align="center">
<q-icon name="contacts" class="home-icons" />
</q-card-main>
<q-card-actions align="center">
{{ $t('pages.home.cards.buddyList') }}
</q-card-actions>
</q-card>
<div
class="csc-call-page row justify-center items-center"
>
<div
class="col col-xs-10 col-md-6 col-lg-4"
>
<div
class="csc-call-page-content"
>
<q-alert
v-if="desktopSharingInstall"
v-model="desktopSharingInstall"
color="warning"
:actions="desktopSharingAlertActions"
>
{{ $t('call.desktopSharingNotInstalled') }}
</q-alert>
<q-alert
class="csc-inline-alert"
appear
icon="info"
color="info"
v-if="!hasRtcEngineCapabilityEnabled"
:actions="rtcEngineInfoActions"
>
{{ $t('call.rtcEngineNotEnabled') }}
</q-alert>
<csc-phone-number-input
class="csc-call-phone-number"
:dark="false"
:value="callNumberInput"
:enabled="hasRtcEngineCapabilityEnabled"
@number-changed="numberInputChanged"
/>
</div>
</div>
</csc-page>
</div>
</template>
<script>
import {
getChromeExtensionUrl
} from '../../helpers/cdk-lib'
import {
mapGetters
} from 'vuex'
import CscPage from '../CscPage'
import CscCall from '../CscCall'
import { mapGetters } from 'vuex'
import { QCard, QCardMain, QCardActions, QIcon, Platform, scroll } from 'quasar-framework'
import { showGlobalWarning } from '../../helpers/ui'
import CscPhoneNumberInput from "../call/CscPhoneNumberInput";
import {
QIcon,
QAlert
} from 'quasar-framework'
export default {
data() {
return {
}
},
components: {
CscPhoneNumberInput,
CscPage,
CscCall,
QCard,
QCardMain,
QCardActions,
QIcon
QIcon,
QAlert
},
inject: ['layout'],
mounted() {
scroll.setScrollPosition(this.$el, 0, 100);
methods: {
numberInputChanged(number) {
this.$store.commit('call/numberInputChanged', number);
},
},
computed: {
...mapGetters('call', [
'isCallAvailable'
'callNumberInput',
'hasCallInitError',
'hasRtcEngineCapabilityEnabled',
'desktopSharingInstall'
]),
isMobile() {
return Platform.is.mobile;
rtcEngineInfoActions() {
return [];
},
gridClasses() {
let classes = ['row'];
if(this.isMobile) {
classes.push('sm-gutter');
}
else {
classes.push('md-gutter');
}
return classes;
}
},
methods: {
call() {
if(this.isCallAvailable) {
this.layout.showRight();
this.$store.dispatch('call/showCall');
}
else {
showGlobalWarning(this.$i18n.t('pages.home.featureNotAvailable'));
}
},
screenShare() {
if(this.isCallAvailable && !this.isMobile) {
this.layout.showRight();
this.$store.dispatch('call/showCall');
}
else {
showGlobalWarning(this.$i18n.t('pages.home.featureNotAvailable'));
}
},
buddyList() {
showGlobalWarning(this.$i18n.t('pages.home.featureNotAvailable'));
desktopSharingAlertActions() {
let self = this;
return [
{
label: 'Install',
handler () {
self.$store.commit('call/desktopSharingInstallReset');
window.open(getChromeExtensionUrl());
}
},
{
label: 'Cancel',
handler () {
self.$store.commit('call/desktopSharingInstallReset');
}
}
]
}
}
}
@ -178,47 +101,16 @@
<style lang="stylus" rel="stylesheet/stylus">
@import '../../themes/quasar.variables'
#csc-page-home
.q-card-actions
font-size 18px
padding 0 10px 35px 10px
.q-card-main
font-size 22px
padding-top 35px
.home-card-active
cursor pointer
border solid 2px #c5e0ba
.home-icons
font-size 5.5rem
color $primary
.q-card-actions
color $primary
.home-card-inactive
cursor not-allowed
border solid 2px #eeeeee
.home-icons
font-size 5.5rem
color $grey
.q-card-actions
color $grey
@media (max-width: $breakpoint-sm)
#csc-page-home
padding 16px
.q-card-actions
font-size 16px
padding 0 10px 25px 10px
.q-card-main
font-size 22px
padding-top 25px
.home-card-active
.home-icons
font-size 3rem
.home-card-inactive
.home-icons
font-size 3rem
.csc-call-page
height calc(100vh - 120px)
padding 0
.csc-call-page-content
margin-top -80px
.csc-info
background-color $info
padding $flex-gutter-md
margin-bottom $flex-gutter-lg
.csc-info-text
line-height 1.4em
color $white
</style>

@ -1,10 +1,18 @@
const userInfoRegExp = new RegExp(/^[-_.!~*'&=+$,;?/%a-zA-Z0-9]+$/);
const macAddressRegExp = new RegExp(/^(?:[0-9A-Fa-f]{2}(?=([-:]|))(?:\1[0-9A-Fa-f]{2}){5})$/);
export function userInfo (value) {
var regex = new RegExp(/^[-_.!~*'&=+$,;?/%a-zA-Z0-9]+$/);
return regex.test(value);
return userInfoRegExp.test(value);
}
export function userInfoAndEmpty(value) {
if(value === '') {
return true;
}
return userInfo(value);
}
export function customMacAddress (value) {
var regex = new RegExp(/^(?:(?:[0-9A-Fa-f]{2}(?=([-:]|))(?:\1[0-9A-Fa-f]{2}){5}))$/);
return regex.test(value);
return macAddressRegExp.test(value);
}

@ -44,8 +44,8 @@
},
"navigation": {
"home": {
"title": "Home",
"subTitle": "Access to main features"
"title": "Start call",
"subTitle": ""
},
"conversations": {
"title": "Conversations",
@ -124,11 +124,13 @@
"conversations": {
"title": "Conversations",
"buttons": {
"call": "CALL",
"call": "Call back",
"audioCall": "Audio Call",
"videoCall": "Video Call",
"play": "Play",
"download": "Download"
"download": "Download",
"downloadFax": "Download fax",
"downloadVoicemail": "Download voicemail"
},
"downloadVoiceMailSuccessMessage": "Voicemail downloaded successfully",
"downloadVoiceMailErrorMessage": "Downloading of voicemail failed",
@ -278,9 +280,16 @@
},
"call": {
"startNew": "Start new call",
"initiating": "Initiating ...",
"ringing": "Ringing ...",
"incoming": "Incoming ...",
"initiating": "Calling {number}...",
"ringing": "Ringing at {number}...",
"incoming": "Incoming call from {number}...",
"established": "In call with {number}",
"inputShort": "Start new call",
"initiatingShort": "Calling",
"ringingShort": "Ringing at",
"incomingShort": "Incoming call from",
"establishedShort": "In call with",
"endedShort": "Call ended",
"ended": "Ended",
"call": "Established",
"number": "Phone number",
@ -290,7 +299,8 @@
"notificationBlocked": "You have blocked incoming call notifications.",
"notificationFailed": "Could not enable incoming call notifications.",
"notificationNotSupported": "Incoming call notifications are not supported.",
"desktopSharingNotInstalled": "Desktop sharing extension for chrome is not installed."
"desktopSharingNotInstalled": "Desktop sharing extension for chrome is not installed.",
"rtcEngineNotEnabled": "You can neither make a call nor receive one because the RTC:engine is not active. If you operate a C5 CE then first upgrade to a C5 PRO to be able to use the RTC:engine."
},
"pbxConfig": {
"seat": "Seat",

@ -8,6 +8,9 @@ export default {
computed: {
isMobile() {
return Platform.is.mobile;
},
isDesktop() {
return Platform.is.platform;
}
}
}

@ -255,13 +255,14 @@ export class RtcEngineCall {
}
end() {
this.endedReason = this.fetchEndedReason();
if(this.localCall !== null) {
this.localCall.end();
this.endedReason = this.fetchEndedReason();
this.localCall = null;
}
if(this.remoteCall !== null) {
this.remoteCall.end();
this.endedReason = this.fetchEndedReason();
this.remoteCall = null;
}
if(this.localMedia !== null) {
@ -327,14 +328,6 @@ export class RtcEngineCall {
}
}
isRemoteSendingAudio() {
return this.getCall()._cdkCall.mediaInfo.remoteSdp.isOfferingAudio();
}
isRemoteSendingVideo() {
return this.getCall()._cdkCall.mediaInfo.remoteSdp.isOfferingVideo();
}
static getInstance() {
if(rtcEngineCallInstance === null) {
rtcEngineCallInstance = new RtcEngineCall();

@ -27,8 +27,7 @@ export default [
path: 'home',
component: Home,
meta: {
title: i18n.t('navigation.home.title'),
subtitle: i18n.t('navigation.home.subTitle')
title: i18n.t('call.inputShort'),
}
},
{

@ -2,6 +2,15 @@
import _ from 'lodash';
import Vue from 'vue';
import {
i18n
} from '../i18n';
import {
normalizeDestination
} from '../filters/number-format'
import {
startCase
} from '../filters/string'
export var CallState = {
input: 'input',
@ -26,45 +35,28 @@ export default {
disabled: false,
endedReason: null,
callState: CallState.input,
number: null,
number: '',
numberInput: '',
localMediaStream: null,
remoteMediaStream: null,
audioEnabled: true,
videoEnabled: true,
muted: false,
caller: false,
callee: false,
desktopSharingInstall: false,
dtmf: null
microphoneEnabled: true,
cameraEnabled: true,
remoteVolumeEnabled: true,
maximized: false,
dialpadOpened: false
},
getters: {
getNumber(state) {
return state.number;
},
localMediaType(state) {
if(state.localMediaStream !== null && state.localMediaStream.hasAudio() && state.localMediaStream.hasVideo()) {
return MediaType.audioVideo;
}
else if (state.localMediaStream !== null && state.localMediaStream.hasAudio()) {
return MediaType.audioOnly;
}
else {
return null;
}
endedReason(state) {
return state.endedReason;
},
remoteMediaType(state) {
if(state.remoteMediaStream !== null && state.remoteMediaStream.hasAudio() && state.remoteMediaStream.hasVideo()) {
return MediaType.audioVideo;
}
else if (state.remoteMediaStream !== null && state.remoteMediaStream.hasAudio()) {
return MediaType.audioOnly;
}
else {
return null;
}
callNumber(state) {
return state.number;
},
getEndedReason(state) {
return state.endedReason;
callNumberInput(state) {
return state.numberInput;
},
isNetworkConnected(state) {
return state.initialized;
@ -72,9 +64,12 @@ export default {
isCallAvailable(state, getters) {
return getters.isNetworkConnected;
},
hasCallInitFailure(state) {
hasCallInitError(state) {
return state.initError !== null && state.disabled === false;
},
callInitError(state) {
return state.initError;
},
isPreparing(state) {
return state.callState === CallState.input;
},
@ -94,7 +89,9 @@ export default {
isCalling(state) {
return state.callState === CallState.initiating ||
state.callState === CallState.ringing ||
state.callState === CallState.established;
state.callState === CallState.established ||
state.callState === CallState.incoming ||
state.callState === CallState.ended;
},
isEstablished(state) {
return state.callState === CallState.established;
@ -123,9 +120,6 @@ export default {
isVideoEnabled(state) {
return state.videoEnabled;
},
isMuted(state) {
return state.muted;
},
isCaller(state) {
return state.caller;
},
@ -138,11 +132,61 @@ export default {
desktopSharingInstall(state) {
return state.desktopSharingInstall;
},
dtmfState(state) {
return state.dtmf;
localMediaStream(state) {
if(state.localMediaStream !== null) {
return state.localMediaStream.getStream();
}
return null;
},
remoteMediaStream(state) {
if(state.remoteMediaStream !== null) {
return state.remoteMediaStream.getStream();
}
return null;
},
isMicrophoneEnabled(state) {
return state.microphoneEnabled;
},
isCameraEnabled(state) {
return state.cameraEnabled;
},
isRemoteVolumeEnabled(state) {
return state.remoteVolumeEnabled;
},
isMaximized(state) {
return state.maximized;
},
isDialpadOpened(state) {
return state.dialpadOpened;
},
callNumberFormatted(state, getters) {
return normalizeDestination(getters.callNumber);
},
callEndedReasonFormatted(state, getters) {
return startCase(getters.endedReason);
},
callStateTitle(state) {
return i18n.t('call.' + state.callState + 'Short');
},
callStateSubtitle(state, getters) {
if(state.callState === CallState.initiating ||
state.callState === CallState.ringing ||
state.callState === CallState.incoming ||
state.callState === CallState.established) {
return getters.callNumberFormatted;
}
else if (state.callState === CallState.ended) {
return getters.callEndedReasonFormatted;
}
else {
return '';
}
}
},
mutations: {
numberInputChanged(state, numberInput) {
state.numberInput = numberInput;
},
initSucceeded(state) {
state.initialized = true;
state.initError = null;
@ -156,12 +200,16 @@ export default {
},
inputNumber(state) {
state.callState = CallState.input;
state.number = '';
state.numberInput = '';
state.endedReason = null;
},
startCalling(state, options) {
state.number = options.number;
startCalling(state, number) {
state.number = number;
state.callState = CallState.initiating;
state.caller = true;
state.callee = false;
state.endedReason = null;
},
localMediaSuccess(state, localMediaStream) {
state.localMediaStream = localMediaStream;
@ -175,15 +223,16 @@ export default {
establishCall(state, remoteMediaStream) {
state.remoteMediaStream = remoteMediaStream;
state.callState = CallState.established;
state.audioEnabled = true;
state.videoEnabled = true;
state.muted = false;
state.microphoneEnabled = true;
state.cameraEnabled = true;
state.remoteVolumeEnabled = true;
},
incomingCall(state, options) {
state.callState = CallState.incoming;
state.number = options.number;
state.callee = true;
state.caller = false;
state.endedReason = null;
},
hangUpCall(state) {
state.callState = CallState.input;
@ -195,10 +244,15 @@ export default {
state.remoteMediaStream.stop();
state.remoteMediaStream = null;
}
state.number = '';
state.numberInput = '';
state.endedReason = null;
},
endCall(state, reason) {
state.callState = CallState.ended;
state.endedReason = reason;
if(state.endedReason === null) {
state.callState = CallState.ended;
state.endedReason = reason;
}
if(_.isObject(state.localMediaStream)) {
state.localMediaStream.stop();
state.localMediaStream = null;
@ -208,24 +262,6 @@ export default {
state.remoteMediaStream = null;
}
},
disableAudio(state) {
state.audioEnabled = false;
},
enableAudio(state) {
state.audioEnabled = true;
},
disableVideo(state) {
state.videoEnabled = false;
},
enableVideo(state) {
state.videoEnabled = true;
},
mute(state) {
state.muted = true;
},
unmute(state) {
state.muted = false;
},
desktopSharingInstallReset(state) {
state.desktopSharingInstall = false;
},
@ -234,13 +270,32 @@ export default {
},
sendDTMF(state, value) {
state.dtmf = value;
},
toggleMicrophone(state) {
state.microphoneEnabled = !state.microphoneEnabled;
},
toggleCamera(state) {
state.cameraEnabled = !state.cameraEnabled;
},
toggleRemoteVolume(state) {
state.remoteVolumeEnabled = !state.remoteVolumeEnabled;
},
maximize(state) {
state.dialpadOpened = false;
state.maximized = true;
},
minimize(state) {
state.dialpadOpened = false;
state.maximized = false;
},
toggleDialpad(state) {
state.dialpadOpened = !state.dialpadOpened;
}
},
actions: {
initialize(context) {
return new Promise((resolve, reject)=>{
Vue.call.onIncoming(()=>{
context.commit('layout/showRight', null, { root: true });
context.commit('incomingCall', {
number: Vue.call.getNumber()
});
@ -265,24 +320,24 @@ export default {
}
});
},
start(context, options) {
start(context, localMedia) {
let number = context.getters.callNumberInput;
context.commit('desktopSharingInstallReset');
context.commit('layout/showRight', null, { root: true });
context.commit('startCalling', { number: options.number });
context.commit('startCalling', number);
Promise.resolve().then(()=>{
return Vue.call.createLocalMedia(options.localMedia);
return Vue.call.createLocalMedia(localMedia);
}).then((localMediaStream)=>{
context.commit('localMediaSuccess', localMediaStream);
Vue.call.onRingingStart(()=>{
context.commit('startRinging');
}).onRingingStop(()=>{
context.commit('stopRinging');
}).start(options.number, localMediaStream);
}).start(number, localMediaStream);
}).catch((err)=>{
Vue.call.end();
if(err.message === 'plugin not detected') {
context.commit('desktopSharingInstall');
context.commit('endCall', 'missingDesktopSharingExtension');
context.commit('hangUpCall');
}
else {
context.commit('endCall', err.name);
@ -298,44 +353,42 @@ export default {
Vue.call.end();
if(err.message === 'plugin not detected') {
context.commit('desktopSharingInstall');
context.commit('endCall', 'missingDesktopSharingExtension');
context.commit('hangUpCall');
}
else {
context.commit('endCall', err.name);
}
});
},
hangUp(context) {
Vue.call.hangUp();
end(context) {
Vue.call.end();
context.commit('hangUpCall');
},
disableAudio(context) {
Vue.call.disableAudio();
context.commit('disableAudio');
},
enableAudio(context) {
Vue.call.enableAudio();
context.commit('enableAudio');
},
disableVideo(context) {
Vue.call.disableVideo();
context.commit('disableVideo');
},
enableVideo(context) {
Vue.call.enableVideo();
context.commit('enableVideo');
},
showCall(context) {
context.commit('layout/showRight', null, { root: true });
},
hideCall(context) {
context.commit('layout/hideRight', null, { root: true });
},
sendDTMF(context, value) {
context.commit('sendDTMF', value);
if(Vue.call.hasRunningCall()) {
Vue.call.sendDTMF(value);
}
},
toggleMicrophone(context) {
if(context.getters.isMicrophoneEnabled) {
Vue.call.disableAudio();
}
else {
Vue.call.enableAudio();
}
context.commit('toggleMicrophone');
},
toggleCamera(context) {
if(context.getters.isCameraEnabled) {
Vue.call.disableVideo();
}
else {
Vue.call.enableVideo();
}
context.commit('toggleCamera');
},
toggleRemoteVolume(context) {
context.commit('toggleRemoteVolume');
}
}
};

@ -7,7 +7,6 @@ import CallBlockingModule from './call-blocking'
import CallForwardModule from './call-forward'
import CallModule from './call'
import ConversationsModule from './conversations'
import LayoutModule from './layout'
import PbxConfigModule from './pbx-config/index'
import ReminderModule from './reminder'
import SpeedDialModule from './speed-dial'
@ -15,6 +14,10 @@ import UserModule from './user'
import CommunicationModule from './communication'
import VoiceboxModule from './voicebox'
import {
i18n
} from '../i18n';
Vue.use(Vuex);
export const store = new Vuex.Store({
@ -23,7 +26,6 @@ export const store = new Vuex.Store({
callForward: CallForwardModule,
call: CallModule,
conversations: ConversationsModule,
layout: LayoutModule,
pbxConfig: PbxConfigModule,
reminder: ReminderModule,
speedDial: SpeedDialModule,
@ -37,6 +39,21 @@ export const store = new Vuex.Store({
},
pageSubtitle(state) {
return _.get(state, 'route.meta.subtitle', '');
},
isCallForward(state) {
return _.startsWith(_.get(state, 'route.path', ''), '/user/call-forward');
},
isCallBlocking(state) {
return _.startsWith(_.get(state, 'route.path', ''), '/user/call-blocking');
},
isPbxConfiguration(state) {
return _.startsWith(_.get(state, 'route.path', ''), '/user/pbx-configuration');
},
isHome(state) {
return _.get(state, 'route.path', '') === '/user/home';
},
title() {
return i18n.t('title');
}
}
});

@ -1,50 +0,0 @@
'use strict';
export default {
namespaced: true,
state: {
sides: {
left: true,
right: false
},
fullscreenEnabled: false
},
getters: {
right(state) {
return state.sides.right;
},
left(state) {
return state.sides.left;
},
isFullscreenEnabled(state) {
return state.fullscreenEnabled;
}
},
mutations: {
updateSides(state, sides) {
state.sides = sides;
},
showRight(state) {
state.sides.right = true;
},
hideRight(state){
state.sides.right = false;
},
showLeft(state){
state.sides.left = true;
},
hideLeft(state){
state.sides.left = false;
},
toggleFullscreen(state) {
state.fullscreenEnabled = !state.fullscreenEnabled;
},
enableFullscreen(state) {
state.fullscreenEnabled = true;
},
disableFullscreen(state) {
state.fullscreenEnabled = false;
}
},
actions: {}
};

@ -1,6 +1,11 @@
@import 'quasar.variables'
.csc-inline-alert
box-shadow none
.q-alert
box-shadow none
.q-field
position relative

Loading…
Cancel
Save