TT#44661 Upload a custom "busy" greeting sound

What has been done:
- TT#44661 Voicebox: As a Customer, I want to upload a custom "busy"
  greeting sound
- TT#44663, Voicebox: As a Customer, I want to delete/reset the custom
  "busy" greeting sound
- Refactoring of CscVoiceMailPlayer to more generic CscAudioPlayer

Change-Id: I24f9d607b9d1acece56cd47c0bd8c99d125d45b4
changes/59/24059/19
raxelsen 7 years ago committed by Hans-Peter Herzog
parent 2fb4691943
commit f872d4de20

@ -4,7 +4,6 @@ import { saveAs } from 'file-saver'
import Vue from 'vue' import Vue from 'vue'
import { getList } from './common' import { getList } from './common'
export function getConversations(options) { export function getConversations(options) {
return new Promise((resolve, reject)=>{ return new Promise((resolve, reject)=>{
let type = _.get(options, 'type', null); let type = _.get(options, 'type', null);

@ -1,7 +1,9 @@
import _ from 'lodash' import _ from 'lodash'
import Vue from 'vue';
import { import {
get, get,
getList,
patchReplace patchReplace
} from './common' } from './common'
@ -28,10 +30,25 @@ export function setVoiceboxDelete(options) {
} }
export function setVoiceboxAttach(options) { export function setVoiceboxAttach(options) {
return patchReplace({ return new Promise((resolve, reject)=>{
path: `api/voicemailsettings/${options.subscriberId}`, Promise.resolve().then(()=>{
fieldPath: 'attach', if(options.value === false) {
value: options.value return setVoiceboxDelete(options);
}
else {
return Promise.resolve();
}
}).then(()=>{
return patchReplace({
path: `api/voicemailsettings/${options.subscriberId}`,
fieldPath: 'attach',
value: options.value
});
}).then(()=>{
resolve();
}).catch((err)=>{
reject(err);
});
}); });
} }
@ -50,3 +67,78 @@ export function setVoiceboxEmail(options) {
value: options.value value: options.value
}); });
} }
export function getVoiceboxGreetingByType(options) {
return new Promise((resolve, reject) => {
getList({
path: 'api/voicemailgreetings/',
root: '_embedded.ngcp:voicemailgreetings',
params: { subscriber_id: options.id, type: options.type }
}).then((result) => {
resolve(result);
}).catch((err)=>{
reject(err);
});
});
}
export function deleteVoiceboxGreetingById(id) {
return new Promise((resolve, reject) => {
Vue.http.delete(`api/voicemailgreetings/${id}`).then(() => {
resolve();
}).catch((err)=>{
reject(err);
});
});
}
export function createNewGreeting(formData, onProgress) {
return new Promise((resolve, reject) => {
Vue.http.post('api/voicemailgreetings/', formData, {
before(request) {
Vue.previousRequest = request;
},
progress(e) {
if (e.lengthComputable) {
onProgress(Math.ceil((e.loaded / e.total ) * 100));
}
}
}).then(() => {
resolve();
}).catch((err)=>{
reject(err);
});
});
}
export function uploadGreeting(options) {
return new Promise((resolve, reject) => {
var formData = new FormData();
var fields = _.clone(options.data);
delete fields.file;
var json = JSON.stringify(fields);
formData.append('json', json);
if (options.data.file) {
formData.append('greetingfile', options.data.file);
}
Promise.resolve().then(() => {
return getVoiceboxGreetingByType({
id: options.data.subscriber_id,
type: options.data.type
});
}).then((greetings) => {
if (_.some(greetings.items, { dir: options.data.dir })) {
deleteVoiceboxGreetingById(greetings.items[0].id);
}
return createNewGreeting(formData, options.onProgress);
}).then(() => {
resolve();
}).catch((err) => {
reject(err);
});
});
}
export function abortPreviousRequest() {
Vue.previousRequest.abort();
}

@ -1,6 +1,6 @@
<template> <template>
<div class="voicemail-player"> <div class="voicemail-player">
<audio :src="soundFileUrl" ref="voiceMailSound" preload="auto" @timeupdate="timeupdate($event)"/> <audio :src="fileUrl" ref="audio" preload="auto" @timeupdate="timeupdate($event)"/>
<div class="control-btns"> <div class="control-btns">
<q-btn class="play-pause-btn" round flat small color="primary" <q-btn class="play-pause-btn" round flat small color="primary"
:icon="playPauseIcon" @click="toggle()" /> :icon="playPauseIcon" @click="toggle()" />
@ -12,28 +12,28 @@
</template> </template>
<script> <script>
import { mapGetters } from 'vuex'
import { QProgress, QBtn } from 'quasar-framework' import { QProgress, QBtn } from 'quasar-framework'
export default { export default {
name: 'csc-voice-mail-player', name: 'csc-audio-player',
props: { props: [
id: Number 'fileUrl',
}, 'loaded'
],
mounted() { mounted() {
this.$refs.voiceMailSound.addEventListener('play', ()=>{ this.$refs.audio.addEventListener('play', ()=>{
this.playing = true; this.playing = true;
}); });
this.$refs.voiceMailSound.addEventListener('playing', ()=>{ this.$refs.audio.addEventListener('playing', ()=>{
this.playing = true; this.playing = true;
}); });
this.$refs.voiceMailSound.addEventListener('ended', ()=>{ this.$refs.audio.addEventListener('ended', ()=>{
this.playing = false; this.playing = false;
this.stop(); this.stop();
}); });
this.$refs.voiceMailSound.addEventListener('canplay', ()=>{ this.$refs.audio.addEventListener('canplay', ()=>{
if(!this.paused && this.playing) { if(!this.paused && this.playing) {
this.$refs.voiceMailSound.play(); this.$refs.audio.play();
} }
}); });
}, },
@ -43,7 +43,6 @@
}, },
data () { data () {
return { return {
platform: this.$q.platform.is,
playing: false, playing: false,
paused: false, paused: false,
progressPercentage: 0 progressPercentage: 0
@ -52,63 +51,53 @@
computed: { computed: {
playPauseIcon() { playPauseIcon() {
return this.playing ? 'pause': 'play_arrow'; return this.playing ? 'pause': 'play_arrow';
}, }
soundFileFormat() {
return this.platform.mozilla ? 'ogg' : 'mp3';
},
soundFileUrl() {
let getter = this.playVoiceMailUrl;
return getter(this.id);
},
...mapGetters('conversations', [
'playVoiceMailState',
'playVoiceMailUrl'
])
}, },
methods: { methods: {
play() { play() {
this.$refs.voiceMailSound.play(); this.$refs.audio.play();
this.playing = true; this.playing = true;
this.paused = false; this.paused = false;
}, },
pause() { pause() {
this.$refs.voiceMailSound.pause(); this.$refs.audio.pause();
this.playing = false; this.playing = false;
this.paused = true; this.paused = true;
}, },
stop() { stop() {
this.$refs.voiceMailSound.currentTime = 0; this.$refs.audio.currentTime = 0;
this.pause(); this.pause();
}, },
load() { setPlayingTrue() {
this.$emit('play-voice-mail', {
id: this.id,
format: this.soundFileFormat
});
this.playing = true; this.playing = true;
},
setPausedFalse() {
this.paused = false; this.paused = false;
}, },
timeupdate(e) {
let newPercentage = Math.floor((e.target.currentTime / e.target.duration) * 100);
this.progressPercentage = newPercentage;
},
load() {
this.$emit('load');
},
toggle() { toggle() {
if(this.playVoiceMailState(this.id) !== 'succeeded') { if (!this.loaded) {
this.load(); this.load();
} }
else if (this.$refs.voiceMailSound.paused) { else if (this.$refs.audio.paused) {
this.play(); this.play();
} }
else { else {
this.pause(); this.pause();
} }
},
timeupdate(e) {
let newPercentage = Math.floor((e.target.currentTime / e.target.duration) * 100);
this.progressPercentage = newPercentage;
} }
} }
} }
</script> </script>
<style lang="stylus" rel="stylesheet/stylus"> <style lang="stylus" rel="stylesheet/stylus">
@import '../../../themes/app.common' @import '../themes/app.common'
.voicemail-player .voicemail-player
width 100% width 100%

@ -51,7 +51,7 @@
</q-field> </q-field>
<q-field class="upload-field"> <q-field class="upload-field">
<label <label
for="file-upload" for="fax-file-upload"
class="upload-label" class="upload-label"
> >
<div class="upload-label"> <div class="upload-label">
@ -61,10 +61,10 @@
class="upload-button" class="upload-button"
flat flat
dark dark
@click="$refs.upload.click()" @click="$refs.faxUpload.click()"
icon-right="cloud_upload" icon="cloud_upload"
> >
{{ $t('communication.label.uploadFile') }} {{ $t('buttons.select') }}
</q-btn> </q-btn>
<span class="upload-filename"> <span class="upload-filename">
{{ selectedFile }} {{ selectedFile }}
@ -79,8 +79,8 @@
/> />
</label> </label>
<input <input
ref="upload" ref="faxUpload"
id="file-upload" id="fax-file-upload"
type="file" type="file"
accept=".pdf,.tiff,.txt,.ps" accept=".pdf,.tiff,.txt,.ps"
@change="processFile($event)" @change="processFile($event)"
@ -139,7 +139,7 @@
pageHeader: null, pageHeader: null,
data: null, data: null,
quality: 'normal', quality: 'normal',
file: null file: {}
}, },
qualityOptions: [ qualityOptions: [
{ label: this.$t('communication.quality.normal'), value: 'normal' }, { label: this.$t('communication.quality.normal'), value: 'normal' },
@ -231,10 +231,12 @@
}, },
methods: { methods: {
resetFile() { resetFile() {
this.form.file = null; this.form.file = {};
this.selectedFile = ''; this.selectedFile = '';
this.$refs.faxUpload.value = '';
}, },
processFile(event) { processFile(event) {
console.log('processFile()');
let file = event.target.files[0]; let file = event.target.files[0];
let fileName = file ? file.name : ''; let fileName = file ? file.name : '';
let fileNameSplit = fileName.split('.'); let fileNameSplit = fileName.split('.');
@ -306,7 +308,7 @@
.upload-filename .upload-filename
color black color black
#file-upload #fax-file-upload
display none display none
#csc-error-label #csc-error-label

@ -0,0 +1,172 @@
<template>
<q-field
class="csc-upload-field"
:icon="icon"
>
<q-input
readonly
:float-label="label"
:value="inputValue"
:after="inputButtons"
/>
<input
v-show="false"
ref="fileUpload"
type="file"
@change="inputChange"
/>
<div
v-show="uploading"
class="row no-wrap csc-upload-progress-field"
>
<q-chip
square
color="primary"
class="upload-chip"
>
{{ `${progress}%` }}
</q-chip>
<q-progress
stripe
animate
color="primary"
:percentage="progress"
class="upload-progress"
/>
</div>
<div
class="csc-file-upload-actions"
>
<q-btn
v-if="selectedFile != null"
flat
color="default"
icon="clear"
@click="cancel"
>
{{ $t('buttons.cancel') }}
</q-btn>
<q-btn
flat
v-if="selectedFile != null && !uploading"
color="primary"
icon="cloud_upload"
@click="upload"
>
{{ $t('buttons.upload') }}
</q-btn>
<q-btn
flat
v-if="uploaded && selectedFile == null"
color="primary"
icon="undo"
@click="undo"
>
{{ $t('buttons.resetDefaults') }}
</q-btn>
</div>
</q-field>
</template>
<script>
import {
QInput,
QField,
QBtn,
QChip,
QProgress
} from 'quasar-framework'
export default {
name: 'csc-sound-file-upload',
components: {
QInput,
QField,
QBtn,
QChip,
QProgress
},
props: [
'icon',
'label',
'value',
'uploading',
'uploaded',
'progress',
'fileTypes'
],
data () {
return {
selectedFile: null
}
},
computed: {
inputValue() {
if(this.selectedFile === null) {
return this.value;
}
else {
return this.selectedFile.name;
}
},
inputButtons() {
let buttons = [];
let self = this;
buttons.push({
icon: 'folder',
error: false,
handler (event) {
event.stopPropagation();
self.$refs.fileUpload.click();
}
}
);
return buttons;
}
},
methods: {
inputChange(event) {
this.selectedFile = event.target.files[0];
},
cancel() {
this.selectedFile = null;
this.$refs.fileUpload.value = null;
if(this.uploading) {
this.abort();
}
},
upload() {
this.$emit('upload', this.selectedFile);
},
abort() {
this.$emit('abort');
},
reset() {
this.cancel();
},
undo() {
this.$emit('reset');
}
}
}
</script>
<style lang="stylus" rel="stylesheet/stylus">
@import '../../themes/quasar.variables';
.csc-file-upload-actions
padding-top $flex-gutter-xs
.csc-upload-field
.q-field-icon
color $primary
.csc-upload-progress-field
margin 10px 0 5px 0
.upload-chip
min-height 20px
height 20px
width 50px
border-top-right-radius 0
border-bottom-right-radius 0
.upload-progress
height 20px
</style>

@ -0,0 +1,202 @@
<template>
<q-field
class="csc-form-field upload-field"
icon="music_note"
>
<label
for="voicemail-file-upload"
class="upload-label"
>
{{ $t('voicebox.label.busyGreeting') }}
</label>
<span
v-show="selectedFile.length === 0"
>
<slot name="status-label" />
</span>
<div
v-show="requesting"
class="row no-wrap progress-field"
>
<q-chip
square
color="primary"
class="upload-chip"
>
{{ `${progress}%` }}
</q-chip>
<q-progress
stripe
animate
color="primary"
:percentage="progress"
class="upload-progress"
/>
</div>
<div class="upload-filename">
{{ selectedFile }}
</div>
<span
v-show="selectedFile.length === 0 && id"
>
<slot name="extra-buttons" />
</span>
<q-btn
v-show="selectedFile.length > 0"
flat
color="negative"
icon="cancel"
@click="cancel"
>
{{ $t('buttons.cancel') }}
</q-btn>
<q-btn
v-show="!requesting"
flat
color="primary"
@click="$refs.upload.click()"
icon="folder"
>
{{ selectLabel }}
</q-btn>
<q-btn
flat
v-show="selectedFile.length > 0 && !requesting"
color="primary"
icon="cloud_upload"
@click="upload"
>
{{ $t('buttons.upload') }}
</q-btn>
<input
ref="upload"
id="voicemail-file-upload"
type="file"
:accept="fileTypes"
@change="processFile($event)"
/>
</q-field>
</template>
<script>
import {
QField,
QInput,
QBtn,
QProgress,
QChip
} from 'quasar-framework'
export default {
name: 'csc-upload-file',
props: [
'progress',
'requesting',
'fileTypes',
'id'
],
data () {
return {
selectedFile: '',
file: null
}
},
components: {
QField,
QInput,
QBtn,
QProgress,
QChip
},
computed: {
selectLabel() {
return this.id ? this.$t('buttons.selectNew') :
this.$t('buttons.select');
}
},
methods: {
cancel() {
if (this.requesting) {
this.abort();
}
else if (this.selectedFile.length > 0) {
this.$emit('reset');
}
},
reset() {
this.file = null;
this.selectedFile = '';
this.$refs.upload.value = '';
},
processFile(event) {
let file = event.target.files[0];
let fileName = file ? file.name : '';
let fileNameSplit = fileName.split('.');
let extension = fileNameSplit[1] ? fileNameSplit[1] : null;
if (fileName.length > 22 && extension) {
fileName = `${fileName.substring(0, 14)}...${extension}`;
}
else if (fileName.length > 22 && !extension) {
fileName = `${fileName.substring(0, 17)}...`;
}
this.file = file;
this.selectedFile = fileName;
},
upload() {
this.$emit('upload', this.file);
},
abort() {
this.$emit('abort');
}
}
}
</script>
<style lang="stylus" rel="stylesheet/stylus">
@import '../../themes/quasar.variables.styl'
#voicemail-file-upload
display none
.upload-field
margin-bottom 10px
.upload-label
display block
color $csc-label
font-size 16px
margin-bottom 5px
.reset-button
padding 0
.q-icon
margin 0
.upload-filename
color $black
margin-bottom 10px
.q-field-icon
margin-top 20px
.inactive-label
color $secondary
.active-label
color $primary
.progress-field
margin 10px 0 5px 0
.upload-chip
min-height 20px
height 20px
width 50px
border-top-right-radius 0
border-bottom-right-radius 0
.upload-progress
height 20px
</style>

@ -33,10 +33,12 @@
{{ $t('pages.conversations.seconds') }} {{ $t('pages.conversations.seconds') }}
</q-item-tile> </q-item-tile>
<q-item-tile> <q-item-tile>
<csc-voice-mail-player <csc-audio-player
:id="voiceMail.id" ref="voicemailPlayer"
:file-url="soundFileUrl"
:loaded="voiceMailLoaded"
class="csc-voice-mail-player" class="csc-voice-mail-player"
@play-voice-mail="playVoiceMail" @load="load"
/> />
</q-item-tile> </q-item-tile>
</q-item-main> </q-item-main>
@ -73,8 +75,9 @@
</template> </template>
<script> <script>
import { mapGetters } from 'vuex'
import CscCallOptionList from './CscCallOptionList' import CscCallOptionList from './CscCallOptionList'
import CscVoiceMailPlayer from './CscVoiceMailPlayer' import CscAudioPlayer from '../../CscAudioPlayer'
import { import {
QItem, QItem,
QItemSide, QItemSide,
@ -96,13 +99,19 @@
QItemTile, QItemTile,
QPopover, QPopover,
QBtn, QBtn,
CscVoiceMailPlayer, CscAudioPlayer,
CscCallOptionList CscCallOptionList
}, },
data () { data () {
return {} return {
platform: this.$q.platform.is
}
}, },
computed: { computed: {
...mapGetters('conversations', [
'playVoiceMailState',
'playVoiceMailUrl'
]),
direction() { direction() {
if(this.voiceMail.direction === 'out') { if(this.voiceMail.direction === 'out') {
return 'to'; return 'to';
@ -110,6 +119,16 @@
else { else {
return 'from'; return 'from';
} }
},
soundFileFormat() {
return this.platform.mozilla ? 'ogg' : 'mp3';
},
soundFileUrl() {
let getter = this.playVoiceMailUrl;
return getter(this.voiceMail.id);
},
voiceMailLoaded() {
return this.playVoiceMailState(this.voiceMail.id) === 'succeeded';
} }
}, },
methods: { methods: {
@ -121,10 +140,18 @@
}); });
}, },
playVoiceMail() { playVoiceMail() {
this.$emit('play-voice-mail', this.voiceMail); this.$emit('play-voice-mail', {
id: this.voiceMail.id,
format: this.soundFileFormat
});
}, },
downloadVoiceMail() { downloadVoiceMail() {
this.$emit('download-voice-mail', this.voiceMail); this.$emit('download-voice-mail', this.voiceMail);
},
load() {
this.playVoiceMail();
this.$refs.voicemailPlayer.setPlayingTrue();
this.$refs.voicemailPlayer.setPausedFalse();
} }
} }
} }

@ -6,8 +6,13 @@
icon="lock" icon="lock"
:error-label="pinErrorMessage" :error-label="pinErrorMessage"
> >
<q-inner-loading :visible="pinRequesting">
<q-spinner-dots
color="primary"
:size="24"
/>
</q-inner-loading>
<q-input <q-input
:loading="pinRequesting"
:disable="pinRequesting" :disable="pinRequesting"
:float-label="$t('voicebox.label.changePin')" :float-label="$t('voicebox.label.changePin')"
v-model="changes.pin" v-model="changes.pin"
@ -23,8 +28,13 @@
icon="email" icon="email"
:error-label="emailErrorMessage" :error-label="emailErrorMessage"
> >
<q-inner-loading :visible="emailRequesting">
<q-spinner-dots
color="primary"
:size="24"
/>
</q-inner-loading>
<q-input <q-input
:loading="emailRequesting"
:disable="emailRequesting" :disable="emailRequesting"
:float-label="$t('voicebox.label.changeEmail')" :float-label="$t('voicebox.label.changeEmail')"
v-model="changes.email" v-model="changes.email"
@ -36,25 +46,39 @@
/> />
</q-field> </q-field>
<q-field class="csc-form-field"> <q-field class="csc-form-field">
<q-inner-loading :visible="attachRequesting">
<q-spinner-dots
color="primary"
:size="24"
/>
</q-inner-loading>
<q-toggle <q-toggle
:class="attachClasses"
:disable="attachRequesting"
:label="attachLabel"
v-model="changes.attach"
@input="toggleAttach"
checked-icon="attach_file"
unchecked-icon="attach_file"
/>
</q-field>
<q-field class="csc-form-field">
<q-inner-loading :visible="deleteRequesting">
<q-spinner-dots
color="primary"
:size="24"
/>
</q-inner-loading>
<q-toggle
:class="deleteClasses"
:disable="deleteRequesting || !canToggleDelete" :disable="deleteRequesting || !canToggleDelete"
:label="deleteLabel" :label="deleteLabel"
v-model="changes.delete" v-model="changes.delete"
@input="toggle('delete')" @input="toggleDelete"
checked-icon="delete" checked-icon="delete"
unchecked-icon="delete" unchecked-icon="delete"
/> />
</q-field> </q-field>
<q-field class="csc-form-field">
<q-toggle
:disable="attachRequesting || !canToggleAttachment"
:label="attachLabel"
v-model="changes.attach"
@input="toggle('attach')"
checked-icon="attach_file"
unchecked-icon="attach_file"
/>
</q-field>
</div> </div>
</template> </template>
@ -66,18 +90,23 @@
import { import {
QField, QField,
QInput, QInput,
QToggle QToggle,
QSpinnerDots,
QInnerLoading
} from 'quasar-framework' } from 'quasar-framework'
export default { export default {
name: 'csc-voicebox-settings', name: 'csc-voicebox-settings',
props: [ props: [
'settings',
'deleteRequesting', 'deleteRequesting',
'attachRequesting', 'attachRequesting',
'pinRequesting', 'pinRequesting',
'emailRequesting', 'emailRequesting',
'attachLabel', 'attachLabel',
'deleteLabel' 'deleteLabel',
'attach',
'delete',
'pin',
'email'
], ],
data () { data () {
return { return {
@ -87,7 +116,9 @@
components: { components: {
QField, QField,
QInput, QInput,
QToggle QToggle,
QSpinnerDots,
QInnerLoading
}, },
validations: { validations: {
changes: { changes: {
@ -110,16 +141,13 @@
return this.$t('validationErrors.email'); return this.$t('validationErrors.email');
}, },
canToggleDelete() { canToggleDelete() {
return this.settings.attach; return this.attach;
},
canToggleAttachment() {
return !this.settings.delete;
}, },
pinHasChanged() { pinHasChanged() {
return this.changes.pin !== this.settings.pin; return this.changes.pin !== this.pin;
}, },
emailHasChanged() { emailHasChanged() {
return this.changes.email !== this.settings.email; return this.changes.email !== this.email;
}, },
pinButtons() { pinButtons() {
let buttons = []; let buttons = [];
@ -166,33 +194,69 @@
); );
} }
return buttons; return buttons;
},
attachClasses() {
let classes = [];
if(this.attach) {
classes.push('csc-toggle-enabled');
}
else {
classes.push('csc-toggle-disabled');
}
return classes;
},
deleteClasses() {
let classes = [];
if(this.delete) {
classes.push('csc-toggle-enabled');
}
else {
classes.push('csc-toggle-disabled');
}
return classes;
} }
}, },
methods: { methods: {
getSettings() { getSettings() {
return { return {
delete: this.settings.delete, delete: this.delete,
attach: this.settings.attach, attach: this.attach,
pin: this.settings.pin, pin: this.pin,
email: this.settings.email email: this.email
} }
}, },
resetFields() { resetFields() {
this.changes = this.getSettings(); this.changes = this.getSettings();
}, },
toggle(field) { toggleDelete() {
if (field === 'delete') { this.$store.dispatch('voicebox/toggleDelete');
this.$store.dispatch('voicebox/toggleDelete'); },
} toggleAttach() {
else if (field === 'attach') { this.$store.dispatch('voicebox/toggleAttach');
this.$store.dispatch('voicebox/toggleAttach');
}
}, },
updatePin() { updatePin() {
this.$store.dispatch('voicebox/updatePin', this.changes.pin); if(this.pinHasChanged) {
this.$store.dispatch('voicebox/updatePin', this.changes.pin);
}
}, },
updateEmail() { updateEmail() {
this.$store.dispatch('voicebox/updateEmail', this.changes.email); if(this.emailHasChanged) {
this.$store.dispatch('voicebox/updateEmail', this.changes.email);
}
}
},
watch: {
pin() {
this.changes.pin = this.pin;
},
delete() {
this.changes.delete = this.delete;
},
attach() {
this.changes.attach = this.attach;
},
email() {
this.changes.email = this.email;
} }
} }
} }

@ -1,23 +1,97 @@
<template> <template>
<csc-page class="csc-simple-page"> <csc-page class="csc-simple-page">
<csc-voicebox-settings <div
v-if="isSettingsLoaded" class="row"
:settings="voiceboxSettings" >
:deleteRequesting="isDeleteRequesting" <div
:attachRequesting="isAttachRequesting" class="col col-xs-12 col-md-6"
:pinRequesting="isPinRequesting" >
:emailRequesting="isEmailRequesting" <csc-voicebox-settings
:deleteLabel="deleteLabel" :attach="voiceboxAttach"
:attachLabel="attachLabel" :delete="voiceboxDelete"
/> :pin="voiceboxPin"
:email="voiceboxEmail"
:deleteRequesting="isDeleteRequesting"
:attachRequesting="isAttachRequesting"
:pinRequesting="isPinRequesting"
:emailRequesting="isEmailRequesting"
:deleteLabel="deleteLabel"
:attachLabel="attachLabel"
/>
<csc-sound-file-upload
ref="uploadBusyGreeting"
icon="music_note"
:label="$t('voicebox.label.busyGreeting')"
:value="busyGreetingLabel"
:progress="uploadProgress"
:uploading="uploadBusyGreetingRequesting"
:uploaded="busyGreetingId !== null"
@upload="uploadBusyGreeting"
@abort="abort"
@reset="deleteBusy"
/>
<!--<q-field-->
<!--icon="music_note"-->
<!--&gt;-->
<!--<q-input-->
<!--readonly-->
<!--:float-label="$t('voicebox.label.busyGreeting')"-->
<!--:value="busyGreetingLabel"-->
<!--:after="busyGreetingButtons"-->
<!--/>-->
<!--<input-->
<!--v-show="false"-->
<!--ref="busyGreetingUpload"-->
<!--type="file"-->
<!--@change="busyGreetingFileChange"-->
<!--/>-->
<!--</q-field>-->
<!--<csc-upload-file-->
<!--ref="uploadBusyGreeting"-->
<!--:progress="uploadProgress"-->
<!--:requesting="uploadBusyGreetingRequesting"-->
<!--:id="busyGreetingId"-->
<!--file-types=".wav,.mp3"-->
<!--@reset="resetBusyFile"-->
<!--@upload="uploadBusyGreeting"-->
<!--@abort="abort"-->
<!--&gt;-->
<!--<div-->
<!--slot="status-label"-->
<!--:class="busyGreetingLabelClasses"-->
<!--&gt;-->
<!--{{ busyGreetingLabel }}-->
<!--</div>-->
<!--<q-btn-->
<!--slot="extra-buttons"-->
<!--flat-->
<!--color="negative"-->
<!--icon="delete"-->
<!--@click="deleteBusy"-->
<!--&gt;-->
<!--{{ $t('buttons.remove') }}-->
<!--</q-btn>-->
<!--</csc-upload-file>-->
</div>
</div>
</csc-page> </csc-page>
</template> </template>
<script> <script>
import {
QBtn,
QField,
QInput,
Dialog
} from 'quasar-framework'
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import CscPage from '../../CscPage' import CscPage from '../../CscPage'
import CscVoiceboxSettings from './CscVoiceboxSettings' import CscVoiceboxSettings from './CscVoiceboxSettings'
import CscSoundFileUpload from '../../form/CscSoundFileUpload'
import { import {
startLoading, startLoading,
stopLoading, stopLoading,
@ -30,15 +104,23 @@
} }
}, },
components: { components: {
CscSoundFileUpload,
CscPage, CscPage,
CscVoiceboxSettings CscVoiceboxSettings,
QBtn,
QField,
QInput
}, },
created() { mounted() {
this.$store.dispatch('voicebox/getVoiceboxSettings'); this.$store.dispatch('voicebox/getVoiceboxSettings');
this.loadBusyGreeting();
}, },
computed: { computed: {
...mapGetters('voicebox', [ ...mapGetters('voicebox', [
'voiceboxSettings', 'voiceboxAttach',
'voiceboxDelete',
'voiceboxEmail',
'voiceboxPin',
'deleteLabel', 'deleteLabel',
'attachLabel', 'attachLabel',
'isDeleteRequesting', 'isDeleteRequesting',
@ -46,8 +128,8 @@
'isPinRequesting', 'isPinRequesting',
'isEmailRequesting', 'isEmailRequesting',
'isSettingsLoaded', 'isSettingsLoaded',
'loadingState', 'loadSettingsState',
'loadingError', 'loadSettingsError',
'toggleDeleteState', 'toggleDeleteState',
'toggleDeleteError', 'toggleDeleteError',
'toggleAttachState', 'toggleAttachState',
@ -56,44 +138,102 @@
'updatePinError', 'updatePinError',
'updateEmailState', 'updateEmailState',
'updateEmailError', 'updateEmailError',
]) 'uploadProgress',
}, 'uploadBusyGreetingState',
watch: { 'uploadBusyGreetingError',
loadingState(state) { 'uploadBusyGreetingRequesting',
if (state === 'requesting') { 'busyGreetingId',
startLoading(); 'unavailGreetingId',
'deleteGreetingState',
'deleteGreetingError',
'isBusyGreetingLoaded',
'busyGreetingLabel'
]),
busyGreetingLabelClasses() {
let classes = ['csc-upload-file-label'];
if(this.busyGreetingId) {
classes.push('active-label');
} }
else if (state === 'succeeded') { else {
stopLoading(); classes.push('inactive-label');
} }
else if (state === 'failed') { return classes;
stopLoading(); },
showGlobalError(this.loadingError); busyGreetingButtons() {
let buttons = [];
let self = this;
buttons.push({
icon: 'folder',
error: false,
handler (event) {
event.stopPropagation();
self.$refs.busyGreetingUpload.click();
}
}
);
return buttons;
}
},
methods: {
busyGreetingFileChange() {
},
resetBusyFile() {
this.$refs.uploadBusyGreeting.reset();
this.$store.commit('voicebox/resetProgress');
},
uploadBusyGreeting(file) {
this.$store.dispatch('voicebox/uploadGreeting', {
dir: 'busy',
file: file
});
},
abort() {
this.$store.dispatch('voicebox/abortPreviousRequest');
},
deleteBusy() {
let self = this;
let store = this.$store;
Dialog.create({
title: self.$t('voicebox.deleteCustomDialogTitle'),
message: self.$t('voicebox.deleteCustomDialogText', {
type: 'busy'
}),
buttons: [
self.$t('buttons.cancel'),
{
label: self.$t('buttons.reset'),
color: 'negative',
handler () {
store.dispatch('voicebox/deleteGreeting', self.busyGreetingId)
}
}
]
});
},
loadBusyGreeting() {
this.$store.dispatch('voicebox/loadBusyGreeting');
}
},
watch: {
loadSettingsState(state) {
if (state === 'failed') {
showGlobalError(this.loadSettingsError);
} }
}, },
toggleDeleteState(state) { toggleDeleteState(state) {
if (state === 'requesting') { if (state === 'succeeded') {
startLoading();
}
else if (state === 'succeeded') {
stopLoading();
showToast(this.$t('voicebox.toggleDeleteSuccessMessage')); showToast(this.$t('voicebox.toggleDeleteSuccessMessage'));
} }
else if (state === 'failed') { else if (state === 'failed') {
stopLoading();
showGlobalError(this.toggleDeleteError); showGlobalError(this.toggleDeleteError);
} }
}, },
toggleAttachState(state) { toggleAttachState(state) {
if (state === 'requesting') { if (state === 'succeeded') {
startLoading();
}
else if (state === 'succeeded') {
stopLoading();
showToast(this.$t('voicebox.toggleAttachSuccessMessage')); showToast(this.$t('voicebox.toggleAttachSuccessMessage'));
} }
else if (state === 'failed') { else if (state === 'failed') {
stopLoading();
showGlobalError(this.toggleAttachError); showGlobalError(this.toggleAttachError);
} }
}, },
@ -112,10 +252,40 @@
else if (state === 'failed') { else if (state === 'failed') {
showGlobalError(this.updateEmailError); showGlobalError(this.updateEmailError);
} }
},
uploadBusyGreetingState(state) {
if (state === 'succeeded') {
showToast(this.$t('voicebox.uploadGreetingSuccessMessage'));
this.resetBusyFile();
}
else if (state === 'failed') {
showGlobalError(this.uploadBusyGreetingError);
if (this.uploadProgress > 0) {
this.resetBusyFile();
}
}
},
deleteGreetingState(state) {
if (state === 'requesting') {
startLoading();
}
else if (state === 'succeeded') {
stopLoading();
showToast(this.$t('voicebox.deleteGreetingSuccessMessage'));
}
else if (state === 'failed') {
stopLoading();
showGlobalError(this.deleteGreetingError);
}
} }
} }
} }
</script> </script>
<style lang="stylus" rel="stylesheet/stylus"> <style lang="stylus" rel="stylesheet/stylus">
@import '../../../themes/quasar.variables';
.csc-upload-file-label
margin-bottom $flex-gutter-sm
</style> </style>

@ -17,7 +17,12 @@
"moveUp": "Move up", "moveUp": "Move up",
"moveDown": "Move down", "moveDown": "Move down",
"dismiss": "Dismiss", "dismiss": "Dismiss",
"reset": "Reset" "reset": "Reset",
"select": "Select",
"upload": "Upload",
"abort": "Abort",
"selectNew": "Select new",
"resetDefaults": "Reset to defaults"
}, },
"toasts": { "toasts": {
"callAvailable": "You are now able to start and receive calls", "callAvailable": "You are now able to start and receive calls",
@ -183,10 +188,10 @@
"removeDialogTitle": "Remove call forward destination", "removeDialogTitle": "Remove call forward destination",
"removeDialogText": "You are about to remove the destination {destination}", "removeDialogText": "You are about to remove the destination {destination}",
"removeSuccessMessage": "Removed destination {destination}", "removeSuccessMessage": "Removed destination {destination}",
"removeErrorMessage": "An error occured while trying to delete the destination. Please try again.", "removeErrorMessage": "An error occured while trying to delete the destination. Please try again",
"addDestinationButton": "Add destination", "addDestinationButton": "Add destination",
"addDestinationSuccessMessage": "Added destination {destination}", "addDestinationSuccessMessage": "Added destination {destination}",
"addErrorMessage": "An error occured while trying to add the destination. Please try again.", "addErrorMessage": "An error occured while trying to add the destination. Please try again",
"addInputError": "Input a valid number or subscriber name", "addInputError": "Input a valid number or subscriber name",
"timeout": "Timeout", "timeout": "Timeout",
"destination": "Destination", "destination": "Destination",
@ -205,13 +210,13 @@
"removeLastDialogTitle": "Remove last call forward time", "removeLastDialogTitle": "Remove last call forward time",
"removeLastDialogText": "WARNING: Removing the last time entry will also delete this timeset and all corresponding destinations. Are you sure you want to do this?", "removeLastDialogText": "WARNING: Removing the last time entry will also delete this timeset and all corresponding destinations. Are you sure you want to do this?",
"removeSuccessMessage": "Removed time entry for {day}", "removeSuccessMessage": "Removed time entry for {day}",
"removeErrorMessage": "An error occured while trying to delete the time entry. Please try again.", "removeErrorMessage": "An error occured while trying to delete the time entry. Please try again",
"removeTimesetSuccessMessage": "Removed timeset", "removeTimesetSuccessMessage": "Removed timeset",
"resetErrorMessage": "An error occured while trying to reset the timesets. Please try again.", "resetErrorMessage": "An error occured while trying to reset the timesets. Please try again",
"resetSuccessMessage": "Reset of timesets completed", "resetSuccessMessage": "Reset of timesets completed",
"addTimeSuccessMessage": "Created new timeset", "addTimeSuccessMessage": "Created new timeset",
"addTimeErrorMessage": "An error occured while trying to create the timeset. Please try again.", "addTimeErrorMessage": "An error occured while trying to create the timeset. Please try again",
"loadDestinationErrorMessage": "An error occured while trying to load the destinations. Please try again.", "loadDestinationErrorMessage": "An error occured while trying to load the destinations. Please try again",
"noTimeSet": "no time set", "noTimeSet": "no time set",
"addTimeButton": "Add Time", "addTimeButton": "Add Time",
"timesetIncompatible": "The {timeset} timeset contains incompatible values. You can resolve this by resetting the {timeset} timeset.", "timesetIncompatible": "The {timeset} timeset contains incompatible values. You can resolve this by resetting the {timeset} timeset.",
@ -238,17 +243,17 @@
"sourceset": "Sourceset", "sourceset": "Sourceset",
"source": "Source", "source": "Source",
"fieldMissing": "Both sourceset name and source is required. Please provide both and try again.", "fieldMissing": "Both sourceset name and source is required. Please provide both and try again.",
"addSourcesetErrorMessage": "An error occured while trying to create the sourceset. Please try again.", "addSourcesetErrorMessage": "An error occured while trying to create the sourceset. Please try again",
"addSourceButton": "Add source", "addSourceButton": "Add source",
"addSourceSuccessMessage": "Added new source {source}", "addSourceSuccessMessage": "Added new source {source}",
"addSourceErrorMessage": "An error occured while trying to add the source. Please try again.", "addSourceErrorMessage": "An error occured while trying to add the source. Please try again",
"removeSourcesetButton": "Delete sourceset", "removeSourcesetButton": "Delete sourceset",
"removeSourcesetErrorMessage": "An error occured while trying to delete the sourceset. Please try again.", "removeSourcesetErrorMessage": "An error occured while trying to delete the sourceset. Please try again",
"removeSourcesetSuccessMessage": "Removed sourceset {sourceset}", "removeSourcesetSuccessMessage": "Removed sourceset {sourceset}",
"removeSourcesetDialogTitle": "Remove call forward sourceset", "removeSourcesetDialogTitle": "Remove call forward sourceset",
"removeSourcesetDialogText": "You are about to remove the sourceset {sourceset}", "removeSourcesetDialogText": "You are about to remove the sourceset {sourceset}",
"removeSourceSuccessMessage": "Removed source {source}", "removeSourceSuccessMessage": "Removed source {source}",
"removeSourceErrorMessage": "An error occured while trying to remove the source. Please try again.", "removeSourceErrorMessage": "An error occured while trying to remove the source. Please try again",
"removeSourceDialogTitle": "Remove call forward source", "removeSourceDialogTitle": "Remove call forward source",
"removeSourceDialogText": "You are about to remove the source entry for {source}", "removeSourceDialogText": "You are about to remove the source entry for {source}",
"removeLastSourceDialogText": "Removing the last source entry is not allowed." "removeLastSourceDialogText": "Removing the last source entry is not allowed."
@ -390,48 +395,56 @@
"pageHeader": "Page Header", "pageHeader": "Page Header",
"content": "Content", "content": "Content",
"file": "File", "file": "File",
"faxFile": "Fax File", "faxFile": "Fax File"
"uploadFile": "Upload File"
}, },
"send": "Send", "send": "Send",
"cancel": "Cancel", "cancel": "Cancel",
"createFaxErrorMessage": "An error occured while trying to send the fax. Please try again.", "createFaxErrorMessage": "An error occured while trying to send the fax. Please try again",
"createFaxSuccessMessage": "Sending fax completed successfully." "createFaxSuccessMessage": "Sending fax completed successfully."
}, },
"speedDial": { "speedDial": {
"whenIDial": "When I dial {slot} ...", "whenIDial": "When I dial {slot} ...",
"ring": "ring", "ring": "ring",
"loadSpeedDialErrorMessage": "An error occured while trying to load the speed dials. Please try again.", "loadSpeedDialErrorMessage": "An error occured while trying to load the speed dials. Please try again",
"noResultsMessage": "No speed dials found", "noResultsMessage": "No speed dials found",
"removeDialogTitle": "Remove speed dial", "removeDialogTitle": "Remove speed dial",
"removeDialogText": "You are about to remove the speed dial {slot}", "removeDialogText": "You are about to remove the speed dial {slot}",
"unassignSlotErrorMessage": "An error occured while trying to unassign the speed dial slot. Please try again.", "unassignSlotErrorMessage": "An error occured while trying to unassign the speed dial slot. Please try again",
"unassignSlotSuccessMessage": "Unassigned slot {slot}", "unassignSlotSuccessMessage": "Unassigned slot {slot}",
"addSpeedDial": "Add Speed Dial", "addSpeedDial": "Add Speed Dial",
"slot": "Slot", "slot": "Slot",
"destination": "Destination", "destination": "Destination",
"addNoSlotsDialogText": "All available speed dial slots have already been assigned. Please delete one first.", "addNoSlotsDialogText": "All available speed dial slots have already been assigned. Please delete one first.",
"assignSlotErrorMessage": "An error occured while trying to assign the speed dial slot. Please try again.", "assignSlotErrorMessage": "An error occured while trying to assign the speed dial slot. Please try again",
"assignSlotSuccessMessage": "Assigned slot {slot}" "assignSlotSuccessMessage": "Assigned slot {slot}"
}, },
"voicebox": { "voicebox": {
"label": { "label": {
"changeEmail": "Change Email", "changeEmail": "Change Email",
"changePin": "Change PIN", "changePin": "Change PIN",
"deletionEnabled": "Voicemail will be deleted after email notification is delivered", "deletionEnabled": "Deleted voicemail after email notification is delivered",
"deletionDisabled": "Voicemail will not be deleted after email notification is delivered", "deletionDisabled": "Deleted voicemail after email notification is delivered",
"attachmentEnabled": "Voicemail will be attached to email notification", "attachmentEnabled": "Attach voicemail to email notification",
"attachmentDisabled": "Voicemail will not be attached to email notification" "attachmentDisabled": "Attach voicemail to email notification",
"busyGreeting": "Busy Greeting",
"customSoundActive": "Custom sound",
"defaultSoundActive": "Default sound"
}, },
"pin": "PIN", "pin": "PIN",
"loadSettingsErrorMessage": "An error occured while trying to load the settings. Please reload the page or check your network connection.", "loadSettingsErrorMessage": "An error occured while trying to load the settings. Please reload the page or check your network connection",
"toggleDeleteSuccessMessage": "Toggled deletion successfully.", "toggleDeleteSuccessMessage": "Toggled deletion successfully",
"toggleDeleteErrorMessage": "An error occured while trying to toggle the delete option. Please try again.", "toggleDeleteErrorMessage": "An error occured while trying to toggle the delete option. Please try again",
"toggleAttachSuccessMessage": "Toggled attachment successfully.", "toggleAttachSuccessMessage": "Toggled attachment successfully",
"toggleAttachErrorMessage": "An error occured while trying to toggle the attach option. Please try again.", "toggleAttachErrorMessage": "An error occured while trying to toggle the attach option. Please try again",
"updatePinSuccessMessage": "Changed PIN successfully.", "updatePinSuccessMessage": "Changed PIN successfully.",
"updatePinErrorMessage": "An error occured while trying to update the pin field. Please try again.", "updatePinErrorMessage": "An error occured while trying to update the pin field. Please try again",
"updateEmailSuccessMessage": "Changed email successfully.", "updateEmailSuccessMessage": "Changed email successfully",
"updateEmailErrorMessage": "An error occured while trying to update the email field. Please try again." "updateEmailErrorMessage": "An error occured while trying to update the email field. Please try again",
"uploadGreetingSuccessMessage": "Uploaded greeting sound successfully",
"uploadGreetingErrorMessage": "An error occured while trying to upload the greeting sound. Please try again",
"deleteGreetingSuccessMessage": "Deleted greeting sound successfully",
"deleteGreetingErrorMessage": "An error occured while trying to delete the greeting sound. Please try again",
"deleteCustomDialogTitle": "Reset busy greeting sound",
"deleteCustomDialogText": "You are about to reset the custom {type} greeting sound to defaults"
} }
} }

@ -1,30 +1,29 @@
'use strict'; 'use strict';
import _ from 'lodash'
import { RequestState } from './common' import { RequestState } from './common'
import { import {
getVoiceboxSettings, getVoiceboxSettings,
setVoiceboxDelete, setVoiceboxDelete,
setVoiceboxAttach, setVoiceboxAttach,
setVoiceboxPin, setVoiceboxPin,
setVoiceboxEmail setVoiceboxEmail,
uploadGreeting,
abortPreviousRequest,
getVoiceboxGreetingByType,
deleteVoiceboxGreetingById
} from '../api/voicebox'; } from '../api/voicebox';
import { i18n } from '../i18n'; import { i18n } from '../i18n';
export default { export default {
namespaced: true, namespaced: true,
state: { state: {
voiceboxSettings: { voiceboxSettingDelete: false,
attach: null, voiceboxSettingAttach: false,
delete: null, voiceboxSettingPin: '',
email: '', voiceboxSettingEmail: '',
id: null, loadSettingsState: RequestState.initial,
pin: null, loadSettingsError: null,
sms_number: ''
},
loadingState: RequestState.initial,
loadingError: null,
toggleDeleteState: RequestState.initial, toggleDeleteState: RequestState.initial,
toggleDeleteError: null, toggleDeleteError: null,
toggleAttachState: RequestState.initial, toggleAttachState: RequestState.initial,
@ -32,47 +31,66 @@ export default {
updatePinState: RequestState.initial, updatePinState: RequestState.initial,
updatePinError: null, updatePinError: null,
updateEmailState: RequestState.initial, updateEmailState: RequestState.initial,
updateEmailError: null updateEmailError: null,
uploadBusyGreetingState: RequestState.initial,
uploadBusyGreetingError: null,
uploadProgress: 0,
busyGreetingId: null,
unavailGreetingId: null,
loadBusyGreetingState: RequestState.initial,
loadBusyGreetingError: null,
deleteGreetingState: RequestState.initial,
deleteGreetingError: null
}, },
getters: { getters: {
subscriberId(state, getters, rootState, rootGetters) { subscriberId(state, getters, rootState, rootGetters) {
return parseInt(rootGetters['user/getSubscriberId']); return parseInt(rootGetters['user/getSubscriberId']);
}, },
isSettingsLoaded(state) { isSettingsLoaded(state) {
return state.loadingState === 'succeeded'; return state.loadSettingsState === 'succeeded';
}, },
isDeleteRequesting(state) { isDeleteRequesting(state) {
return state.toggleDeleteState === 'requesting'; return state.loadSettingsState === 'requesting' ||
state.toggleDeleteState === 'requesting';
}, },
isAttachRequesting(state) { isAttachRequesting(state) {
return state.toggleAttachState === 'requesting'; return state.loadSettingsState === 'requesting' ||
state.toggleAttachState === 'requesting';
}, },
isPinRequesting(state) { isPinRequesting(state) {
return state.updatePinState === 'requesting'; return state.loadSettingsState === 'requesting' ||
state.updatePinState === 'requesting';
}, },
isEmailRequesting(state) { isEmailRequesting(state) {
return state.updateEmailState === 'requesting'; return state.loadSettingsState === 'requesting' ||
state.updateEmailState === 'requesting';
}, },
loadingState(state) { loadSettingsState(state) {
return state.loadingState; return state.loadSettingsState;
}, },
loadingError(state) { loadSettingsError(state) {
return state.loadingError || return state.loadSettingsError ||
i18n.t('voicebox.loadSettingsErrorMessage'); i18n.t('voicebox.loadSettingsErrorMessage');
}, },
voiceboxDelete(state) { voiceboxDelete(state) {
return _.get(state.voiceboxSettings, 'delete', false); return state.voiceboxSettingDelete;
}, },
voiceboxAttach(state) { voiceboxAttach(state) {
return _.get(state.voiceboxSettings, 'attach', false); return state.voiceboxSettingAttach;
}, },
deleteLabel(state) { voiceboxPin(state) {
return state.voiceboxSettings.delete ? return state.voiceboxSettingPin;
},
voiceboxEmail(state) {
return state.voiceboxSettingEmail;
},
deleteLabel(state, getters) {
return getters.voiceboxDelete ?
i18n.t('voicebox.label.deletionEnabled') : i18n.t('voicebox.label.deletionEnabled') :
i18n.t('voicebox.label.deletionDisabled'); i18n.t('voicebox.label.deletionDisabled');
}, },
attachLabel(state) { attachLabel(state, getters) {
return state.voiceboxSettings.attach ? return getters.voiceboxAttach ?
i18n.t('voicebox.label.attachmentEnabled') : i18n.t('voicebox.label.attachmentEnabled') :
i18n.t('voicebox.label.attachmentDisabled'); i18n.t('voicebox.label.attachmentDisabled');
}, },
@ -106,21 +124,64 @@ export default {
updateEmailError(state) { updateEmailError(state) {
return state.updateEmailError || return state.updateEmailError ||
i18n.t('voicebox.updateEmailErrorMessage'); i18n.t('voicebox.updateEmailErrorMessage');
},
uploadBusyGreetingState(state) {
return state.uploadBusyGreetingState;
},
uploadBusyGreetingError(state) {
return state.uploadBusyGreetingError ||
i18n.t('voicebox.uploadGreetingErrorMessage');
},
uploadProgress(state) {
return state.uploadProgress;
},
uploadBusyGreetingRequesting(state) {
return state.uploadBusyGreetingState === 'requesting';
},
busyGreetingId(state) {
return state.busyGreetingId;
},
unavailGreetingId(state) {
return state.unavailGreetingId;
},
deleteGreetingState(state) {
return state.deleteGreetingState;
},
deleteGreetingError(state) {
return state.deleteGreetingError ||
i18n.t('voicebox.deleteGreetingErrorMessage');
},
isBusyGreetingLoaded(state) {
return state.loadBusyGreetingState === 'succeeded';
},
busyGreetingLabel(state) {
return state.busyGreetingId ? i18n.t('voicebox.label.customSoundActive') :
i18n.t('voicebox.label.defaultSoundActive');
} }
}, },
mutations: { mutations: {
loadingRequesting(state) { loadedSettings(state, settings) {
state.loadingState = RequestState.requesting; state.voiceboxSettingDelete = settings.delete;
state.loadingError = null; state.voiceboxSettingAttach = settings.attach;
}, state.voiceboxSettingPin = settings.pin;
loadingSucceeded(state, settings) { state.voiceboxSettingEmail = settings.email;
state.loadingState = RequestState.succeeded; state.loadSettingsError = null;
state.voiceboxSettings = settings; },
state.loadingError = null; loadSettingsRequesting(state) {
}, state.loadSettingsState = RequestState.requesting;
loadingFailed(state, error) { state.loadSettingsError = null;
state.loadingState = RequestState.failed; },
state.loadingError = error; loadSettingsSucceeded(state, settings) {
state.loadSettingsState = RequestState.succeeded;
state.loadSettingsError = null;
state.voiceboxSettingDelete = settings.delete;
state.voiceboxSettingAttach = settings.attach;
state.voiceboxSettingPin = settings.pin;
state.voiceboxSettingEmail = settings.email;
},
loadSettingsFailed(state, error) {
state.loadSettingsState = RequestState.succeeded;
state.loadSettingsError = error;
}, },
toggleDeleteRequesting(state) { toggleDeleteRequesting(state) {
state.toggleDeleteState = RequestState.requesting; state.toggleDeleteState = RequestState.requesting;
@ -169,16 +230,62 @@ export default {
updateEmailFailed(state, error) { updateEmailFailed(state, error) {
state.updateEmailState = RequestState.failed; state.updateEmailState = RequestState.failed;
state.updateEmailError = error; state.updateEmailError = error;
},
uploadBusyGreetingRequesting(state) {
state.uploadBusyGreetingState = RequestState.requesting;
state.uploadBusyGreetingError = null;
},
uploadBusyGreetingSucceeded(state) {
state.uploadBusyGreetingState = RequestState.succeeded;
state.uploadBusyGreetingError = null;
},
uploadBusyGreetingFailed(state, error) {
state.uploadBusyGreetingState = RequestState.failed;
state.uploadBusyGreetingError = error;
},
uploadProgress(state, progress) {
state.uploadProgress = progress;
},
resetProgress(state) {
state.uploadProgress = 0;
},
loadBusyGreetingRequesting(state) {
state.busyGreetingId = null,
state.loadBusyGreetingState = RequestState.requesting;
state.loadBusyGreetingError = null;
},
loadBusyGreetingSucceeded(state, greetings) {
if (greetings.length > 0) {
state.busyGreetingId = greetings[0].id;
}
state.loadBusyGreetingState = RequestState.succeeded;
state.loadBusyGreetingError = null;
},
loadBusyGreetingFailed(state, error) {
state.loadBusyGreetingState = RequestState.failed;
state.loadBusyGreetingError = error;
},
deleteGreetingRequesting(state) {
state.deleteGreetingState = RequestState.requesting;
state.deleteGreetingError = null;
},
deleteGreetingSucceeded(state) {
state.deleteGreetingState = RequestState.succeeded;
state.deleteGreetingError = null;
},
deleteGreetingFailed(state, error) {
state.deleteGreetingState = RequestState.failed;
state.deleteGreetingError = error;
} }
}, },
actions: { actions: {
getVoiceboxSettings(context) { getVoiceboxSettings(context) {
context.commit('loadingRequesting'); context.commit('loadSettingsRequesting');
getVoiceboxSettings(context.getters.subscriberId).then((settings) => { getVoiceboxSettings(context.getters.subscriberId).then((settings) => {
context.commit('loadingSucceeded', settings); context.commit('loadSettingsSucceeded', settings);
}).catch((err) => { }).catch((err) => {
context.commit('loadingFailed', err.message); context.commit('loadSettingsFailed', err.message);
}) });
}, },
toggleDelete(context) { toggleDelete(context) {
context.commit('toggleDeleteRequesting'); context.commit('toggleDeleteRequesting');
@ -186,8 +293,10 @@ export default {
subscriberId: context.getters.subscriberId, subscriberId: context.getters.subscriberId,
value: !context.getters.voiceboxDelete value: !context.getters.voiceboxDelete
}).then(() => { }).then(() => {
return getVoiceboxSettings(context.getters.subscriberId);
}).then((settings)=>{
context.commit('loadedSettings', settings);
context.commit('toggleDeleteSucceeded'); context.commit('toggleDeleteSucceeded');
context.dispatch('getVoiceboxSettings');
}).catch((err) => { }).catch((err) => {
context.commit('toggleDeleteFailed', err.message); context.commit('toggleDeleteFailed', err.message);
context.dispatch('getVoiceboxSettings'); context.dispatch('getVoiceboxSettings');
@ -199,8 +308,10 @@ export default {
subscriberId: context.getters.subscriberId, subscriberId: context.getters.subscriberId,
value: !context.getters.voiceboxAttach value: !context.getters.voiceboxAttach
}).then(() => { }).then(() => {
return getVoiceboxSettings(context.getters.subscriberId);
}).then((settings)=>{
context.commit('loadedSettings', settings);
context.commit('toggleAttachSucceeded'); context.commit('toggleAttachSucceeded');
context.dispatch('getVoiceboxSettings');
}).catch((err) => { }).catch((err) => {
context.commit('toggleAttachFailed', err.message); context.commit('toggleAttachFailed', err.message);
context.dispatch('getVoiceboxSettings'); context.dispatch('getVoiceboxSettings');
@ -212,8 +323,10 @@ export default {
subscriberId: context.getters.subscriberId, subscriberId: context.getters.subscriberId,
value: value value: value
}).then(() => { }).then(() => {
return getVoiceboxSettings(context.getters.subscriberId);
}).then((settings)=>{
context.commit('loadedSettings', settings);
context.commit('updatePinSucceeded'); context.commit('updatePinSucceeded');
context.dispatch('getVoiceboxSettings');
}).catch((err) => { }).catch((err) => {
context.commit('updatePinFailed', err.message); context.commit('updatePinFailed', err.message);
}); });
@ -224,11 +337,58 @@ export default {
subscriberId: context.getters.subscriberId, subscriberId: context.getters.subscriberId,
value: value value: value
}).then(() => { }).then(() => {
return getVoiceboxSettings(context.getters.subscriberId);
}).then((settings)=>{
context.commit('loadedSettings', settings);
context.commit('updateEmailSucceeded'); context.commit('updateEmailSucceeded');
context.dispatch('getVoiceboxSettings');
}).catch((err) => { }).catch((err) => {
context.commit('updateEmailFailed', err.message); context.commit('updateEmailFailed', err.message);
}); });
},
uploadGreeting(context, $options) {
let options = Object.assign($options, {
subscriber_id: context.getters.subscriberId,
type: $options.dir
});
context.commit('uploadBusyGreetingRequesting');
uploadGreeting({
data: options,
onProgress: (progress) => { context.commit('uploadProgress', progress) }
}).then(() => {
context.commit('loadBusyGreetingRequesting');
return getVoiceboxGreetingByType({
id: context.getters.subscriberId,
type: 'busy'
})
}).then((greetings)=>{
context.commit('loadBusyGreetingSucceeded', greetings.items);
context.commit('uploadBusyGreetingSucceeded');
}).catch((err) => {
context.commit('uploadBusyGreetingFailed', err.message);
});
},
abortPreviousRequest() {
abortPreviousRequest();
},
loadBusyGreeting(context) {
context.commit('loadBusyGreetingRequesting');
getVoiceboxGreetingByType({
id: context.getters.subscriberId,
type: 'busy'
}).then((greetings) => {
context.commit('loadBusyGreetingSucceeded', greetings.items);
}).catch((err) => {
context.commit('loadBusyGreetingFailed', err.message);
});
},
deleteGreeting(context, id) {
context.commit('deleteGreetingRequesting');
deleteVoiceboxGreetingById(id).then(() => {
context.commit('deleteGreetingSucceeded');
context.dispatch('loadBusyGreeting');
}).catch((err) => {
context.commit('deleteGreetingFailed', err.message);
});
} }
} }
}; };

@ -1,6 +1,13 @@
@import 'quasar.variables' @import 'quasar.variables'
.q-field
position relative
.csc-toggle-disabled
.q-option-label
color: $faded
.q-list-highlight .q-list-highlight
.csc-item-expanded:hover .csc-item-expanded:hover
background-color white background-color white
@ -118,3 +125,6 @@
.q-field-icon .q-field-icon
color $primary color $primary
.csc-black-label
color black

@ -4,20 +4,22 @@
import Vue from 'vue'; import Vue from 'vue';
import VueResource from 'vue-resource'; import VueResource from 'vue-resource';
import { import {
get get,
getList
} from '../../src/api/common'; } from '../../src/api/common';
import { import {
getVoiceboxSettings getVoiceboxSettings,
getVoiceboxGreetingByType
} from '../../src/api/voicebox'; } from '../../src/api/voicebox';
import { assert } from 'chai'; import { assert } from 'chai';
Vue.use(VueResource); Vue.use(VueResource);
describe('Voicebox', function(){ describe('Voicebox', function() {
const subscriberId = 123; const subscriberId = 123;
it('should get subscriber\'s voicebox settings', function(done){ it('should get subscriber\'s voicebox settings', function(done) {
let data = { let data = {
"_links" : { "_links" : {
@ -64,15 +66,50 @@ describe('Voicebox', function(){
}; };
Vue.http.interceptors = []; Vue.http.interceptors = [];
Vue.http.interceptors.unshift((request, next)=>{ Vue.http.interceptors.unshift((request, next) => {
next(request.respondWith(JSON.stringify(data), { next(request.respondWith(JSON.stringify(data), {
status: 200 status: 200
})); }));
}); });
getVoiceboxSettings(subscriberId).then((result)=>{ getVoiceboxSettings(subscriberId).then((result) => {
assert.deepEqual(result, settings); assert.deepEqual(result, settings);
done(); done();
}).catch((err)=>{ }).catch((err) => {
done(err);
});
});
it('should get subscriber\'s busy greeting', function(done) {
let data = {
"_embedded" : {
"ngcp:voicemailgreetings" : [
{
"dir" : "busy",
"id" : 1,
"subscriber_id" : 123
}
]
},
"total_count" : 1
};
let greeting = {
"dir" : "busy",
"id" : 1,
"subscriber_id" : 123
};
Vue.http.interceptors = [];
Vue.http.interceptors.unshift((request, next) => {
next(request.respondWith(JSON.stringify(data), {
status: 200
}));
});
getVoiceboxGreetingByType({id: subscriberId, type: 'busy'}).then((result) => {
assert.deepEqual(result.items[0], greeting);
done();
}).catch((err) => {
done(err); done(err);
}); });
}); });

@ -8,14 +8,10 @@ describe('Voicebox', function(){
it('should load all voicebox settings into store', function(){ it('should load all voicebox settings into store', function(){
let state = { let state = {
voiceboxSettings: { voiceboxSettingDelete: false,
attach: null, voiceboxSettingAttach: false,
delete: null, voiceboxSettingPin: '',
email: '', voiceboxSettingEmail: '',
id: null,
pin: null,
sms_number: ''
}
}; };
let settings = { let settings = {
attach: true, attach: true,
@ -25,8 +21,25 @@ describe('Voicebox', function(){
pin: 1234, pin: 1234,
sms_number: '' sms_number: ''
}; };
VoiceboxModule.mutations.loadingSucceeded(state, settings); VoiceboxModule.mutations.loadSettingsSucceeded(state, settings);
assert.deepEqual(state.voiceboxSettings, settings); assert.equal(state.voiceboxSettingDelete, settings.delete);
assert.equal(state.voiceboxSettingAttach, settings.attach);
assert.equal(state.voiceboxSettingEmail, settings.email);
assert.equal(state.voiceboxSettingPin, settings.pin);
});
it('should load all busy greeting id into store', function(){
let state = {
busyGreetingId: null
};
let greetings = [
{
id: 1
}
];
VoiceboxModule.mutations.loadBusyGreetingSucceeded(state, greetings);
assert.deepEqual(state.busyGreetingId, greetings[0].id);
}); });
}); });

Loading…
Cancel
Save