/** * This file is part of Threema Web. * * Threema Web is free software: you can redistribute it and/or modify it * under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or (at * your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero * General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with Threema Web. If not, see . */ import {Transition as UiTransition, TransitionService as UiTransitionService} from '@uirouter/angularjs'; import {saveAs} from 'file-saver'; import {bufferToUrl, hasValue} from '../helpers'; import {LogService} from '../services/log'; import {MediaboxService} from '../services/mediabox'; import {MessageService} from '../services/message'; import {TimeoutService} from '../services/timeout'; import {WebClientService} from '../services/webclient'; function showAudioDialog( $mdDialog: ng.material.IDialogService, logService: LogService, blobInfo: threema.BlobInfo, ): void { const log = logService.getLogger('AudioPlayerDialog-C'); $mdDialog.show({ controllerAs: 'ctrl', controller: function() { this.cancel = () => $mdDialog.cancel(); this.audioSrc = bufferToUrl(blobInfo.buffer, blobInfo.mimetype, log); }, template: `

messageTypes.AUDIO_MESSAGE

common.OK
`, parent: angular.element(document.body), clickOutsideToClose: true, }); } export default [ 'LogService', 'WebClientService', 'MediaboxService', 'MessageService', 'TimeoutService', '$rootScope', '$mdDialog', '$timeout', '$transitions', '$translate', '$filter', '$window', function(logService: LogService, webClientService: WebClientService, mediaboxService: MediaboxService, messageService: MessageService, timeoutService: TimeoutService, $rootScope: ng.IRootScopeService, $mdDialog: ng.material.IDialogService, $timeout: ng.ITimeoutService, $transitions: UiTransitionService, $translate: ng.translate.ITranslateService, $filter: ng.IFilterService, $window: ng.IWindowService) { const log = logService.getLogger('MessageMedia-C'); return { restrict: 'EA', scope: {}, bindToController: { message: '=eeeMessage', receiver: '=eeeReceiver', showDownloading: '=eeeShowDownloading', }, controllerAs: 'ctrl', controller: [function() { // On state transitions, clear mediabox $transitions.onStart({}, function(trans: UiTransition) { mediaboxService.clearMedia(); }); this.$onInit = function() { this.type = this.message.type; // Downloading this.downloading = false; this.thumbnailDownloading = false; this.downloaded = false; // Uploading this.uploading = this.message.temporaryId !== undefined && this.message.temporaryId !== null; // AnimGIF detection this.isAnimGif = !this.uploading && (this.message as threema.Message).type === 'file' && (this.message as threema.Message).file.type === 'image/gif'; // Preview thumbnail let thumbnailPreviewUri = null; this.getThumbnailPreviewUri = () => { // Cache thumbnail preview URI if (thumbnailPreviewUri === null && hasValue(this.message) && hasValue(this.message.thumbnail)) { thumbnailPreviewUri = bufferToUrl( (this.message as threema.Message).thumbnail.preview, webClientService.appCapabilities.imageFormat.thumbnail, log, ); } return thumbnailPreviewUri; }; // Thumbnail loading // // Do not show thumbnail in file messages (except anim gif). // If a thumbnail in file messages are available, the thumbnail // will be shown in the file circle this.showThumbnail = this.message.thumbnail !== undefined && ((this.message as threema.Message).type !== 'file' || this.isAnimGif); this.thumbnail = null; this.thumbnailFormat = webClientService.appCapabilities.imageFormat.thumbnail; if (this.message.thumbnail !== undefined) { this.thumbnailStyle = { width: this.message.thumbnail.width + 'px', height: this.message.thumbnail.height + 'px', }; } let loadingThumbnailTimeout: ng.IPromise = null; this.wasInView = false; this.thumbnailInView = (inView: boolean) => { if (this.message.thumbnail === undefined || this.wasInView === inView) { // do nothing return; } this.wasInView = inView; if (!inView) { if (loadingThumbnailTimeout !== null) { timeoutService.cancel(loadingThumbnailTimeout); } this.thumbnailDownloading = false; this.thumbnail = null; } else { if (this.thumbnail === null) { const setThumbnail = (buf: ArrayBuffer) => { this.thumbnail = bufferToUrl( buf, webClientService.appCapabilities.imageFormat.thumbnail, log, ); }; if (this.message.thumbnail.img !== undefined) { setThumbnail(this.message.thumbnail.img); return; } else { this.thumbnailDownloading = true; loadingThumbnailTimeout = timeoutService.register(() => { webClientService .requestThumbnail(this.receiver, this.message) .then((img) => $timeout(() => { setThumbnail(img); this.thumbnailDownloading = false; })) .catch((error) => { // TODO: Handle this properly / show an error message const message = `Thumbnail request has been rejected: ${error}`; this.log.error(message); }); }, 1000, false, 'thumbnail'); } } } }; // For locations, retrieve the coordinates this.location = null; if (this.message.location !== undefined) { this.location = this.message.location; this.downloaded = true; } // Open map link in new window using mapLink-filter this.openMapLink = () => { $window.open($filter('mapLink')(this.location), '_blank'); }; // Play a Audio file in a dialog this.playAudio = (blobInfo: threema.BlobInfo) => showAudioDialog($mdDialog, logService, blobInfo); // Download function this.download = () => { log.debug('Download blob'); if (this.downloading) { log.debug('Download already in progress...'); return; } const message: threema.Message = this.message; const receiver: threema.Receiver = this.receiver; this.downloading = true; webClientService.requestBlob(message.id, receiver) .then((blobInfo: threema.BlobInfo) => { $rootScope.$apply(() => { log.debug('Blob loaded'); this.downloading = false; this.downloaded = true; switch (this.message.type) { case 'image': const caption = message.caption || ''; mediaboxService.setMedia( blobInfo.buffer, blobInfo.filename, blobInfo.mimetype, caption, ); break; case 'video': saveAs(new Blob([blobInfo.buffer]), blobInfo.filename); break; case 'file': if (this.message.file.type === 'image/gif') { // Show inline this.blobBufferUrl = bufferToUrl( blobInfo.buffer, 'image/gif', log); // Hide thumbnail this.showThumbnail = false; } else { saveAs(new Blob([blobInfo.buffer]), blobInfo.filename); } break; case 'audio': // Show inline this.playAudio(blobInfo); break; default: log.warn('Ignored download request for message type', this.message.type); } }); }) .catch((error) => { $rootScope.$apply(() => { this.downloading = false; let contentString; switch (error) { case 'blobDownloadFailed': contentString = 'error.BLOB_DOWNLOAD_FAILED'; break; case 'blobDecryptFailed': contentString = 'error.BLOB_DECRYPT_FAILED'; break; default: contentString = 'error.ERROR_OCCURRED'; break; } const confirm = $mdDialog.alert() .title($translate.instant('common.ERROR')) .textContent($translate.instant(contentString)) .ok($translate.instant('common.OK')); $mdDialog.show(confirm); }); }); }; this.isDownloading = () => { return this.downloading || this.thumbnailDownloading || (this.showDownloading && this.showDownloading()); }; }; }], templateUrl: 'directives/message_media.html', }; }, ];