Convert Gallery to work with PouchType

This commit is contained in:
Timothy Farrell 2018-07-15 23:05:09 -05:00
parent 13468e3c1b
commit 549c336c47
12 changed files with 81 additions and 162 deletions

View File

@ -23,7 +23,7 @@
"pouchdb-core": "~7.0.0",
"pouchdb-find": "~7.0.0",
"pouchdb-replication": "~7.0.0",
"pouchorm": "~1.0.0",
"pouchtype": "~1.0.0",
"request": "~2.87.0",
"router": "2.1.0",
"semantic-ui-reset": "~2.2.12",

View File

@ -1,6 +1,7 @@
import pica from 'pica/dist/pica';
import { FileType } from '../data/file.js';
import { ImageType } from '../data/image.js';
const THUMBNAIL_MAX_DIMENSION = 320;
@ -60,7 +61,7 @@ export async function generateThumbnailForImage(doc) {
const mimetype = attachment.type;
const resizedBlob = await resizeImage(attachment, mimetype, width, height);
const thumbfile = await FileType.upload(resizedBlob);
await doc.update({
await ImageType.update(doc, {
sizes: {
thumbnail: FileType.getURL(thumbfile)
}
@ -68,7 +69,7 @@ export async function generateThumbnailForImage(doc) {
} else {
console.log('using original as thumbnail');
// Thumbnail would be bigger so let's just use the original.
await doc.update({
await ImageType.update(doc, {
sizes: {
thumbnail: doc.sizes.full
}

View File

@ -1,85 +0,0 @@
import { TypeSpec } from 'pouchorm';
import { PouchDB, db } from '../services/db.js';
import { ImageType } from '../data/image.js';
import { extractID } from '../utils/conversion.js';
class AlbumSpec extends TypeSpec {
static getUniqueID(doc) {
return doc.title
.trim()
.replace(/[ \-~!@#$%^&]/g, '_')
.toLowerCase();
}
async findImages(live = false) {
return await ImageType.find(
Object.assign({ [`$$links.${this._id}`]: { $exists: true } }, ImageType.selector),
live
);
}
async addImage(image) {
if (!image.$$links[this._id]) {
await image.update({
$$links: {
[this._id]: {
sequence: this.count
}
}
});
this.count += 1;
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 });
}
//
// static validate(doc) {
// // TODO actually validate perhaps against a JSON schema
//
// const schema = {
// title: t.REQUIRED_STRING,
// createdDate: t.REQUIRED_DATE,
// count: t.REQUIRED_INTEGER
// };
// }
}
export const AlbumType = PouchDB.registerType('Album', AlbumSpec, db);
ImageType.subscribe((id, deleted, doc) => {
if (!deleted) {
return;
}
Object.keys(doc.$$links)
.filter(k => k.startsWith(AlbumType.prefix))
.forEach(async albumId => {
const album = await AlbumType.find(albumId);
album.count -= 1;
album.save();
});
});

View File

@ -1,43 +1,43 @@
import { TypeSpec } from 'pouchorm';
import { TypeHandler } from 'pouchtype';
import { sha1 } from '../utils/crypto.js';
import { blobToArrayBuffer } from '../utils/conversion.js';
import { PouchDB, db } from '../services/db.js';
class FileSpec extends TypeSpec {
static getUniqueID(doc) {
class FileHandler extends TypeHandler {
getUniqueID(doc) {
return doc.digest.substr(0, 16);
}
static getURL(doc, attachmentName = 'data') {
getURL(doc, attachmentName = 'data') {
const end = attachmentName ? '/' + attachmentName : '';
return `/${doc._prefix}/${doc._id}` + end;
return `/${doc.type}/${doc._id}` + end;
}
static async getDocFromURL(path) {
async getDocFromURL(path) {
if (path.endsWith('/')) {
path = path.substr(0, path.length - 1);
}
const [_, db, id, attname] = path.split('/');
return await FileType.find(id);
return await this.get(id);
}
static async getFromURL(path) {
async getFromURL(path) {
if (path.endsWith('/')) {
path = path.substr(0, path.length - 1);
}
const [_, db, id, attname] = path.split('/');
const doc = await FileType.find(id);
const doc = await this.get(id);
if (attname) {
return await doc.getAttachment(attname);
return await this.getAttachment(doc, attname);
}
return doc;
}
static async upload(blob) {
async upload(blob) {
const digest = await sha1(await blobToArrayBuffer(blob));
const lastModified = blob.lastModified ? new Date(blob.lastModified) : new Date();
return await FileType.getOrCreate({
return await this.getOrCreate({
name: blob.name,
mimetype: blob.type,
size: blob.size,
@ -73,4 +73,4 @@ class FileSpec extends TypeSpec {
// }
}
export const FileType = PouchDB.registerType('File', FileSpec, db);
export const FileType = new FileHandler(db, 'file');

View File

@ -1,15 +1,15 @@
import { TypeSpec } from 'pouchorm';
import { TypeHandler } from 'pouchtype';
import { PouchDB, db } from '../services/db.js';
import { db } from '../services/db.js';
import { blobToArrayBuffer, deepAssign } from '../utils/conversion.js';
import { backgroundTask } from '../utils/event.js';
import { FileType } from './file.js';
import { error } from '../utils/console.js';
class ImageSpec extends TypeSpec {
static async upload(blob) {
class ImageHandler extends TypeHandler {
async upload(blob) {
const f = await FileType.upload(blob, false);
const doc = await ImageType.getOrCreate({
const doc = await this.getOrCreate({
digest: f.digest,
originalDate: f.lastModified,
importing: true,
@ -23,18 +23,19 @@ class ImageSpec extends TypeSpec {
return doc;
}
static getUniqueID(doc) {
getUniqueID(doc) {
return doc.digest.substr(0, 16);
}
async delete(cascade = true) {
async remove(docOrId, cascade = true) {
if (cascade) {
new Set(Object.keys(this.sizes)).forEach(async key => {
const f = await FileType.getDocFromURL(this.sizes[key]);
f.delete();
const doc = typeof docOrId === 'string' ? await this.get(docOrId) : docOrId;
Object.values(doc.sizes).forEach(async link => {
await FileType.remove(await FileType.getDocFromURL(link));
});
}
return await this.update({ _deleted: true });
return await super.remove(docOrId);
}
//
// static validate(doc) {
@ -79,10 +80,12 @@ const processImportables = backgroundTask(async function _processImportables(ima
const imageData = await FileType.getFromURL(sizes.full);
const img = new Image();
const imageProps = await new Promise(resolve => {
await new Promise(resolve => {
img.onload = () => {
resolve({ width: img.width, height: img.height });
image.width = img.width;
image.height = img.height;
URL.revokeObjectURL(img.src);
resolve();
};
img.src = URL.createObjectURL(imageData);
});
@ -97,7 +100,9 @@ const processImportables = backgroundTask(async function _processImportables(ima
tags.DateTimeOriginal ? new Date(tags.DateTimeOriginal * 1000).toISOString() : image.originalDate
).toISOString();
deepAssign(imageProps, {
ImageType.update(
image,
{
originalDate,
orientation: tags.Orientation,
digest,
@ -112,18 +117,19 @@ const processImportables = backgroundTask(async function _processImportables(ima
altitude: tags.GPSAltitude,
heading: tags.GPSImgDirection
}
});
},
false
);
}
delete image.importing;
image.update(imageProps);
ImageType.update(image, { importing: undefined });
const module = await import('../context/generateThumbnails');
module.generateThumbnailForImage(image);
}, false);
export const ImageType = PouchDB.registerType('Image', ImageSpec, db);
export const ImageType = new ImageHandler(db, 'image');
ImageType.index('originalDate', ['originalDate', 'id']);
ImageType.index('originalDate', ['originalDate']);
ImageType.find({ importing: true }).then(results => results.forEach(processImportables));
ImageType.filter({ importing: true }).then(results => results.forEach(processImportables));

View File

@ -22,7 +22,7 @@ import { injectStyle, styled } from '../utils/style.js';
import { CLICKABLE } from './styles.js';
export function uploadImages(evt, files) {
Array.from(files || evt.currentTarget.files).forEach(ImageType.upload);
Array.from(files || evt.currentTarget.files).forEach(ImageType.upload.bind(ImageType));
if (evt.currentTarget) {
evt.currentTarget.value = null;
@ -120,7 +120,7 @@ export function AllImagesView(vm, params) {
function deleteSelectedImages() {
if (confirm(`Delete ${selectedIds.size} image(s)?`)) {
selectedIds.forEach(ImageType.delete);
selectedIds.forEach(ImageType.remove.bind(ImageType));
selectedIds.clear();
}
}
@ -158,12 +158,10 @@ export function AllImagesView(vm, params) {
containerScrollTop(evt.target.scrollTop);
}
ImageType.find(
{
ImageType.watch({
['sizes.thumbnail']: { $exists: true }
},
{ live: true }
).then(la => {
}).then(la => {
const injectImages = imgArr => images.splice(0, images.length, ...imgArr);
subscribeToRender(vm, [
selectedIds,
images,
@ -171,8 +169,9 @@ export function AllImagesView(vm, params) {
appBarStyle,
hover,
hoverSelectButton,
() => la.subscribe(res => images.splice(0, images.length, ...res))
() => la.subscribe(injectImages)
]);
injectImages(la());
});
function renderSection({ title, sectionId, images: _images }) {

View File

@ -67,7 +67,7 @@ export function FocusView(vm, params) {
async function clickTrash() {
if (confirm('Delete this image?')) {
await ImageType.delete(id());
await ImageType.remove(id());
navBack();
}
}
@ -105,9 +105,9 @@ export function FocusView(vm, params) {
if (!_id) {
return;
}
doc(await ImageType.find(_id));
doc(await ImageType.get(_id));
const n = await ImageType.find(
const n = await ImageType.filter(
{
originalDate: { $gte: doc().originalDate }
},
@ -119,7 +119,7 @@ export function FocusView(vm, params) {
}
);
const p = await ImageType.find(
const p = await ImageType.filter(
{
originalDate: { $lte: doc().originalDate }
},

View File

@ -8,7 +8,6 @@ import {
defineElement as el
} from '../utils/domvm.js';
import { ImageType } from '../data/image.js';
import { AlbumType } from '../data/album.js';
import { AllImagesView, uploadImages } from './allImages.js';
import { FocusView } from './focus.js';
import { Dropzone } from './components/dropzone.js';

View File

@ -92,7 +92,7 @@ export function SectionView(vm, params, key) {
onmouseenter: [patchRefStyle, sectionSelectButtonRef, 'opacity: 0.7;'],
onmouseleave: [patchRefStyle, sectionSelectButtonRef, 'opacity: 0;'],
_data: {
$$type: 'section',
type: 'section',
sectionImageIds: photos.map(extractID)
}
},

View File

@ -4,22 +4,21 @@ import http from 'pouchdb-adapter-http';
import replication from 'pouchdb-replication';
import find from 'pouchdb-find';
import { PouchORM } from 'pouchorm';
import { PouchType } from 'pouchtype';
import { B2Adapter } from './b2.js';
export const PouchDB = core
.plugin(idb)
.plugin(http)
.plugin(replication)
.plugin(find)
// .plugin(
// B2Adapter(
// B2_ACCOUNT,
// B2_API_KEY,
// B2_BUCKET_ID
// )
// )
.plugin(PouchORM);
.plugin(find);
// .plugin(
// B2Adapter(
// B2_ACCOUNT,
// B2_API_KEY,
// B2_BUCKET_ID
// )
// );
export const db = new PouchDB('gallery');

View File

@ -42,7 +42,7 @@ export function PouchDBAttachmentProxy({ save, getFn, remove }) {
// All documents must have a .name field.
const deletedFiles = [];
const attachments = [];
docs.filter(d => d.$$type === 'file').forEach(f => {
docs.filter(d => d.type === 'file').forEach(f => {
if (f._deleted) {
deletedFiles.push(pouchGetAttachment.call(this, f._id, 'data'));
return;

View File

@ -54,7 +54,7 @@ export function changeElementStateMap(refStateMap, evt, node, vm) {
export function nodeParentWithType(node, type) {
let parentNode = node;
while (parentNode && (!parentNode.data || parentNode.data.$$type !== type)) {
while (parentNode && (!parentNode.data || parentNode.data.type !== type)) {
parentNode = parentNode.parent;
}
if (!parentNode) {