var App = App || {}; App.Presenters = App.Presenters || {}; App.Presenters.PostUploadPresenter = function( _, jQuery, keyboard, promise, util, api, auth, router, tagList, topNavigationPresenter, messagePresenter) { var KEY_RETURN = 13; var $el = jQuery('#content'); var $messages; var templates = {}; var allPosts = []; var tagInput; var fileDropper; var interactionEnabled = true; var currentUploadId = null; var currentUploadXhr = null; var maxPostSize = 0; function init(params, loaded) { topNavigationPresenter.select('upload'); topNavigationPresenter.changeTitle('Upload'); maxPostSize = parseInt(jQuery('head').attr('data-max-post-size')); promise.wait(util.promiseTemplate('post-upload')) .then(function(template) { templates.upload = template; render(); loaded(); }).fail(function() { console.log(arguments); loaded(); }); } function render() { $el.html(templates.upload({ canUploadPostsAnonymously: auth.hasPrivilege(auth.privileges.uploadPostsAnonymously) })); $messages = $el.find('.messages'); tagInput = new App.Controls.TagInput($el.find('form [name=tags]')); fileDropper = new App.Controls.FileDropper($el.find('[name=post-content]')); fileDropper.onChange = fileHandlerChanged; $el.find('.url-handler input').keydown(urlHandlerKeyPressed); $el.find('.url-handler button').click(urlHandlerButtonClicked); $el.find('thead th.checkbox').click(postTableSelectAllCheckboxClicked); keyboard.keydown('ctrl+up', selectPrevPostTableRow); keyboard.keydown('ctrl+down', selectNextPostTableRow); keyboard.keyup('q', tagInput.focus); $el.find('.remove').click(removeButtonClicked); $el.find('.move-up').click(moveUpButtonClicked); $el.find('.move-down').click(moveDownButtonClicked); $el.find('.previous').click(selectPrevPostTableRow); $el.find('.next').click(selectNextPostTableRow); $el.find('.upload').click(uploadButtonClicked); $el.find('.stop').click(stopButtonClicked); } function getDefaultPost() { return { safety: 'safe', source: null, anonymous: false, tags: [], fileName: null, content: null, url: null, getThumbnail: function() { return promise.makeSilent(function(resolve, reject) { resolve(null); }); }, $tableRow: null, }; } function urlHandlerKeyPressed(e) { if (e.which !== KEY_RETURN) { return; } e.preventDefault(); $el.find('.url-handler button').trigger('click'); } function urlHandlerButtonClicked(e) { var $input = $el.find('.url-handler input'); var url = $input.val().trim(); if (url === '') { return; } var protocol = /^(\w+):\/\//.exec(url); if (!protocol) { url = 'http://' + url; } else { protocol = protocol[1].toLowerCase(); if (protocol !== 'http' && protocol !== 'https') { window.alert('Unsupported protocol: ' + protocol); return; } } $input.val(''); var post = addPostFromURL(url); selectPostTableRow(post); } function fileHandlerChanged(files) { if (files.length > 0) { var posts = []; for (var i = 0; i < files.length; i ++) { if (files[i].size > maxPostSize) { window.alert('File "' + files[i].name + '" is too big - ignoring.' + String.fromCharCode(10) + '(max allowed file size = ' + maxPostSize + ' bytes)'); continue; } var post = addPostFromFile(files[i]); posts.push(post); } selectPostTableRow(_.first(posts)); } } function postAdded(post) { var allPosts = getAllPosts(); allPosts.push(post); setAllPosts(allPosts); createPostTableRow(post); updatePostThumbnailInTable(post); } function postChanged(post) { updatePostTableRow(post); } function postTableRowClicked(e) { e.preventDefault(); if (!interactionEnabled) { return; } var $allCheckboxes = jQuery(this).parents('table').find('tbody input[type=checkbox]'); var $myCheckbox = jQuery(this).parents('tr').find('input[type=checkbox]'); $allCheckboxes.prop('checked', false); $myCheckbox.prop('checked', true); postTableCheckboxesChanged(e); tagInput.focus(); } function postTableCheckboxClicked(e) { if (!interactionEnabled) { e.preventDefault(); return; } if (e.target.nodeName === 'TD') { var checkbox = jQuery(this).find('input[type=checkbox]'); checkbox.prop('checked', !checkbox.prop('checked')); } postTableCheckboxesChanged(e); } function postTableSelectAllCheckboxClicked(e) { if (!interactionEnabled) { e.preventDefault(); return; } var $checkbox = jQuery(this).find('input[type=checkbox]'); if (e.target.nodeName === 'TH') { $checkbox.prop('checked', !$checkbox.prop('checked')); } $el.find('tbody input[type=checkbox]').prop('checked', $checkbox.prop('checked')); postTableCheckboxesChanged(); } function postTableCheckboxesChanged(e) { if (!interactionEnabled) { if (typeof(e) !== 'undefined') { e.preventDefault(); } return; } var $table = $el.find('table'); $table.find('tbody tr').each(function(i, row) { var $row = jQuery(row); var checked = $row.find('input[type=checkbox]').prop('checked'); $row.toggleClass('selected', checked); }); var allPosts = getAllPosts(); var selectedPosts = getSelectedPosts(); $table.find('[name=select-all]').prop('checked', allPosts.length === selectedPosts.length); postTableSelectionChanged(selectedPosts); } function removeButtonClicked(e) { e.preventDefault(); removePosts(getSelectedPosts()); } function moveUpButtonClicked(e) { e.preventDefault(); movePostsUp(getSelectedPosts()); } function moveDownButtonClicked(e) { e.preventDefault(); movePostsDown(getSelectedPosts()); } function uploadButtonClicked(e) { e.preventDefault(); if (!interactionEnabled) { return; } startUpload(); } function stopButtonClicked(e) { e.preventDefault(); stopUpload(); } function makeThumbnail(thumbnailWidth, thumbnailHeight, file) { return promise.makeSilent(function(resolve, reject) { var canvas = document.createElement('canvas'); var img = new Image(); canvas.width = thumbnailWidth; canvas.height = thumbnailHeight; var context = canvas.getContext('2d'); img.onload = function() { //memory still leaks... img.onload = null; context.drawImage(img, 0, 0, thumbnailWidth, thumbnailHeight); URL.revokeObjectURL(img.src); img.src = null; resolve(canvas.toDataURL()); }; img.src = URL.createObjectURL(file); }); } function addPostFromFile(file) { var post = _.extend({}, getDefaultPost(), { fileName: file.name, file: file, getThumbnail: function(thumbnailWidth, thumbnailHeight) { return promise.makeSilent(function(resolve, reject) { if (!file.type.match('image.*')) { resolve(null); return; } if (thumbnailWidth === null || thumbnailHeight === null) { resolve(URL.createObjectURL(post.file)); return; } makeThumbnail(thumbnailWidth, thumbnailHeight, post.file) .then(function(thumbnailDataURL) { resolve(thumbnailDataURL); }); }); }, }); postAdded(post); return post; } function addPostFromURL(url) { var post = _.extend({}, getDefaultPost(), { url: url, fileName: url, }); var matches = url.match(/watch.*?=([a-zA-Z0-9_-]+)/); if (matches) { var youtubeThumbnailURL = 'http://img.youtube.com/vi/' + matches[1] + '/mqdefault.jpg'; post.getThumbnail = function(thumbnailWidth, thumbnailHeight) { return promise.makeSilent(function(resolve, reject) { resolve(youtubeThumbnailURL); }); }; } else if (url.match(/image|img|jpg|png|gif/i)) { post.getThumbnail = function(thumbnailWidth, thumbnailHeight) { return promise.makeSilent(function(resolve, reject) { resolve(url); }); }; } postAdded(post); setPostsSource([post], url); return post; } function createPostTableRow(post) { var $table = $el.find('table'); var $row = $table.find('.template').clone(true); post.$tableRow = $row; $row.removeClass('template'); $row.find('td:not(.checkbox)').click(postTableRowClicked); $row.find('a').click(postTableRowClicked); $row.find('td.checkbox').click(postTableCheckboxClicked); $row.data('post', post); $table.find('tbody').append($row); $row.find('td.checkbox input').attr('id', _.uniqueId()); $row.find('td.checkbox label').attr('for', $row.find('td.checkbox input').attr('id')); postChanged(post); showOrHidePostsTable(); } function updatePostTableRow(post) { var $row = post.$tableRow; $row.find('.tags').text(post.tags.join(', ') || '-'); $row.find('.safety div').attr('class', 'safety-' + post.safety); } function updatePostThumbnailInForm(post) { post.getThumbnail(null, null).then(function(thumbnailDataURL) { var $thumbnail = $el.find('.form-slider .thumbnail'); var $img = $thumbnail.find('img'); var $link = $thumbnail.find('a'); if (thumbnailDataURL === null) { $img.hide(); $link.hide(); } else { $img.show(); $img.attr('src', thumbnailDataURL); $link.show(); $link.attr('href', thumbnailDataURL); } }); } function updatePostThumbnailInTable(post) { post.getThumbnail(30, 30).then(function(thumbnailDataURL) { var $row = post.$tableRow; if (thumbnailDataURL === null) { $row.find('img').attr('src', util.transparentPixel()); } else { $row.find('img').attr('src', thumbnailDataURL); } }); } function getAllPosts() { return allPosts; } function setAllPosts(newPosts) { allPosts = newPosts; } function syncPostsWithTable() { setAllPosts(_.map($el.find('tbody tr'), function(row) { return jQuery(row).data('post'); })); } function getSelectedPosts() { return _.map($el.find('tbody tr.selected'), function(row) { return jQuery(row).data('post'); }); } function postTableSelectionChanged(selectedPosts) { messagePresenter.hideMessages($messages); if (selectedPosts.length === 0) { hidePostEditForm(); } else { tagInput.hideSuggestions(); showPostEditForm(selectedPosts); } $el.find('.post-table-op').prop('disabled', selectedPosts.length === 0); if (selectedPosts.length === 1) { updatePostThumbnailInForm(selectedPosts[0]); } } function hidePostEditForm() { var $postEditForm = $el.find('form'); $postEditForm.parent('.form-slider').slideUp(function() { $postEditForm.find('.thumbnail').hide(); }); } function showPostEditForm(selectedPosts) { var $postEditForm = $el.find('form'); $postEditForm.parent('.form-slider').slideDown(); if (selectedPosts.length !== 1) { $postEditForm.parent('.form-slider').find('.thumbnail').slideUp(); $postEditForm.find('.file-name strong').text('Multiple posts selected'); } else { var post = selectedPosts[0]; $postEditForm.parent('.form-slider').find('.thumbnail').slideDown(); $postEditForm.find('.file-name strong').text(post.fileName || post.url); updatePostThumbnailInForm(post); } var combinedPost = getCombinedPost(selectedPosts); $postEditForm.find('[name=source]').val(combinedPost.source); $postEditForm.find('[name=anonymous]').prop('checked', combinedPost.anonymous); $postEditForm.find('[name=safety]').prop('checked', false); if (combinedPost.safety !== null) { $postEditForm.find('[name=safety][value=' + combinedPost.safety + ']').prop('checked', true); } tagInput.setTags(combinedPost.tags); $postEditForm.find('[name=source]').unbind('change').bind('change', function(e) { setPostsSource(selectedPosts, jQuery(this).val()); }); $postEditForm.find('[name=safety]').unbind('change').bind('change', function(e) { setPostsSafety(selectedPosts, jQuery(this).val()); }); $postEditForm.find('[name=anonymous]').unbind('change').bind('change', function(e) { setPostsAnonymity(selectedPosts, jQuery(this).is(':checked')); }); tagInput.beforeTagAdded = function(tag) { addTagToPosts(selectedPosts, tag); }; tagInput.beforeTagRemoved = function(tag) { removeTagFromPosts(selectedPosts, tag); }; } function getTagIndex(post, tag) { var tags = jQuery.map(post.tags, function(tag) { return tag.toLowerCase(); }); return tags.indexOf(tag.toLowerCase()); } function hasTag(post, tag) { return getTagIndex(post, tag) !== -1; } function getCombinedPost(posts) { var combinedPost = _.extend({}, getDefaultPost()); if (posts.length === 0) { return combinedPost; } _.extend(combinedPost, posts[0]); var tagFilter = function(post) { return function(tag) { return hasTag(post, tag); }; }; for (var i = 1; i < posts.length; i ++) { if (posts[i].safety !== posts[0].safety) { combinedPost.safety = null; } if (posts[i].anonymous !== posts[0].anonymous) { combinedPost.anonymous = null; } if (posts[i].source !== posts[0].source) { combinedPost.source = null; } combinedPost.tags = combinedPost.tags.filter(tagFilter(posts[i])); } return combinedPost; } function setPostsSource(posts, newSource) { _.each(posts, function(post) { var maxSourceLength = 200; if (newSource.length > maxSourceLength) { newSource = newSource.substring(0, maxSourceLength - 5) + '(...)'; } post.source = newSource; postChanged(post); }); } function setPostsSafety(posts, newSafety) { _.each(posts, function(post) { post.safety = newSafety; postChanged(post); }); } function setPostsAnonymity(posts, isAnonymous) { _.each(posts, function(post) { post.anonymous = isAnonymous; postChanged(post); }); } function addTagToPosts(posts, tag) { jQuery.each(posts, function(i, post) { if (!hasTag(post, tag)) { post.tags.push(tag); } postChanged(post); }); } function removeTagFromPosts(posts, tag) { jQuery.each(posts, function(i, post) { if (hasTag(post, tag)) { post.tags.splice(getTagIndex(post, tag), 1); } postChanged(post); }); } function selectPostTableRow(post) { if (post) { var $table = $el.find('table'); $table.find('tbody input[type=checkbox]').prop('checked', false); $table.find('tbody tr').each(function(i, row) { var $row = jQuery(row); if (post === $row.data('post')) { $row.find('input[type=checkbox]').prop('checked', true); return false; } }); postTableCheckboxesChanged(); } } function selectPrevPostTableRow() { selectPostTableRow($el.find('tbody tr.selected:eq(0)').prev().data('post')); return false; } function selectNextPostTableRow() { selectPostTableRow($el.find('tbody tr.selected:eq(0)').next().data('post')); return false; } function showOrHidePostsTable() { if (getAllPosts().length === 0) { util.disableExitConfirmation(); $el.find('#post-upload-step2').fadeOut(); } else { util.enableExitConfirmation(); $el.find('#post-upload-step2').fadeIn(); } } function removePosts(posts) { _.each(posts, function(post) { post.$tableRow.remove(); }); syncPostsWithTable(); showOrHidePostsTable(); postTableCheckboxesChanged(); } function movePostsUp(posts) { _.each(posts, function(post) { var $row = post.$tableRow; $row.insertBefore($row.prev('tr:not(.selected)')); }); syncPostsWithTable(); } function movePostsDown(posts) { _.each(posts.reverse(), function(post) { var $row = post.$tableRow; $row.insertAfter($row.next('tr:not(.selected)')); }); syncPostsWithTable(); } function startUpload() { $el.find('tbody input[type=checkbox]').prop('checked', false); postTableCheckboxesChanged(); $el.find('.upload').hide(); $el.find('.stop').show(); interactionEnabled = false; currentUploadId = Math.random(); uploadNextPost(); } function stopUpload() { currentUploadId = null; showUploadError('Upload stopped.'); if (currentUploadXhr && currentUploadXhr.readystate !== api.AJAX_DONE) { currentUploadXhr.abort(); } } function uploadNextPost() { var priorUploadId = currentUploadId; messagePresenter.hideMessages($messages); var posts = getAllPosts(); if (posts.length === 0) { onUploadCompleted(); return; } messagePresenter.showInfo($messages, 'Uploading in progress…'); var post = posts[0]; var $row = post.$tableRow; var formData = new FormData(); if (post.url) { formData.append('url', post.url); } else { formData.append('content', post.file); formData.append('contentFileName', post.fileName); } formData.append('source', post.source || ''); formData.append('safety', post.safety); formData.append('anonymous', (post.anonymous | 0)); formData.append('tags', post.tags.join(' ')); if (post.tags.length === 0) { showUploadError('No tags set.'); return; } var apiPromise = api.post('/posts', formData); currentUploadXhr = apiPromise.xhr; promise.wait(apiPromise) .then(function(response) { $row.slideUp(function(response) { if (priorUploadId === currentUploadId) { $row.remove(); posts.shift(); setAllPosts(posts); uploadNextPost(); } }); }).fail(function(response) { if (priorUploadId === currentUploadId) { showUploadError(response.json && response.json.error || response); } }); } function onUploadCompleted() { util.disableExitConfirmation(); tagList.refreshTags(); router.navigate('#/posts'); } function showUploadError(message) { $el.find('.upload').show(); $el.find('.stop').hide(); messagePresenter.hideMessages($messages); messagePresenter.showError($messages, message); interactionEnabled = true; } return { init: init, render: render, }; }; App.DI.register('postUploadPresenter', [ '_', 'jQuery', 'keyboard', 'promise', 'util', 'api', 'auth', 'router', 'tagList', 'topNavigationPresenter', 'messagePresenter'], App.Presenters.PostUploadPresenter);