Release 5.2
diff --git a/src/js/upload_ui.js b/src/js/upload_ui.js
new file mode 100644
index 0000000..5b073f5
--- /dev/null
+++ b/src/js/upload_ui.js
@@ -0,0 +1,487 @@
+const CURRENT_LOCALE = chrome.i18n.getMessage('@@ui_locale');

+const MULTIPART_FORMDATA_BOUNDARY = 'Google_Chrome_Screen_Capture';

+const HIDE_ERROR_INFO_DELAY_TIME = 5000;

+

+var UploadUI = {

+  currentSite: '',

+  uploading: false,

+  sites: {},

+

+  registerSite: function(id, siteObject) {

+    this.sites[id] = siteObject;

+  },

+

+  getSiteObject: function(id) {

+    return this.sites[id];

+  },

+

+  setUploading: function(state) {

+    UploadUI.uploading = state;

+  },

+

+  init: function() {

+    // Register supported site for image sharing.

+    UploadUI.registerSite(SinaMicroblog.siteId, SinaMicroblog);

+    UploadUI.registerSite(Facebook.siteId, Facebook);

+    UploadUI.registerSite(Picasa.siteId, Picasa);

+    UploadUI.registerSite(Imgur.siteId, Imgur);

+

+    // Import style sheet for current locale

+    if (CURRENT_LOCALE == 'zh_CN')

+      UI.addStyleSheet('./i18n_styles/zh_CN_upload_image.css');

+    else

+      UI.addStyleSheet('./i18n_styles/en_US_upload_image.css');

+

+    // Get i18n message

+    i18nReplace('shareToSinaMicroblogText', SinaMicroblog.siteId +

+      '_upload_header');

+    i18nReplace('shareToFacebookText', Facebook.siteId + '_upload_header');

+    i18nReplace('shareToPicasaText', Picasa.siteId + '_upload_header');

+    i18nReplace('lastStep', 'return_to_site_selection');

+    i18nReplace('closeUploadWrapper', 'close_upload_wrapper');

+    i18nReplace('imageCaptionText', 'image_caption');

+    i18nReplace('photoSizeTip', 'photo_size_tip');

+    i18nReplace('shareToImgurText', Imgur.siteId + '_upload_header');

+    $('requiredFlag').setAttribute('title',

+      chrome.i18n.getMessage('invalid_caption'));

+

+    // Add event listeners

+    //$('btnUpload').addEventListener('click', UploadUI.showUploadWrapper, false);

+    $('btnSave').addEventListener('click', UploadUI.saveImage, false);

+    $('closeUploadWrapper').addEventListener('click',

+      UploadUI.hideUploadWrapper, false);

+

+    $('picasaBtn').addEventListener('click', function() {

+      UploadUI.showUploadContentWrapper(Picasa.siteId);

+    });

+    $('facebookBtn').addEventListener('click', function() {

+      UploadUI.showUploadContentWrapper(Facebook.siteId);

+    }, false);

+    $('sinaMicroblogBtn').addEventListener('click', function() {

+      UploadUI.showUploadContentWrapper(SinaMicroblog.siteId);

+    }, false);

+    $('imgurBtn').addEventListener('click', function() {

+      UploadUI.showUploadContentWrapper(Imgur.siteId);

+    }, false);

+    $('shareToOtherAccount').addEventListener('click', function() {

+      var currentSite = UploadUI.currentSite;

+

+      // Validate image description first

+      if (UploadUI.validatePhotoDescription(currentSite)) {

+        var callback = function() {

+          var authenticationTip =

+            chrome.i18n.getMessage('user_authentication_tip');

+          UploadUI.showAuthenticationProgress(authenticationTip);

+          UploadUI.getAccessToken(currentSite);

+        };

+        var users = Account.getUsers(currentSite);

+        var numberOfUsers = Object.keys(users).length;

+

+        // Logout when user has authenticated app

+        if (numberOfUsers) {

+          var logoutTip = chrome.i18n.getMessage('user_logout_tip');

+          UploadUI.showAuthenticationProgress(logoutTip);

+          var site = UploadUI.getSiteObject(currentSite);

+          site.logout(callback);

+        } else {

+          callback();

+        }

+      }

+    }, false);

+    $('lastStep').addEventListener('click', UploadUI.showUploadSitesWrapper,

+      false);

+  },

+

+  showUploadWrapper: function() {

+    var uploadWrapper = $('uploadWrapper');

+    UI.show(uploadWrapper);

+

+    // Reset upload wrapper position

+    var viewportWidth = window.innerWidth;

+    var viewportHeight = window.innerHeight;

+    var wrapperWidth = uploadWrapper.offsetWidth;

+    var wrapperHeight = uploadWrapper.offsetHeight;

+

+    var left = (viewportWidth - wrapperWidth) / 2;

+    var top = (viewportHeight - wrapperHeight) / 3;

+    left = left < 0 ? 0 : left;

+    top = top < 0 ? 0 : top;

+

+    var scrollTop = document.body.scrollTop;

+    var scrollLeft = document.body.scrollLeft;

+

+    UI.setStyle(uploadWrapper, {

+      top: top + scrollTop + 'px',

+      left: left + scrollLeft + 'px'

+    });

+    UploadUI.showUploadSitesWrapper();

+    UploadUI.showOverlay();

+  },

+

+  hideUploadWrapper: function() {

+    UI.hide($('uploadWrapper'));

+    UploadUI.hideOverlay();

+  },

+

+  showOverlay: function() {

+    var overlay = $('overlay');

+    UI.setStyle(overlay, {

+      width: document.body.scrollWidth + 'px',

+      height: document.body.scrollHeight + 'px'

+    });

+    UI.show($('overlay'));

+  },

+

+  hideOverlay: function() {

+    UI.hide($('overlay'));

+  },

+

+  updateUploadHeader: function(title) {

+    $('uploadHeader').firstElementChild.firstElementChild.innerText = title;

+  },

+

+  showUploadSitesWrapper: function() {

+    var uploadHeader = chrome.i18n.getMessage('upload_sites_header');

+    UploadUI.updateUploadHeader(uploadHeader);

+    UI.show($('uploadSitesWrapper'));

+    UploadUI.hideUploadContentWrapper();

+    UI.hide($('lastStep'));

+  },

+

+  hideUploadSitesWrapper: function() {

+    UI.hide($('uploadSitesWrapper'));

+  },

+

+  showUploadContentWrapper: function(site) {

+    UploadUI.currentSite = site;

+

+    // Update upload wrapper UI

+    var uploadHeader = chrome.i18n.getMessage(site + '_upload_header');

+    UploadUI.updateUploadHeader(uploadHeader);

+    UploadUI.hideUploadSitesWrapper();

+    UploadUI.hideErrorInfo();

+    UploadUI.hideAuthenticationProgress();

+    UploadUI.clearPhotoDescription();

+    UI.show($('uploadContentWrapper'));

+    UI.show($('lastStep'));

+    UploadUI.updateShareToOtherAccountText(site);

+    UploadUI.togglePhotoDescriptionRequiredFlag(site);

+

+    // Show authenticated accounts of current site

+    UploadUI.clearAccounts();

+    var users = Account.getUsers(site);

+    for (var userId in users) {

+      UploadUI.addAuthenticatedAccount(site, userId);

+    }

+  },

+

+  hideUploadContentWrapper: function() {

+    UI.hide($('uploadContentWrapper'));

+  },

+

+  clearPhotoDescription: function() {

+    $('imageCaption').value = '';

+  },

+

+  validatePhotoDescription: function(site) {

+    var caption = $('imageCaption');

+    var invalidCaptionMsg = chrome.i18n.getMessage('invalid_caption');

+

+    // Validate photo description

+    if (site == SinaMicroblog.siteId && caption.value == '') {

+      UploadUI.showErrorInfo(invalidCaptionMsg);

+      caption.focus();

+      return false;

+    }

+    return true;

+  },

+

+  togglePhotoDescriptionRequiredFlag: function(siteId) {

+    if (siteId == SinaMicroblog.siteId)

+      UI.show($('requiredFlag'));

+    else

+      UI.hide($('requiredFlag'));

+  },

+

+  updateShareToOtherAccountText: function(siteId) {

+    var users = Account.getUsers(siteId);

+    var userLength = Object.keys(users).length;

+    if (userLength)

+      i18nReplace('shareToOtherAccount', 'share_to_other_account');

+    else

+      i18nReplace('shareToOtherAccount', 'share_to_' + siteId + '_account');

+  },

+

+  showErrorInfo: function(text) {

+    UI.show($('errorWrapper'));

+    $('errorInfo').innerHTML = text;

+    setTimeout(function() {

+      UploadUI.hideErrorInfo();

+    }, HIDE_ERROR_INFO_DELAY_TIME);

+  },

+

+  hideErrorInfo: function() {

+    UI.hide($('errorWrapper'));

+  },

+

+  showProgressBar: function(accountId) {

+    var progress = document.querySelector('#' + accountId +

+      ' .progressBar');

+    UI.show(progress);

+  },

+

+  hideProgressBar: function(accountId) {

+    var progress = document.querySelector('#' + accountId +

+      ' .progressBar');

+    UI.hide(progress);

+  },

+

+  showAuthenticationProgress: function(title) {

+    var progress = $('authenticationProgress');

+    progress.setAttribute('title', title);

+    UI.show(progress);

+  },

+

+  hideAuthenticationProgress: function() {

+    UI.hide($('authenticationProgress'));

+  },

+

+  setProgress: function(accountId, loaded, total) {

+    console.log('In setProgress, loaded: ' + loaded + ', total: ' + total);

+    var progress = document.querySelector('#' + accountId + ' .progressBar');

+

+    // One progress bar has 4 parts to represent progress

+    var level = parseInt(loaded / total / 0.25);

+    UI.setStyle(progress, 'background-position-y', '-' + (12 * level) + 'px');

+  },

+

+  showPhotoLink: function(accountId, link) {

+    var photoLink = document.querySelector('#' + accountId + ' .photoLink');

+    photoLink.setAttribute('href', link);

+    UI.setStyle(photoLink, 'display', 'inline');

+  },

+

+  hidePhotoLink: function(accountId) {

+    var photoLink = document.querySelector('#' + accountId + ' .photoLink');

+    UI.hide(photoLink);

+  },

+

+  showUploadInfo: function(accountId, text) {

+    var uploadInfo = document.querySelector('#' + accountId + ' .uploadInfo');

+    uploadInfo.innerHTML = text;

+    UI.show(uploadInfo);

+  },

+

+  hideUploadInfo: function(accountId) {

+    var uploadInfo = document.querySelector('#' + accountId + ' .uploadInfo');

+    UI.hide(uploadInfo);

+  },

+

+  clearAccounts: function() {

+    $('uploadAccountList').innerHTML = '';

+  },

+

+  addAuthenticatedAccount: function(site, userId) {

+    var template = $('accountItemTemplate').innerHTML;

+

+    // Replace i18n message

+    template = template.replace(/\$\{accountId\}/gi, site + '_' + userId);

+    var shareToText = chrome.i18n.getMessage('share_to');

+    template = template.replace(/\$\{accountName\}/gi,

+      shareToText + ' ' + Account.getUser(site, userId)['name']);

+    template = template.replace('${site}', site);

+    template = template.replace('${userId}', userId);

+    template = template.replace(/\$\{deletionTitle\}/gi,

+      chrome.i18n.getMessage('deletion_title'));

+    template = template.replace(/\$\{photoLinkText\}/gi,

+      chrome.i18n.getMessage('photo_link_text'));

+    template = template.replace(/\$\{progressInfo\}/gi,

+      chrome.i18n.getMessage('progress_info'));

+

+    // At most show 3 authenticated users

+    var uploadAccountList = $('uploadAccountList');

+    var accountsNumber = uploadAccountList.childElementCount;

+    if (accountsNumber == 2) {

+      uploadAccountList.removeChild(uploadAccountList.lastElementChild);

+    }

+    uploadAccountList.innerHTML = template + uploadAccountList.innerHTML;

+

+    $('accountName').addEventListener('click', function(e) {

+      UploadUI.upload(site, userId);

+    });

+    $('deleteBtn').addEventListener('click', function(e) {

+      e.stopPropagation();

+      UploadUI.deleteAccountItem(site + '_' + userId);

+    });

+

+    UploadUI.updateShareToOtherAccountText(site);

+  },

+

+  deleteAccountItem: function(accountId, noConfirm) {

+    if (UploadUI.uploading && !noConfirm)

+      return;

+    var confirmText = chrome.i18n.getMessage('account_deletion_confirm');

+    if (noConfirm || confirm(confirmText)) {

+      $('uploadAccountList').removeChild($(accountId));

+

+      // Clear localStorage

+      var site = accountId.split('_')[0];

+      var userId = accountId.split('_')[1];

+      Account.removeUser(site, userId);

+      UploadUI.updateShareToOtherAccountText(site);

+    }

+  },

+

+  upload: function(siteId, userId) {

+    if (UploadUI.uploading)

+      return;

+

+    // Initialize UI

+    var accountId = siteId + '_' + userId;

+    UploadUI.hideErrorInfo();

+    UploadUI.hideUploadInfo(accountId);

+    UploadUI.hidePhotoLink(accountId);

+    if (!UploadUI.validatePhotoDescription(siteId))

+      return;

+    var caption = $('imageCaption').value;

+

+    // Get ready for upload image.

+    photoshop.draw();

+    UploadUI.setUploading(true);

+    UploadUI.showProgressBar(accountId);

+

+    var site = UploadUI.getSiteObject(siteId);

+    var user = Account.getUser(siteId, userId);

+    var imageData = UploadUI.getImageData();

+    var infoText;

+

+    var callback = function(result, photoIdOrMessage) {

+      if (result == 'success') {

+        infoText = chrome.i18n.getMessage('get_photo_link');

+        UploadUI.showUploadInfo(accountId, infoText);

+        site.getPhotoLink(user, photoIdOrMessage, function(photoLinkResult,

+                                                           photoLinkOrMessage) {

+          if (photoLinkResult == 'success') {

+            UploadUI.setUploading(false);

+            UploadUI.hideUploadInfo(accountId);

+            UploadUI.showPhotoLink(accountId, photoLinkOrMessage);

+          } else {

+            UploadUI.showErrorInfo(photoLinkOrMessage);

+          }

+        });

+      } else {

+        if (photoIdOrMessage == 'bad_access_token' ||

+            photoIdOrMessage == 'invalid_album_id') {

+          Account.removeUser(site.siteId, site.currentUserId);

+          UploadUI.deleteAccountItem(accountId, true);

+          UploadUI.getAccessToken(siteId);

+        }

+        UploadUI.setUploading(false);

+        UploadUI.hideProgressBar(accountId);

+        UploadUI.showErrorInfo(chrome.i18n.getMessage(photoIdOrMessage));

+      }

+      UploadUI.hideProgressBar(accountId);

+    };

+

+    if (user) {

+      site.currentUserId = user.id;

+      site.upload(user, caption, imageData, callback);

+    } else {

+      UploadUI.getAccessToken(siteId);

+    }

+  },

+

+  getAccessToken: function(siteId) {

+    var site = UploadUI.getSiteObject(siteId);

+    var accessTokenCallback = function(result, userOrMessage) {

+      if (result == 'success') {

+        UploadUI.getUserInfo(siteId, userOrMessage);

+      } else {

+        // Show error information according to error reason

+        UploadUI.showErrorInfo(chrome.i18n.getMessage(userOrMessage));

+        UploadUI.hideAuthenticationProgress();

+      }

+    };

+

+    site.getAccessToken(accessTokenCallback);

+  },

+

+  getUserInfo: function(siteId, user) {

+    var site = UploadUI.getSiteObject(siteId);

+    site.getUserInfo(user, function(result, userOrMessage) {

+      if (result == 'success') {

+        var userId = user.id;

+        // Check if the authenticated user is added.

+        if (!Account.getUser(siteId, userId)) {

+          site.currentUserId = userId;

+          Account.addUser(siteId, user);

+          UploadUI.addAuthenticatedAccount(siteId, userId);

+        }

+        UploadUI.upload(siteId, userId);

+      } else {

+        var msg = chrome.i18n.getMessage(userOrMessage);

+        UploadUI.showErrorInfo(msg);

+      }

+      UploadUI.hideAuthenticationProgress();

+    });

+  },

+

+  getImageData: function() {

+    var dataUrl = $('canvas').toDataURL('image/png');

+    var imageDataIndex = dataUrl.indexOf('data:image/png;base64,');

+    if (imageDataIndex != 0) {

+      return;

+    }

+

+    // Decode to binary data

+    return atob(dataUrl.substr(imageDataIndex + 22));

+  },

+

+  saveImage: function() {

+    $('canvas').toBlob(function(blob) {

+      console.log(chrome.extension.getBackgroundPage());

+      saveAs(blob, chrome.extension.getBackgroundPage().screenshot.screenshotName+".png");

+    });

+  }

+};

+

+(function() {

+// Cache tab id of edit page, so that we can get tab focus after getting access

+// token.

+var tabIdOfEditPage;

+chrome.tabs.getSelected(null, function(tab) {

+  tabIdOfEditPage = tab.id;

+});

+

+function selectTab(tabId) {

+  chrome.tabs.update(tabId, {

+    selected: true

+  });

+}

+

+function closeTab(tabId) {

+  chrome.tabs.remove(tabId);

+}

+

+function parseAccessToken(senderId, url, siteId) {

+  var sites = UploadUI.sites;

+  for (var id in sites) {

+    var site = sites[id];

+    if ((siteId && id == siteId) || site.isRedirectUrl(url)) {

+      selectTab(tabIdOfEditPage);

+      closeTab(senderId);

+      site.parseAccessToken(url);

+      return true;

+    }

+  }

+  return false;

+}

+

+chrome.extension.onMessage.addListener(function(request, sender) {

+  switch (request.msg) {

+  case 'url_for_access_token':

+    parseAccessToken(sender.tab.id, request.url, request.siteId);

+    break;

+  }

+});

+})();