Associate images with albums via image.$links.

All types now have a $links property.
This commit is contained in:
Timothy Farrell 2017-11-21 23:58:22 -06:00
parent 43a20c4afe
commit b44886f0f5
7 changed files with 87 additions and 81 deletions

View File

@ -1,5 +1,6 @@
import { PouchDB, TypeSpec } from '../services/db.js'; import { PouchDB, TypeSpec } from '../services/db.js';
import { log } from '../services/console.js'; import { ImageType } from '../data/image.js';
import { extractID } from '../utils/conversion.js';
class AlbumSpec extends TypeSpec { class AlbumSpec extends TypeSpec {
static getUniqueID(doc) { static getUniqueID(doc) {
@ -9,23 +10,48 @@ class AlbumSpec extends TypeSpec {
.toLowerCase(); .toLowerCase();
} }
async addMember(member, position) { async findImages(live = false) {
const currentPosition = this.members.indexOf(member); return await ImageType.find(
const newPosition = position ? position : this.members.length; Object.assign({ [`$links.${this._id}`]: { $exists: true } }, ImageType.selector),
if (currentPosition !== -1) { live
this.members.splice(currentPosition, 1); );
}
this.members.splice(newPosition, 0, member);
await this.save();
} }
async removeMember(member) { async addImage(image) {
const currentPosition = this.members.indexOf(member); if (!image.$links[this._id]) {
await image.update({
if (currentPosition !== -1) { $links: {
this.members.splice(currentPosition, 1); [this._id]: {
sequence: this.count
}
}
});
this.count += 1;
await this.save(); await this.save();
} }
return image;
}
async removeImage(image) {
if (image.$links[this._id]) {
delete image.$links[this._id];
this.count -= 1;
await image.save();
await this.save();
}
return image;
}
async addImageBlob(blob) {
return await this.addImage(await ImageType.upload(blob));
}
async delete(cascade = true) {
if (cascade) {
const images = await this.findImages();
images.map(async i => await this.removeImage(i));
}
return await this.update({ _deleted: true });
} }
// //
@ -34,10 +60,8 @@ class AlbumSpec extends TypeSpec {
// //
// const schema = { // const schema = {
// title: t.REQUIRED_STRING, // title: t.REQUIRED_STRING,
// members: { // createdDate: t.REQUIRED_DATE,
// type: "array", // count: t.REQUIRED_INTEGER
// items: t.STRING
// }
// }; // };
// } // }
} }

View File

@ -91,8 +91,8 @@ const processImportables = backgroundTask(async function _processImportables(ima
tags.DateTimeOriginal ? new Date(tags.DateTimeOriginal * 1000).toISOString() : image.originalDate tags.DateTimeOriginal ? new Date(tags.DateTimeOriginal * 1000).toISOString() : image.originalDate
).toISOString(); ).toISOString();
await image.update( delete image.importing;
{ await image.update({
originalDate, originalDate,
width, width,
height, height,
@ -109,11 +109,7 @@ const processImportables = backgroundTask(async function _processImportables(ima
altitude: tags.GPSAltitude, altitude: tags.GPSAltitude,
heading: tags.GPSImgDirection heading: tags.GPSImgDirection
} }
}, });
false
);
delete image.importing;
await image.save();
const module = await import('../context/generateThumbnails'); const module = await import('../context/generateThumbnails');
module.generateThumbnailForImage(image); module.generateThumbnailForImage(image);

View File

@ -6,7 +6,7 @@ import { pouchDocArrayHash, pouchDocHash } from '../utils/conversion.js';
import { ThumbnailView } from './thumbnail.js'; import { ThumbnailView } from './thumbnail.js';
import { prop, computed, bundle } from 'frptools'; import { prop, computed, bundle } from 'frptools';
export function AlbumView(vm, params) { export function AlbumView(vm, doc) {
const model = prop({}, pouchDocHash); const model = prop({}, pouchDocHash);
const images = prop([], pouchDocArrayHash); const images = prop([], pouchDocArrayHash);
@ -16,53 +16,49 @@ export function AlbumView(vm, params) {
let laCleanup = null; let laCleanup = null;
id.subscribe(async () => { model.subscribe(async album => {
const la = await ImageType.find( if (!album.findImages) {
{ return;
_id: { $in: members() }
},
true
);
function refresh() {
images(la());
vm.redraw();
} }
const imagesLiveArray = await album.findImages(true);
if (laCleanup) { if (laCleanup) {
laCleanup(); laCleanup();
} }
laCleanup = la.subscribe(refresh); function refresh() {
la.ready.subscribe(refresh); images(imagesLiveArray());
vm.redraw();
}
laCleanup = imagesLiveArray.subscribe(refresh);
imagesLiveArray.ready.subscribe(refresh);
}); });
function removeImageFromAlbum(image) { function removeImageFromAlbum(image) {
model().removeMember(image._id); model().removeImage(image);
} }
function removeAlbum() { function removeAlbum(album) {
model().delete(); album.delete();
} }
function uploadImages(album, evt) { function uploadImages(album, evt) {
Promise.all(Array.from(evt.currentTarget.files).map(ImageType.upload)).then(images => { Array.from(evt.currentTarget.files).forEach(f => album.addImageBlob(f));
images.forEach(i => album.addMember(i._id));
});
} }
model(params.doc); model(doc);
return function(vm, params, key, opts) { return function(vm, album, key, opts) {
model(params.doc); model(album);
return el('.album', [ return el('.album', [
el('h2', [title(), el('button', { onclick: removeAlbum }, 'X')]), el('h2', [title(), el('button', { onclick: [removeAlbum, album] }, 'X')]),
el('input#fInput', { el('input#fInput', {
type: 'file', type: 'file',
multiple: true, multiple: true,
accept: 'image/jpeg', accept: 'image/jpeg',
onchange: [uploadImages, model()] onchange: [uploadImages, album]
}), }),
...images().map(i => { ...images().map(i => {
return defineView( return defineView(

View File

@ -89,23 +89,13 @@ export function GalleryView(vm, model) {
{ {
doc: i, doc: i,
showTags: true, showTags: true,
// addTag: imageTag.add,
remove: deleteImage remove: deleteImage
// removeTag: imageTag.remove
}, },
i._id + i._rev i._id + i._rev
); );
}) })
: data().map(a => { : data().map(a => {
return vw( return vw(AlbumView, a, a._id + a._rev);
AlbumView,
{
doc: a
// addTag: imageTag.add,
// remove: imageTag.remove
},
a._id + a._rev
);
})) }))
]) ])
]); ]);

View File

@ -19,7 +19,7 @@ export const PouchDB = core
export class TypeSpec { export class TypeSpec {
constructor(props) { constructor(props) {
this._populateId(props); this._populateId(props);
Object.assign(this, props, { type: this._prefix }); Object.assign(this, { $links: {} }, props, { type: this._prefix });
} }
static getSequence(doc) { static getSequence(doc) {

View File

@ -1,7 +1,4 @@
import { pick } from './conversion.js'; import { extractID, extractREV } from './conversion.js';
const extractID = pick('_id');
const extractREV = pick('_rev');
export function pouchDocComparator(a, b) { export function pouchDocComparator(a, b) {
return extractID(a) === extractID(b) && extractREV(a) === extractREV(b); return extractID(a) === extractID(b) && extractREV(a) === extractREV(b);

View File

@ -46,3 +46,6 @@ export function deepAssign(to, ...rest) {
} }
export const pick = id => doc => doc[id]; export const pick = id => doc => doc[id];
export const extractID = pick('_id');
export const extractREV = pick('_rev');