diff --git a/packages/gallery/package.json b/packages/gallery/package.json index ca0b68e..2e9b628 100644 --- a/packages/gallery/package.json +++ b/packages/gallery/package.json @@ -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", diff --git a/packages/gallery/src/context/generateThumbnails.js b/packages/gallery/src/context/generateThumbnails.js index 000aaa6..4d85138 100644 --- a/packages/gallery/src/context/generateThumbnails.js +++ b/packages/gallery/src/context/generateThumbnails.js @@ -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 } diff --git a/packages/gallery/src/data/album.js b/packages/gallery/src/data/album.js deleted file mode 100644 index f218463..0000000 --- a/packages/gallery/src/data/album.js +++ /dev/null @@ -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(); - }); -}); diff --git a/packages/gallery/src/data/file.js b/packages/gallery/src/data/file.js index fce3a93..10985d2 100644 --- a/packages/gallery/src/data/file.js +++ b/packages/gallery/src/data/file.js @@ -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'); diff --git a/packages/gallery/src/data/image.js b/packages/gallery/src/data/image.js index 610dd70..3316ad1 100644 --- a/packages/gallery/src/data/image.js +++ b/packages/gallery/src/data/image.js @@ -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,33 +100,36 @@ const processImportables = backgroundTask(async function _processImportables(ima tags.DateTimeOriginal ? new Date(tags.DateTimeOriginal * 1000).toISOString() : image.originalDate ).toISOString(); - deepAssign(imageProps, { - originalDate, - orientation: tags.Orientation, - digest, - make: tags.Make, - model: tags.Model, - flash: !!tags.Flash, - iso: tags.ISO, - sizes, - gps: { - latitude: tags.GPSLatitude, - longitude: tags.GPSLongitude, - altitude: tags.GPSAltitude, - heading: tags.GPSImgDirection - } - }); + ImageType.update( + image, + { + originalDate, + orientation: tags.Orientation, + digest, + make: tags.Make, + model: tags.Model, + flash: !!tags.Flash, + iso: tags.ISO, + sizes, + gps: { + latitude: tags.GPSLatitude, + longitude: tags.GPSLongitude, + 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)); diff --git a/packages/gallery/src/interface/allImages.js b/packages/gallery/src/interface/allImages.js index 971d4d9..c9060ab 100644 --- a/packages/gallery/src/interface/allImages.js +++ b/packages/gallery/src/interface/allImages.js @@ -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( - { - ['sizes.thumbnail']: { $exists: true } - }, - { live: true } - ).then(la => { + ImageType.watch({ + ['sizes.thumbnail']: { $exists: true } + }).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 }) { diff --git a/packages/gallery/src/interface/focus.js b/packages/gallery/src/interface/focus.js index 92607b7..4532454 100644 --- a/packages/gallery/src/interface/focus.js +++ b/packages/gallery/src/interface/focus.js @@ -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 } }, diff --git a/packages/gallery/src/interface/gallery.js b/packages/gallery/src/interface/gallery.js index 64eef9c..c55368f 100644 --- a/packages/gallery/src/interface/gallery.js +++ b/packages/gallery/src/interface/gallery.js @@ -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'; diff --git a/packages/gallery/src/interface/sectionView.js b/packages/gallery/src/interface/sectionView.js index 56d49a7..91b3ff6 100644 --- a/packages/gallery/src/interface/sectionView.js +++ b/packages/gallery/src/interface/sectionView.js @@ -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) } }, diff --git a/packages/gallery/src/services/db.js b/packages/gallery/src/services/db.js index b73e375..930a2b3 100644 --- a/packages/gallery/src/services/db.js +++ b/packages/gallery/src/services/db.js @@ -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'); diff --git a/packages/gallery/src/utils/attachmentProxy.js b/packages/gallery/src/utils/attachmentProxy.js index 6f742fd..7ad64b3 100644 --- a/packages/gallery/src/utils/attachmentProxy.js +++ b/packages/gallery/src/utils/attachmentProxy.js @@ -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; diff --git a/packages/gallery/src/utils/domvm.js b/packages/gallery/src/utils/domvm.js index a3709c6..17f59d7 100644 --- a/packages/gallery/src/utils/domvm.js +++ b/packages/gallery/src/utils/domvm.js @@ -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) {