kahuna/public/js/services/api/leases.js (173 lines of code) (raw):
import angular from 'angular';
import './media-api';
import '../../services/image-list';
import {service} from '../../edits/service';
import { trackAll } from '../../util/batch-tracking';
import { getApiImageAndApiLeasesIfUpdated } from './leases-helper';
import { Subject } from 'rx';
var leaseService = angular.module('kahuna.services.lease', [
service.name
]);
leaseService.factory('leaseService', [
'$rootScope',
'$q',
'imageAccessor',
'imageList',
'mediaApi',
'editsService',
'apiPoll',
function ($rootScope, $q, imageAccessor, imageList, mediaApi, editsService, apiPoll) {
let leasesRoot;
function getLeasesRoot() {
if (!leasesRoot) {
leasesRoot = mediaApi.root.follow('leases');
}
return leasesRoot;
}
function getLeases(images) {
// search page has fancy image list
if (angular.isDefined(images.toArray)) {
images = images.toArray();
}
return $q.all(images.map(i => i.get()))
.then((images) => imageList.getLeases(images));
}
function clear(image) {
return refreshImages([image]).then(images => {
const currentLeases = getLeases(images);
return currentLeases.then(() => {
return image
.perform('delete-leases').then(() => {
pollLeasesAndUpdateUI(images, imageList.getLeases(images));
});
});
});
}
function replace(image, leases) {
const images = [image];
const currentLeasesPromise = getLeases(images);
return currentLeasesPromise.then((currentLeases) => {
const updatedLeases = leases.map((lease) => {
var newLease = angular.copy(lease);
newLease.mediaId = image.data.id;
return newLease;
});
// Don't update the leases if they're "the same"
if (JSON.stringify(currentLeases.leases) === JSON.stringify(updatedLeases)) {
return image;
}
return image
.perform('replace-leases', { body: updatedLeases })
.then(() => pollLeasesAndUpdateUI(images, updatedLeases));
});
}
function add(image, lease) {
const newLease = angular.copy(lease);
newLease.mediaId = image.data.id;
if (angular.isDefined(newLease.notes) && newLease.notes.trim().length === 0) {
newLease.notes = null;
}
return image.perform('add-lease', { body: newLease });
}
function addLeases(image, leases) {
const updatedLeases = leases.map((lease) => {
let newLease = angular.copy(lease);
newLease.mediaId = image.data.id;
return newLease;
});
return image
.perform('add-leases', { body: updatedLeases })
.then(() => pollLeasesAndUpdateUI([image]));
}
function batchAdd(lease, images) {
// search page has fancy image list
if (angular.isDefined(images.toArray)) {
images = images.toArray();
};
// We check whether the leases in the image have a later lastModified date,
// If the leases have updated in the background, or we haven't yet integrated
// the users changes into the model (somewhat eager).
// We should just update the images we do have so that untilLeasesChange doesn't
// immediately return without the user's expected change.
return refreshImages(images).then(updatedImages =>
trackAll($q, $rootScope, "leases", updatedImages, [
image => add(image, lease),
image => apiPoll(() => untilLeasesChange([image])).then(([{ image }]) => image) //Extract the image from untilLeasesChange
], ['images-updated', 'leases-updated'])
);
}
function canUserEdit(image){
return editsService.canUserEdit(image);
}
/**
* Delete a lease by uuid from a collection of images.
* This method does not support batch deletion, because a
* uuid will only ever match one lease.
*/
function deleteLease(lease, images) {
// search page has fancy image list
if (angular.isDefined(images.toArray)) {
images = images.toArray();
}
return refreshImages(images).then(images =>
getLeasesRoot().follow('leases', {id: lease.id}).delete()
.then(() => pollLeasesAndUpdateUI(images))
);
}
function getByMediaId(image) {
return getLeasesRoot().follow('by-media-id', {id: image.data.id}).get();
}
function pollLeasesAndUpdateUI(images, updatedLeases) {
apiPoll(() => {
return untilLeasesChange(images, updatedLeases);
}).then(results => {
return results.map(({ image, leases }) => {
emitLeaseUpdate(leases);
emitImageUpdate(image);
});
});
}
const imageUpdates$ = new Subject();
imageUpdates$.bufferWithTime(1000).subscribe((images) => {
if (images.length > 0) {
$rootScope.$emit('images-updated', images);
}
});
function emitImageUpdate(lease) {
imageUpdates$.onNext(lease);
}
const leaseUpdates$ = new Subject();
leaseUpdates$.bufferWithTime(1000).subscribe((leases) => {
if (leases.length > 0) {
$rootScope.$emit('leases-updated');
}
});
function emitLeaseUpdate(lease) {
leaseUpdates$.onNext(lease);
}
// If the leases have changed without being updated in the model
// then the user will see this immediately returned without their update
// And as this might return the edit on the first call
// You must call refreshImages before you make any changes this watches for.
function untilLeasesChange(images, updatedLeases) {
const imagesArray = images.toArray ? images.toArray() : images;
return $q.all(imagesArray.map(image => {
return image.get().then(apiImage => {
const apiImageAndApiLeases = getApiImageAndApiLeasesIfUpdated(image, apiImage, updatedLeases);
if (apiImageAndApiLeases) {
image = apiImage;
return apiImageAndApiLeases;
} else {
// returning $q.reject() will make apiPoll function to poll again
// until api call will return image with updated leases
return $q.reject();
}
});
}));
}
// See comment on untilLeasesChange
function refreshImages(images) {
return $q.all(images.map(_ => _.get()));
}
function flattenLeases(leaseByMedias) {
return {
leases: leaseByMedias.map(l => l.leases).reduce((a, b) => a.concat(b)),
lastModified: leaseByMedias.map(l => l.lastModified).sort()[0]
};
}
function isLeaseSyndication(lease) {
return lease.access.endsWith('-syndication');
}
return {
addLeases,
batchAdd,
getLeases,
canUserEdit,
deleteLease,
getByMediaId,
replace,
clear,
flattenLeases,
isLeaseSyndication
};
}]);
export default leaseService;