var App = App || {}; App.Presenters = App.Presenters || {}; App.Presenters.PostUploadPresenter = function( _, jQuery, keyboard, promise, util, auth, api, router, 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; function init(params, loaded) { topNavigationPresenter.select('upload'); topNavigationPresenter.changeTitle('Upload'); 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('.upload').click(uploadButtonClicked); $el.find('.stop').click(stopButtonClicked); } function getDefaultPost() { return { safety: 'safe', source: null, anonymous: false, tags: [], fileName: null, content: null, url: null, thumbnail: 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 ++) { var post = addPostFromFile(files[i]); posts.push(post); } selectPostTableRow(_.first(posts)); } } function postAdded(post) { var allPosts = getAllPosts(); allPosts.push(post); setAllPosts(allPosts); createPostTableRow(post); } function postChanged(post) { updatePostTableRow(post); } function postThumbnailLoaded(post) { var selectedPosts = getSelectedPosts(); if (selectedPosts.length === 1 && selectedPosts[0] === post && post.thumbnail !== null) { updatePostThumbnailInForm(post); } updatePostThumbnailInTable(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); } 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 postTableRowImageHovered(e) { var $img = jQuery(this); if ($img.parents('tr').data('post').thumbnail) { var $lightbox = jQuery('#lightbox'); $lightbox.find('img').attr('src', $img.attr('src')); $lightbox .show() .css({ left: ($img.position().left + $img.outerWidth()) + 'px', top: ($img.position().top + ($img.outerHeight() - $lightbox.outerHeight()) / 2) + 'px', }); } } function postTableRowImageUnhovered(e) { jQuery('#lightbox').hide(); } 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 addPostFromFile(file) { var post = _.extend({}, getDefaultPost(), {fileName: file.name}); fileDropper.readAsDataURL(file, function(content) { post.content = content; if (file.type.match('image.*')) { post.thumbnail = content; postThumbnailLoaded(post); } }); postAdded(post); return post; } function addPostFromUrl(url) { var post = _.extend({}, getDefaultPost(), {url: url, source: url, fileName: url}); postAdded(post); var matches = url.match(/watch.*?=([a-zA-Z0-9_-]+)/); if (matches) { var youtubeThumbnailUrl = 'http://img.youtube.com/vi/' + matches[1] + '/mqdefault.jpg'; post.thumbnail = youtubeThumbnailUrl; postThumbnailLoaded(post); } else if (url.match(/image|img|jpg|png|gif/i)) { post.thumbnail = url; postThumbnailLoaded(post); } 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('td.checkbox').click(postTableCheckboxClicked); $row.find('img').mouseenter(postTableRowImageHovered); $row.find('img').mouseleave(postTableRowImageUnhovered); $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) { if (post.thumbnail === null) { $el.find('.form-slider .thumbnail img').hide(); } else { $el.find('.form-slider .thumbnail img').show()[0].setAttribute('src', post.thumbnail); } } function updatePostThumbnailInTable(post) { var $row = post.$tableRow; if (post.thumbnail === null) { $row.find('img')[0].setAttribute('src', util.transparentPixel()); //huge speedup thanks to this condition } else if ($row.find('img').attr('src') !== post.thumbnail) { $row.find('img')[0].setAttribute('src', post.thumbnail); } } 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); } 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 getCombinedPost(posts) { var combinedPost = _.extend({}, getDefaultPost()); if (posts.length === 0) { return combinedPost; } _.extend(combinedPost, posts[0]); var tagFilter = function(post) { return function(tag) { return post.tags.indexOf(tag) !== -1; }; }; 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) { //todo: take care of max source length 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) { var index = post.tags.indexOf(tag); if (index === -1) { post.tags.push(tag); } postChanged(post); }); } function removeTagFromPosts(posts, tag) { jQuery.each(posts, function(i, post) { var index = post.tags.indexOf(tag); if (index !== -1) { post.tags.splice(index, 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')); } function selectNextPostTableRow() { selectPostTableRow($el.find('tbody tr.selected:eq(0)').next().data('post')); } 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, 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 = {}; if (post.url) { formData.url = post.url; } else { formData.content = post.content; formData.contentFileName = post.fileName; } formData.source = post.source; formData.safety = post.safety; formData.anonymous = (post.anonymous | 0); formData.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(); 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', 'auth', 'api', 'router', 'topNavigationPresenter', 'messagePresenter'], App.Presenters.PostUploadPresenter);