kahuna/public/js/upload/dnd-uploader.js (188 lines of code) (raw):
import angular from 'angular';
import template from './dnd-uploader.html';
import {witnessApi} from '../services/api/witness';
import strings from '../strings.json';
export var dndUploader = angular.module('kahuna.upload.dndUploader', [
'kahuna.upload.manager',
witnessApi.name,
'kahuna.edits.service',
'util.async'
]);
dndUploader.controller('DndUploaderCtrl',
['$state', '$window', '$q', 'uploadManager', 'loaderApi', 'editsService',
'apiPoll', 'witnessApi',
function($state, $window, $q, uploadManager, loaderApi, editsService,
apiPoll, witnessApi) {
var ctrl = this;
//hack to prevent grid thumbnails being re-added to the grid for now
//TODO make generic - have API tell kahuna S3 buckets' domain
const gridThumbnailPattern = /https:\/\/media-service([0-9-a-z]+)thumbbucket([0-9-a-z]+)/;
ctrl.uploadFiles = uploadFiles;
ctrl.importWitnessImage = importWitnessImage;
ctrl.isWitnessUri = witnessApi.isWitnessUri;
ctrl.isNotGridThumbnail = (uri) => !gridThumbnailPattern.test(uri);
ctrl.loadUriImage = loadUriImage;
function uploadFiles(files) {
// Queue up files for upload and go to the upload state to
// show progress
uploadManager.upload(files).then(() => {
$state.go('upload', {}, { reload: true });
});
}
function loadAndUpdateWitnessImage(fileUri, metadata, identifiers) {
return loaderApi.import(fileUri, identifiers).then(mediaResp => {
// Wait until image indexed
return apiPoll(() => mediaResp.get());
}).then(fullImage => {
// Override with Witness metadata
const userMetadata = fullImage.data.userMetadata.data.metadata;
const metadataUpdate = editsService.
update(userMetadata, metadata, fullImage);
const rights = {
category: 'guardian-witness'
};
const userRights = fullImage.data.userMetadata.data.usageRights;
const rightsUpdate = editsService.
update(userRights, rights, fullImage);
return $q.all([metadataUpdate, rightsUpdate]).
then(() => fullImage.data.id);
});
}
function loadUriImage(fileUri) {
uploadManager.uploadUri(fileUri);
$state.go('upload', {}, { reload: true });
}
function importWitnessImage(uri) {
const witnessReportId = witnessApi.extractReportId(uri);
if (witnessReportId) {
return witnessApi.getReport(witnessReportId).
then(({fileUri, metadata, identifiers}) => {
return loadAndUpdateWitnessImage(fileUri, metadata, identifiers);
}).then(imageId => {
// Go to image preview page
$state.go('image', {imageId});
}).catch(() => {
$window.alert('An error occurred while importing the ' +
'Witness contribution, please try again');
});
} else {
// Should not get to here
$window.alert('Failed to identify the Witness contribution, please try again');
return $q.reject();
}
}
}]);
/**
* we using the dragging bool[1] as the dragleave event is fired
* when you hover over child elements within a drag zone.
* This way we allow for a small interval and a check on this bool[2] to
* establish whether we have *really* stopped dragging. I am assuming this is how
* it is done on Gmail / Dropbox as you can observe a small delay on their
* `dragover` UI disappearing. What a drag.
*
* This behaviour is pretty well observed:
* https://code.google.com/p/chromium/issues/detail?id=131325
*/
dndUploader.directive('dndUploader', ['$window', '$rootScope', 'delay', 'safeApply', 'vndMimeTypes',
function($window, $rootScope, delay, safeApply, vndMimeTypes) {
return {
restrict: 'E',
controller: 'DndUploaderCtrl',
controllerAs: 'dndUploader',
template: template,
scope: true,
bindToController: true,
link: (scope, element, attrs, ctrl) => {
let dragging = false; // [1]
ctrl.dropzoneExplanation = strings.dropzoneExplanation;
const $$window = angular.element($window);
const activate = () => safeApply(scope, () => ctrl.activated = true);
const deactivate = () => safeApply(scope, () => ctrl.activated = false);
const trackEvent = 'Upload action';
const trackAction = actionName => ({ 'Action': actionName });
const dropAction = content =>
angular.extend({}, trackAction('Drop'), { 'Content': content });
$$window.on('dragover', over);
$$window.on('dragenter', enter);
$$window.on('dragleave', leave);
$$window.on('drop', drop);
scope.$on('$destroy', clean);
const hasType = (types, key) => types.indexOf(key) !== -1;
function hasGridMimetype(types) {
const mimeTypes = Array.from(vndMimeTypes.values());
return types.some(t => mimeTypes.indexOf(t) !== -1);
}
function isGridFriendly(e) {
// we search through the types array as we don't have the `files`
// or `data` (uris etc) ondragenter, only drop.
const types = Array.from(e.dataTransfer.types);
// Dragging out of the Firefox URL bar uses a Mozilla specific MIME type without
// text/uri-list as a fallback
const isUri = hasType(types, 'text/uri-list') || hasType(types, 'text/x-moz-url');
const hasFiles = hasType(types, 'Files');
const isGridImage = hasGridMimetype(types);
const isFriendly = (hasFiles || isUri) && !isGridImage;
return isFriendly;
}
function over(event) {
dragging = isGridFriendly(event);
// The dragover `preventDefault` is to allow for dropping
event.preventDefault();
}
function enter(event) {
if (isGridFriendly(event)) {
dragging = true;
activate();
$rootScope.$emit(
'track:event',
trackEvent,
'Drag',
'Enter',
null,
trackAction('Drag enter')
);
}
}
function leave() {
dragging = false;
delay(50).then(() => {
if (!dragging) {
deactivate();
}
}); // [2]
}
function drop(event) {
const dataTransfer = event.dataTransfer;
const files = Array.from(dataTransfer.files);
const uri = dataTransfer.getData('text/uri-list');
event.preventDefault();
if (isGridFriendly(event)) {
performDropAction(files, uri);
}
scope.$apply(deactivate);
}
function performDropAction(files, uri) {
if (files.length > 0) {
ctrl.uploadFiles(files);
$rootScope.$emit(
'track:event',
trackEvent,
'Drop',
'Files',
null,
dropAction('Files')
);
} else if (ctrl.isWitnessUri(uri)) {
ctrl.importing = true;
ctrl.importWitnessImage(uri).finally(() => {
ctrl.importing = false;
});
$rootScope.$emit(
'track:event',
trackEvent,
'Drop',
'Witness',
null,
dropAction('Witness')
);
} else if (ctrl.isNotGridThumbnail(uri)) {
ctrl.loadUriImage(uri);
}
else {
$window.alert('You must drop valid files or ' +
'URLs to upload them');
$rootScope.$emit(
'track:event',
trackEvent,
'Drop',
'Invalid',
null,
dropAction('Invalid')
);
}
}
function clean() {
$$window.off('dragover', over);
$$window.off('dragenter', enter);
$$window.off('dragleave', leave);
$$window.off('drop', drop);
}
}
};
}]);