define(function(require) { var $ = require('jquery'), _ = require('lodash'), monster = require('monster'); var app = { name: 'recordings', css: [ 'app' ], i18n: { 'de-DE': { customCss: false }, 'en-US': { customCss: false } }, appFlags: { recordings: { maxRange: 31, defaultRange: 1, minPhoneNumberLength: 7 } }, requests: { 'recordings.get': { 'verb': 'GET', 'url': 'accounts/{accountId}/recordings?{filters}' }, 'recordings.user.get': { 'verb': 'GET', 'url': 'accounts/{accountId}/users/{userId}/recordings?{filters}' }, 'recordings.delete': { 'verb': 'DELETE', 'url': 'accounts/{accountId}/recordings/{recordingId}' } }, subscribe: {}, load: function(callback) { var self = this; self.initApp(function() { callback && callback(self); }); }, initApp: function(callback) { var self = this; monster.pub('auth.initApp', { app: self, callback: callback }); }, render: function(container) { var self = this; var menus = [ { tabs: [ { text: self.i18n.active().recordings.menuTitles.receivedRECs, callback: self.renderReceivedRECs } ] } ]; var tabConfig = { text: self.i18n.active().recordings.menuTitles.config, menus: [{ tabs: [ { text: 'Account', callback: self.renderRecordConfig } ], }] }; menus[0].tabs.push(tabConfig); monster.ui.generateAppLayout(self, { menus: menus }); }, renderRecordConfig: function (pArgs) { var self = this, args = pArgs || {}, parent = args.container || $('#recordings_app_container .app-content-wrapper'); self.getAccount(function (accountData) { var templateData = { account: $.extend(true, {}, accountData) }; var contentTemplate = $(self.getTemplate({ name: 'recordings-config', data: templateData })); contentTemplate.find('#recordings_config_save').on('click', function() { var formData = monster.ui.getFormData('recordings_config_form'); var formattedData = { "call_recording": { "account": { "inbound": { "offnet": { "enabled": formData['inbound-offnet'], }, "onnet": { "enabled": formData['inbound-onnet'], } }, "outbound": { "offnet": { "enabled": formData['outbound-offnet'], }, "onnet": { "enabled": formData['outbound-onnet'], } } } } }; self.callApi({ resource: 'account.get', data: { accountId: accountData.id }, success: function(data, status) { self.callApi({ resource: 'account.update', data: { accountId: accountData.id, data: $.extend(true, {}, data.data, formattedData) }, success: function(_data, _status) { monster.ui.toast({ type: 'success', message: self.i18n.active().toastrMessages.recordConfigUpdateSuccess, options: { timeOut: 5000 } }); } }); } }); }); parent .fadeOut(function () { $(this) .empty() .append(contentTemplate) .fadeIn(); }); }); }, renderReceivedRECs: function(pArgs) { var self = this, args = pArgs || {}, parent = args.container || $('#recordings_app_container .app-content-wrapper'); self.listRECBoxes(function(recboxes) { var dataTemplate = { recboxes: recboxes, count: recboxes.length }, template = $(self.getTemplate({ name: 'received-recordings', data: dataTemplate })); self.recordingsInitDatePicker(parent, template); self.bindReceivedRECs(template); parent .fadeOut(function() { $(this) .empty() .append(template) .fadeIn(); }); }); }, recordingsInitDatePicker: function(parent, template) { var self = this, dates = monster.util.getDefaultRangeDates(self.appFlags.recordings.defaultRange), fromDate = dates.from, toDate = dates.to; var optionsDatePicker = { container: template, range: self.appFlags.recordings.maxRange }; monster.ui.initRangeDatepicker(optionsDatePicker); template.find('#startDate').datepicker('setDate', fromDate); template.find('#endDate').datepicker('setDate', toDate); template.find('.apply-filter').on('click', function(e) { var recboxId = template.find('#select_recbox').val(); self.displayRECList(parent, recboxId); }); template.find('.toggle-filter').on('click', function() { template.find('.filter-by-date').toggleClass('active'); }); }, bindReceivedRECs: function(template) { var self = this, $selectRECBox = template.find('.select-recbox'); monster.ui.tooltips(template); monster.ui.footable(template.find('.footable')); monster.ui.chosen($selectRECBox, { placeholder_text_single: self.i18n.active().recordings.receivedRECs.actionBar.selectREC.none }); // Default selection when page is loaded self.displayRECList(template, "all"); $selectRECBox.on('change', function() { var recboxId = $(this).val(); self.displayRECList(template, recboxId); }); template.find('#refresh_recordings').on('click', function() { var recboxId = $selectRECBox.val(); if (recboxId !== 'none') { self.displayRECList(template, recboxId); } }); template.find('.mark-as-link').on('click', function() { var folder = $(this).data('type'), recboxId = $selectRECBox.val(), $recordings = template.find('.select-recording:checked'), recordings = []; $recordings.each(function() { recordings.push($(this).data('media-id')); }); template.find('.data-state') .hide(); template.find('.loading-state') .show(); }); template.find('.delete-recordings').on('click', function() { var recboxId = $selectRECBox.val(), $recordings = template.find('.select-recording:checked'), recordings = []; $recordings.each(function() { recordings.push($(this).data('media-id')); }); template.find('.data-state') .hide(); template.find('.loading-state') .show(); self.bulkRemoveRecordings(recboxId, recordings, function() { self.displayRECList(template, recboxId); }); }); template.on('click', '.play-rec', function(e) { var $row = $(this).parents('.recording-row'), $activeRows = template.find('.recording-row.active'); if (!$row.hasClass('active') && $activeRows.length !== 0) { return; } e.stopPropagation(); var recboxId = template.find('#select_recbox').val(), mediaId = $row.data('media-id'); template.find('table').addClass('highlighted'); $row.addClass('active'); self.playRecording(template, recboxId, mediaId); }); template.on('click', '.details-rec', function() { var $row = $(this).parents('.recording-row'), callId = $row.data('call-id'); self.getCDR(callId, function(cdr) { var template = $(self.getTemplate({ name: 'recordings-CDRDialog' })); monster.ui.renderJSON(cdr, template.find('#jsoneditor')); monster.ui.dialog(template, { title: self.i18n.active().recordings.receivedRECs.CDRPopup.title }); }, function() { monster.ui.alert(self.i18n.active().recordings.receivedRECs.noCDR); }); }); var afterSelect = function() { if (template.find('.select-recording:checked').length) { template.find('.hidable').removeClass('hidden'); template.find('.main-select-recording').prop('checked', true); } else { template.find('.hidable').addClass('hidden'); template.find('.main-select-recording').prop('checked', false); } }; template.on('change', '.select-recording', function() { afterSelect(); }); template.find('.main-select-recording').on('click', function() { var $this = $(this), isChecked = $this.prop('checked'); template.find('.select-recording').prop('checked', isChecked); afterSelect(); }); template.find('.select-some-recordings').on('click', function() { var $this = $(this), type = $this.data('type'); template.find('.select-recording').prop('checked', false); if (type !== 'none') { if (type === 'all') { template.find('.select-recording').prop('checked', true); } else if (['new', 'saved', 'deleted'].indexOf(type) >= 0) { template.find('.recording-row[data-folder="' + type + '"] .select-recording').prop('checked', true); } } afterSelect(); }); template.on('click', '.select-line', function() { if (template.find('table').hasClass('highlighted')) { return; } var cb = $(this).parents('.recording-row').find('.select-recording'); cb.prop('checked', !cb.prop('checked')); afterSelect(); }); }, removeOpacityLayer: function(template, $activeRows) { $activeRows.find('.recording-player').remove(); $activeRows.find('.duration, .actions').show(); $activeRows.removeClass('active'); template.find('table').removeClass('highlighted'); }, formatRECURI: function(recboxId, mediaId) { var self = this; return self.apiUrl + 'accounts/' + self.accountId + '/recordings/' + mediaId + '?accept=audio/mpeg&auth_token=' + self.getAuthToken(); }, playRecording: function(template, recboxId, mediaId) { var self = this, $row = template.find('.recording-row[data-media-id="' + mediaId + '"]'); template.find('table').addClass('highlighted'); $row.addClass('active'); $row.find('.duration, .actions').hide(); var uri = self.formatRECURI(recboxId, mediaId), dataTemplate = { uri: uri }, templateCell = $(self.getTemplate({ name: 'cell-recording-player', data: dataTemplate })); $row.append(templateCell); var closePlayerOnClickOutside = function(e) { if ($(e.target).closest('.recording-player').length) { return; } e.stopPropagation(); closePlayer(); }, closePlayer = function() { $(document).off('click', closePlayerOnClickOutside); self.removeOpacityLayer(template, $row); }; $(document).on('click', closePlayerOnClickOutside); templateCell.find('.close-player').on('click', closePlayer); // Autoplay in JS. For some reason in HTML, we can't pause the stream properly for the first play. templateCell.find('audio').get(0).play(); }, recordingsGetRows: function(filters, recboxId, callback) { var self = this; self.newGetRECBoxMessages(filters, recboxId, function(data) { var formattedData = self.formatRecordingsData(data.data, recboxId), dataTemplate = { recordings: formattedData.recordings }, $rows = $(self.getTemplate({ name: 'recordings-rows', data: dataTemplate })); callback && callback($rows, data, formattedData); }); }, displayRECList: function(container, recboxId) { var self = this, fromDate = container.find('input.filter-from').datepicker('getDate'), toDate = container.find('input.filter-to').datepicker('getDate'), filterByDate = container.find('.filter-by-date').hasClass('active'); container.removeClass('empty'); //container.find('.counts-wrapper').hide(); container.find('.count-wrapper[data-type="new"] .count-text').html('?'); container.find('.count-wrapper[data-type="total"] .count-text').html('?'); // Gives a better feedback to the user if we empty it as we click... showing the user something is happening. container.find('.data-state') .hide(); container.find('.loading-state') .show(); container.find('.hidable').addClass('hidden'); container.find('.main-select-recording').prop('checked', false); monster.ui.footable(container.find('.recordings-table .footable'), { getData: function(filters, callback) { if (filterByDate) { filters = $.extend(true, filters, { created_from: monster.util.dateToBeginningOfGregorianDay(fromDate), created_to: monster.util.dateToEndOfGregorianDay(toDate) }); } // we do this to keep context self.recordingsGetRows(filters, recboxId, function($rows, data, formattedData) { container.find('.count-wrapper[data-type="new"] .count-text').html(formattedData.counts.newRecordings); container.find('.count-wrapper[data-type="total"] .count-text').html(formattedData.counts.totalRecordings); callback && callback($rows, data); }); }, afterInitialized: function() { container.find('.data-state') .show(); container.find('.loading-state') .hide(); }, backendPagination: { enabled: false } }); }, formatRecordingsData: function(recordings, recboxId) { var self = this, tryFormatPhoneNumber = function(value) { var minPhoneNumberLength = self.appFlags.recordings.minPhoneNumberLength, prefixedPhoneNumber, formattedPhoneNumber; if (_.size(value) < minPhoneNumberLength) { return { isPhoneNumber: false, value: value, userFormat: value }; } prefixedPhoneNumber = _.head(value) === '+' ? value : /^\d+$/.test(value) // Prepend '+' if there are only numbers ? '+' + value : value; formattedPhoneNumber = monster.util.getFormatPhoneNumber(prefixedPhoneNumber); return { isPhoneNumber: formattedPhoneNumber.isValid, value: formattedPhoneNumber.isValid ? formattedPhoneNumber.e164Number : value, userFormat: formattedPhoneNumber.isValid ? formattedPhoneNumber.userFormat : value }; }, formattedRecordings = _.map(recordings, function(rec) { var to = rec.to.substr(0, rec.to.indexOf('@')), from = rec.from.substr(0, rec.from.indexOf('@')), callerIDName = _.get(rec, 'caller_id_name', ''), formattedTo = tryFormatPhoneNumber(to), formattedFrom = tryFormatPhoneNumber(from), formattedCallerIDName = tryFormatPhoneNumber(callerIDName); rec.direction = rec.origin.split(' ')[0]; return _.merge({ formatted: { to: formattedTo, from: formattedFrom, callerIDName: formattedCallerIDName, duration: monster.util.friendlyTimer(rec.duration_ms / 1000), uri: self.formatRECURI(recboxId, rec.id), callId: monster.util.getModbID(rec.call_id, rec.start), mediaId: rec.id, showCallerIDName: formattedCallerIDName.value !== formattedFrom.value }, direction: rec.direction, timestamp: rec.start }, rec); }), formattedData = { recordings: formattedRecordings, counts: { newRecordings: _.sumBy(recordings, function(rec) { return _ .chain(rec) .get('folder') .isEqual('new') .toInteger() .value(); }), totalRecordings: recordings.length } }; return formattedData; }, getAccount: function (callback) { var self = this; self.callApi({ resource: 'account.get', data: { accountId: self.accountId }, success: function (data) { callback(data.data); } }); }, getCDR: function(callId, callback, error) { var self = this; self.callApi({ resource: 'cdrs.get', data: { accountId: self.accountId, cdrId: callId, generateError: false }, success: function(data) { callback && callback(data.data); }, error: function(data, status, globalHandler) { if (data && data.error === '404') { error && error({}); } else { globalHandler(data, { generateError: true }); } } }); }, newGetRECBoxMessages: function(filters, recboxId, callback) { var self = this; if (recboxId === 'all') { monster.request({ resource: 'recordings.get', data: { accountId: self.accountId, filters: filters }, success: function(data) { callback && callback(data); } }); } else { monster.request({ resource: 'recordings.user.get', data: { accountId: self.accountId, userId: recboxId, filters: filters }, success: function(data) { callback && callback(data); } }); } }, bulkRemoveRecordings: function(recboxId, recordings, callback) { var self = this; $.each(recordings, function(i, recordingId){ monster.request({ resource: 'recordings.delete', data: { accountId: self.accountId, recordingId: recordingId }, success: function(data) { callback && callback(data.data); } }); }) }, listRECBoxes: function(callback) { var self = this; self.callApi({ resource: 'user.list', data: { accountId: self.accountId, filters: { paginate: false } }, success: function(data) { callback && callback(data.data); } }); } }; return app; });