diff --git a/src/apps/callflows/app.js b/src/apps/callflows/app.js index 7ebe04f..d4bf7a7 100644 --- a/src/apps/callflows/app.js +++ b/src/apps/callflows/app.js @@ -5,6 +5,7 @@ define(function(require) { var appSubmodules = [ 'blacklist', 'callcenter', + 'audiomacro', 'conference', 'device', 'directory', diff --git a/src/apps/callflows/i18n/en-US.json b/src/apps/callflows/i18n/en-US.json index 40b602d..11a2c76 100644 --- a/src/apps/callflows/i18n/en-US.json +++ b/src/apps/callflows/i18n/en-US.json @@ -27,6 +27,72 @@ "qubicle_tip": "Route to a queue", "which_queue": "Which queue?" }, + "audio_macro": { + "dialog_title": "Audio Macro", + "macro_type": "Macro type", + "media": "Media", + "endless_playback": "Endless Playback", + "language": "Language", + "voice": "Voice", + "answer": "Answer", + "loop_count": "Loop count", + "terminators": "Terminators", + "engine": { + "label": "Engine", + "flite": "Flite", + "google": "Google", + "ispeech": "Ispeach", + "voicefabric": "Voicefabric" + }, + "text": "Text", + "prompt": "Prompt", + "gender": { + "label": "Gender", + "masculine": "Masculine", + "feminine": "Feminine", + "neuter": "Neuter" + }, + "method": { + "label": "Method", + "pronounced": "Pronounced", + "iterated": "Iterated", + "counted": "Counted", + "none": "None" + }, + "say": { + "type": { + "label": "Type", + "number": "Number", + "items": "Items", + "persons": "Persons", + "messages": "Messages", + "currency": "Currency", + "time_measurement": "Time measurement", + "current_date": "Current date", + "current_time": "Current time", + "current_date_time": "Current date time", + "telephone_number": "Telephone number", + "telephone_extension": "Telephone extension", + "url": "URL", + "ip_address": "IP Address", + "e-mail_address": "Email", + "postal_address": "Postal address", + "account_number": "Account number", + "name_spelled": "Name spelled", + "name_phonetic": "Name phonetic", + "short_date_time": "Short date time" + } + }, + "new_macro_btn_text": "Add macro", + "common_terminators": "Terminators", + "accordion_titles": { + "play": "Play", + "say": "Say", + "tts": "TTS", + "prompt": "Prompt" + }, + "are_you_sure_you_want_to_delete": "Are you sure you want to delete this item?" + }, "conference": { "conference": "Conference", "conference_tip": "Connect a caller to a Meet-Me conference bridge", @@ -928,7 +994,7 @@ "check_voicemail": "Check Voicemail", "miscellaneous_cat": "Miscellaneous", "direct_to_voicemail": "Direct to Voicemail", - "eavesdrop_feature": "Eavesdrop Feature", + "eavesdrop": "Eavesdrop", "eavesdropPopup": { "title": "Eavesdrop", "eavesdropTarget": { diff --git a/src/apps/callflows/style/app.css b/src/apps/callflows/style/app.css index 6147754..4347383 100644 --- a/src/apps/callflows/style/app.css +++ b/src/apps/callflows/style/app.css @@ -10,6 +10,7 @@ @import url('../submodules/featurecodes/featurecodes.css'); @import url('../submodules/temporalset/temporalset.css'); @import url('../submodules/callcenter/callcenter.css'); +@import url('../submodules/audiomacro/audiomacro.css'); @import url('../../../css/vendor/bootstrap-tour.css'); /* style.css */ #ws_callflow > .callflow { @@ -1272,6 +1273,9 @@ margin-left:20px; display:inline; } +.callflows-port .pill-content .edit_create a { + color: #EEE; +} .callflows-port .pill-content .edit_create > a:not(:first-child) { margin-left: 15px; diff --git a/src/apps/callflows/submodules/audiomacro/audiomacro.css b/src/apps/callflows/submodules/audiomacro/audiomacro.css new file mode 100644 index 0000000..1091ca1 --- /dev/null +++ b/src/apps/callflows/submodules/audiomacro/audiomacro.css @@ -0,0 +1,89 @@ +.accordion-header-link { + float: right; +} + +.audio-macro-form { + margin-bottom: 0; +} + +.audio-macro-form .popup_field textarea { + margin-bottom: 0; +} + +.form-part_hidden { + display: none; +} + +.audio-macro-dialog {} + +.audio-macro-dialog .ui-state-default .fa{ + color: #2297FF; +} + +.audio-macro-dialog .ui-state-hover .fa { + color: white; +} + + +.audio-macro-dialog .ui-state-active { + border: 1px solid #2297FF; + color: white; + background: #2297FF; +} + +.audio-macro-dialog .ui-state-active .fa, +.audio-macro-dialog .ui-state-focus .fa { + color: white; +} + +.audio-macro-dialog .ui-state-hover, +.audio-macro-dialog .ui-state-focus { + border: 1px solid #2297FF; + background: #2297FF; + color: white; +} + +.audio-macro-dialog .ui-state-default .ui-icon { + background: none; + width: 0; + height: 0; + border-top: 5px solid transparent; + border-bottom: 5px solid transparent; + border-left: 7px solid #2297FF; + margin-top: -5px; +} + +.audio-macro-dialog .ui-state-focus .ui-icon, +.audio-macro-dialog .ui-state-hover .ui-icon { + border-left: 7px solid white; +} + +.audio-macro-dialog .ui-state-active .ui-icon { + border-top: 7px solid white; + border-bottom: none; + border-left: 5px solid transparent; + border-right: 5px solid transparent; +} + +.dialog_popup .form_content input[type="text"], +.dialog_popup .form_content input[type="number"] { + width: 100%; + max-width: 200px; +} + +.dialog_popup .form_content .field_wrapper { + float: left; +} + +.dialog_popup .form_content .checkbox-label-wrapper { + padding-left: 140px; + text-align: left; + width: 200px; + margin-right: 0; + margin-bottom: 13px; + padding-top: 0; +} + +.dialog_content-part { + margin: 15px 0; +} diff --git a/src/apps/callflows/submodules/audiomacro/audiomacro.js b/src/apps/callflows/submodules/audiomacro/audiomacro.js new file mode 100644 index 0000000..5416623 --- /dev/null +++ b/src/apps/callflows/submodules/audiomacro/audiomacro.js @@ -0,0 +1,587 @@ +define(function(require) { + var $ = require('jquery'), + _ = require('lodash'), + Handlebars = require('handlebars'), + monster = require('monster'); + + var macrosAllList = [ + { + name: 'Play', + value: 'play' + }, { + name: 'TTS', + value: 'tts' + }, { + name: 'Prompt', + value: 'prompt' + }, { + name: 'Say', + value: 'say' + } + ]; + + var languages = [ + 'en-us', + 'en-ca', + 'en-au', + 'en-gb', + 'es-us', + 'us-us', + 'zh-cn', + 'zh-hk', + 'zh-tw', + 'ja-jp', + 'ko-kr', + 'da-dk', + 'de-de', + 'ca-es', + 'es-es', + 'fi-fi', + 'fr-ca', + 'fr-fr', + 'it-it', + 'nb-no', + 'nl-nl', + 'pl-pl', + 'pt-br', + 'pt-pt', + 'ru-ru', + 'sv-se', + 'hu-hu', + 'cs-cz', + 'tr-tr', + ]; + + var voices = [ + 'female', + 'male' + ]; + + var app = { + requests: { + 'callflows.audio_macro.media.prompts': { + 'verb': 'GET', + 'url': 'accounts/{accountId}/media/prompts' + }, + }, + + subscribe: { + 'callflows.fetchActions': 'audiomacroDefineActions', + 'callflows.audiomacro.editPopup': 'audiomacroPopupEdit', + 'callflows.audiomacro.edit': 'audiomacroEdit' + }, + + audiomacroDefineActions: function(args) { + var self = this, + callflow_nodes = args.actions; + + self.audiomacroInitHandlebarsHelpers(); + + $.extend(callflow_nodes, { + 'audio_macro[]': { + name: 'Audio macro', + icon: 'link', + category: 'Advanced', + module: 'audio_macro', + tip: 'Tooltip for this', + data: { + macros: [], + terminators: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '*', '0', '#'] + }, + rules: [ + { + type: 'quantity', + maxSize: '1' + } + ], + isUsable: 'true', + weight: 48, + caption: function(node, caption_map) { + var macros = node.getMetadata('macros'); + return self.audiomacroGetCaptionText(macros); + }, + edit: function(node, callback) { + self.audiomacroEditAudioMacro(node, callback); + }, + editEntity: 'callflows.audiomacro.edit' + } + }); + }, + + audiomacroGetCaptionText: function (macros) { + var self = this; + var captionText = '', + accordionTitles = self.i18n.active().callflows.audio_macro.accordion_titles, + macroName = '', + macroI18nName = ''; + for(var i = 0, len = macros.length; i < len; i++) { + macroName = macros[i].macro; + macroI18nName = accordionTitles.hasOwnProperty(macroName) ? + accordionTitles[macroName] : + macroName; + if(i === 0) { + captionText += macroI18nName + } else { + captionText += ', ' + macroI18nName + } + } + return captionText; + }, + + audiomacroInitHandlebarsHelpers: function () { + Handlebars.registerHelper('join', function(array, separator) { + return Array.isArray(array) ? array.join(separator) : ''; + }); + + Handlebars.registerHelper('safeVal', function (value, safeValue) { + var out = value || safeValue; + return new Handlebars.SafeString(out); + }); + }, + + audiomacroGetData: function (callback) { + var self = this; + var requestsList = { + media: function (callback) { + self.audiomacroGetMediaList(function (media) { + callback(null, media); + }) + }, + prompts: function (callback) { + self.audiomacroGetPrompts(function (prompts) { + callback(null, prompts); + }) + } + }; + monster.parallel(requestsList, function(error, results) { + callback && callback(results); + }); + }, + + audiomacroGetPrompts: function (callback) { + var self = this; + monster.request({ + resource: 'callflows.audio_macro.media.prompts', + data: { + accountId: self.accountId, + filters: { + paginate: false + } + }, + success: function(response) { + var prompts = response.data; + if(prompts.length > 0) { + prompts = prompts[0]; + } + + callback && callback(prompts); + }, + error: function(response) { + console.log('Error while getting prompts'); + console.log(response) + } + }); + }, + + audiomacroGetMediaList: function(callback) { + var self = this; + + self.callApi({ + resource: 'media.list', + data: { + accountId: self.accountId, + filters: { + paginate: false + } + }, + success: function(data) { + var mediaList = _.sortBy(data.data, function(item) { return item.name.toLowerCase(); }); + + mediaList.unshift( + { + id: 'silence_stream://300000', + name: self.i18n.active().callflows.media.silence + }, + { + id: 'shoutcast', + name: self.i18n.active().callflows.media.shoutcastURL + } + ); + + callback && callback(mediaList); + } + }); + }, + + audiomacroEditAudioMacro (node, callback) { + var self = this; + + self.audiomacroGetData(function(data) { + var $popup, + $dialog, + macros = node.getMetadata('macros'), + commonTerminators = node.getMetadata('terminators'); + + // TODO: Remove this test data in the future + /* { + "macros": [ + { + "macro": "play", + "id": "27502f3d5b5240a976748a2e49faf54f", + "endless_playback": true, + "answer": true, // Whether to answer an unanswered call + "loop_count": 2, // How many times to loop the media + "terminators": ["1", "2", "3"] + }, + { + "macro": "tts", + "endless_playback": true, + "engine": "ispeech", // 'google', 'flite', 'voicefabric' + "language": "en-us", + "terminators": ["1", "2", "3"], + "text": "this can be said", + "voice": "female" + }, + { + "macro": "prompt", + "id": "agent-resume", + "language": "en-us", + "terminators": ["1", "2", "3"] + }, + { + "macro": "say", + "gender": "masculine", // "feminine", "neuter" + "language": "en-us", + "method": "pronounced", // 'none', 'iterated', 'counted' + "terminators": ["1", "2", "3"], + "text": "123", + "type": "number" // 'items', 'persons', 'messages' + } + ], + "terminators": ["1","2","3","4","5","6","7","8","9","*","0","#"] + }; */ + + var accordionItems = []; + + for(var i = 0, len = macros.length; i < len; i++) { + accordionItems.push({ + title: self.audiomacroGetMacroI18nName(macros[i].macro), + content: self.getTemplate({ + name: 'form', + data: { + macro: macros[i], + macrosAllList: macrosAllList, + media: data.media, + languages: languages, + voices: voices, + prompts: data.prompts, + randomInt: monster.util.randomString(7) + }, + submodule: 'audiomacro' + }) + }) + } + + var accordion_html = self.getTemplate({ + name: 'accordion', + data: { + items: accordionItems, + terminators: commonTerminators.join('') + }, + submodule: 'audiomacro' + }); + + $dialog = $(self.getTemplate({ + name: 'dialog', + data: { + content: accordion_html + }, + submodule: 'audiomacro' + })); + + $popup = monster.ui.dialog($dialog, { + title: self.i18n.active().callflows.audio_macro.dialog_title, + minHeight: '0', + width: 500, + beforeClose: function() { + if (typeof callback === 'function') { + callback(); + } + } + }); + + self.audiomacroInitFormBehavior($dialog); + self.audiomacroInitDialogBehavior({ + node: node, + callback: callback, + $dialog: $dialog, + $popup: $popup, + macrosAllList: macrosAllList, + media: data.media, + languages: languages, + voices: voices, + prompts: data.prompts + }); + self.audiomacroInitAccordionBehavior(); + }); + }, + + audiomacroInitFormBehavior: function ($container) { + var self = this; + $container.find('.js-macro').change(function() { + var $form = $(this).closest('form'), + macroI18nName = self.audiomacroGetMacroI18nName(this.value); + $form.find('.form-part').addClass('form-part_hidden'); + $form.find('.form-part.form-part_' + this.value).removeClass('form-part_hidden'); + $(this).closest('.js-accordion-group').find('.js-accordion-group-title').text(macroI18nName); + }); + }, + + audiomacroGetMacroI18nName: function (macroKeyName) { + var self = this, + accordionTitles = self.i18n.active().callflows.audio_macro.accordion_titles; + + return accordionTitles.hasOwnProperty(macroKeyName) ? + accordionTitles[macroKeyName] : + macroKeyName; + }, + + audiomacroInitDialogBehavior: function (data) { + var self = this, + $dialog = data.$dialog, + $popup = data.$popup, + node = data.node, + callback = data.callback; + + $dialog.find('.js-add-item').on('click', function (e) { + e.preventDefault(); + self.audiomacroAddAccordionItem({ + macrosAllList: data.macrosAllList, + media: data.media, + languages: data.languages, + voices: data.voices, + prompts: data.prompts, + randomInt: monster.util.randomString(7) + }); + }); + + self.audiomacroInitAccordionRemoveItemBehavior($dialog); + + $dialog.find('.js-save').click(function() { + self.audiomacroSaveAudioMacro(node, callback); + $popup.dialog('close'); + }); + + monster.ui.tooltips($dialog); + }, + + audiomacroInitAccordionRemoveItemBehavior: function($dialog) { + var self = this; + $dialog.find('.js-remove-item').on('click', function(e){ + e.stopPropagation(); + e.preventDefault(); + var confirmMessage = self.i18n.active().callflows.audio_macro.are_you_sure_you_want_to_delete; + if(confirm(confirmMessage)) { + $(this).closest('.js-accordion-group').remove(); + $('.js-accordion').accordion('refresh'); + } + }); + }, + + audiomacroInitAccordionBehavior: function () { + $('.js-accordion') + .accordion({ + header: '> div > h3', + collapsible: true, + heightStyle: 'content', + active: false + }) + .sortable({ + axis: 'y', + handle: 'h3', + stop: function( event, ui ) { + // IE doesn't register the blur when sorting + // so trigger focusout handlers to remove .ui-state-focus + ui.item.children('h3').triggerHandler('focusout'); + $(this).accordion('refresh'); + } + }); + }, + + audiomacroAddAccordionItem: function (data) { + data.macro = { // default macro for display + 'macro': 'play' + }; + + var self = this, + formHtml = self.getTemplate({ + name: 'form', + data: data, + submodule: 'audiomacro' + }), + accordionTitles = self.i18n.active().callflows.audio_macro.accordion_titles, + macroName = data.macro.macro, + macroI18nName = accordionTitles.hasOwnProperty(macroName) ? + accordionTitles[macroName] : + macroName; + var $accordionItem = $(self.getTemplate({ + name: 'accordion_item', + data: { + title: macroI18nName, + content: formHtml + }, + submodule: 'audiomacro' + })); + + self.audiomacroInitFormBehavior($accordionItem); + self.audiomacroInitAccordionRemoveItemBehavior($accordionItem); + + $('.js-accordion').append($accordionItem).accordion('refresh'); + }, + + audiomacroSaveAudioMacro: function (node, callback) { + var self = this, + resultMacros = []; + + $('.js-accordion-group').each(function (i, el) { + var itemData = {}; + itemData.macro = $(el).find('select[name="macro"]').val(); + var $macroContainer = $(el).find('.form-part_' + itemData.macro); + var macroFormData = monster.ui.getFormData($macroContainer[0]); + _.extend(itemData, macroFormData); + + if(itemData.terminators) { + itemData.terminators = itemData.terminators.split(''); + } + + if (itemData.macro === 'play' && itemData.loop_count) { + itemData.loop_count = parseInt(itemData.loop_count); + } + + resultMacros.push(itemData); + }); + var commonTerminators = $('#common-terminators').val().split(''); + + node.setMetadata('macros', resultMacros); // self.audiomacroPrepareDataForSave(resultMacros) + node.setMetadata('terminators', commonTerminators); + node.caption = self.audiomacroGetCaptionText(resultMacros); + }, + + audiomacroPopupEdit: function(args) { + var self = this, + popup_html = $('
'), + callback = args.callback, + popup, + data = args.data, + data_defaults = args.data_defaults; + + popup_html.css({ + height: 500, + 'overflow-y': 'scroll' + }); + + self.queueEdit({ + data: data, + parent: popup_html, + target: $('.inline_content', popup_html), + callbacks: { + save_success: function(_data) { + popup.dialog('close'); + + if (typeof callback === 'function') { + callback(_data); + } + }, + delete_success: function() { + popup.dialog('close'); + + if (typeof callback === 'function') { + callback({ data: {} }); + } + }, + after_render: function() { + popup = monster.ui.dialog(popup_html, { + title: (data.id) ? self.i18n.active().callflows.callcenter.editQueue : self.i18n.active().callflows.callcenter.createQueue + }); + } + }, + data_defaults: data_defaults + }); + }, + + audiomacroEdit: function(args) { + var self = this, + data = args.data, + parent = args.parent || $('#queue-content'), + target = args.target || $('#queue-view', parent), + _callbacks = args.callbacks || {}, + callbacks = { + save_success: _callbacks.save_success || function(_data) { + self.queueRenderList(parent); + + self.queueEdit({ + data: { + id: _data.data.id + }, + parent: parent, + target: target, + callbacks: callbacks + }); + }, + + save_error: _callbacks.save_error, + + delete_success: _callbacks.delete_success || function() { + target.empty(); + + self.queueRenderList(parent); + }, + + delete_error: _callbacks.delete_error, + + after_render: _callbacks.after_render + }, + defaults = { + data: $.extend(true, { + connection_timeout: '300', + member_timeout: '5' + /* caller_exit_key: '#' */ + }, args.data_defaults || {}), + field_data: { + sort_by: { + 'first_name': self.i18n.active().callflows.callcenter.first_name, + 'last_name': self.i18n.active().callflows.callcenter.last_name + } + } + }; + + self.getUsersList(function(users) { + defaults.field_data.users = users; + + if (typeof data === 'object' && data.id) { + self.queueGet(data.id, function(queueData) { + var render_data = $.extend(true, defaults, queueData); + + render_data.field_data.old_list = []; + if ('agents' in queueData.data) { + render_data.field_data.old_list = queueData.data.agents; + } + self.queueRender(render_data, target, callbacks); + + if (typeof (callbacks.after_render) === 'function') { + callbacks.after_render(); + } + }); + } else { + self.queueRender(defaults, target, callbacks); + + if (typeof (callbacks.after_render) === 'function') { + callbacks.after_render(); + } + } + }); + } + }; + + return app; +}); diff --git a/src/apps/callflows/submodules/audiomacro/views/accordion.html b/src/apps/callflows/submodules/audiomacro/views/accordion.html new file mode 100644 index 0000000..7c251d5 --- /dev/null +++ b/src/apps/callflows/submodules/audiomacro/views/accordion.html @@ -0,0 +1,23 @@ +
+ {{#each items}} +
+

+ {{ this.title }} + + + +

+ +
+ {{{ this.content }}} +
+
+ {{/each}} +
+
+ +
+
+ {{ i18n.callflows.audio_macro.common_terminators }}:
+ +
diff --git a/src/apps/callflows/submodules/audiomacro/views/accordion_item.html b/src/apps/callflows/submodules/audiomacro/views/accordion_item.html new file mode 100644 index 0000000..7438fa1 --- /dev/null +++ b/src/apps/callflows/submodules/audiomacro/views/accordion_item.html @@ -0,0 +1,12 @@ +
+

+ {{ title }} + + + +

+ +
+ {{{ content }}} +
+
diff --git a/src/apps/callflows/submodules/audiomacro/views/dialog.html b/src/apps/callflows/submodules/audiomacro/views/dialog.html new file mode 100644 index 0000000..b1471db --- /dev/null +++ b/src/apps/callflows/submodules/audiomacro/views/dialog.html @@ -0,0 +1,8 @@ +
+ {{{ content }}} +
+ +
+
diff --git a/src/apps/callflows/submodules/audiomacro/views/form.html b/src/apps/callflows/submodules/audiomacro/views/form.html new file mode 100644 index 0000000..583a065 --- /dev/null +++ b/src/apps/callflows/submodules/audiomacro/views/form.html @@ -0,0 +1,255 @@ +
+
+ + +
+ + + + + + +
+ +
+ + + + + + + + + + + +
+ +
+ + + + + +
+ +
+ + + + + + + + + + + +
+
+