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;