Convert Gallery to work with PouchType
This commit is contained in:
parent
13468e3c1b
commit
549c336c47
@ -23,7 +23,7 @@
|
|||||||
"pouchdb-core": "~7.0.0",
|
"pouchdb-core": "~7.0.0",
|
||||||
"pouchdb-find": "~7.0.0",
|
"pouchdb-find": "~7.0.0",
|
||||||
"pouchdb-replication": "~7.0.0",
|
"pouchdb-replication": "~7.0.0",
|
||||||
"pouchorm": "~1.0.0",
|
"pouchtype": "~1.0.0",
|
||||||
"request": "~2.87.0",
|
"request": "~2.87.0",
|
||||||
"router": "2.1.0",
|
"router": "2.1.0",
|
||||||
"semantic-ui-reset": "~2.2.12",
|
"semantic-ui-reset": "~2.2.12",
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import pica from 'pica/dist/pica';
|
import pica from 'pica/dist/pica';
|
||||||
|
|
||||||
import { FileType } from '../data/file.js';
|
import { FileType } from '../data/file.js';
|
||||||
|
import { ImageType } from '../data/image.js';
|
||||||
|
|
||||||
const THUMBNAIL_MAX_DIMENSION = 320;
|
const THUMBNAIL_MAX_DIMENSION = 320;
|
||||||
|
|
||||||
@ -60,7 +61,7 @@ export async function generateThumbnailForImage(doc) {
|
|||||||
const mimetype = attachment.type;
|
const mimetype = attachment.type;
|
||||||
const resizedBlob = await resizeImage(attachment, mimetype, width, height);
|
const resizedBlob = await resizeImage(attachment, mimetype, width, height);
|
||||||
const thumbfile = await FileType.upload(resizedBlob);
|
const thumbfile = await FileType.upload(resizedBlob);
|
||||||
await doc.update({
|
await ImageType.update(doc, {
|
||||||
sizes: {
|
sizes: {
|
||||||
thumbnail: FileType.getURL(thumbfile)
|
thumbnail: FileType.getURL(thumbfile)
|
||||||
}
|
}
|
||||||
@ -68,7 +69,7 @@ export async function generateThumbnailForImage(doc) {
|
|||||||
} else {
|
} else {
|
||||||
console.log('using original as thumbnail');
|
console.log('using original as thumbnail');
|
||||||
// Thumbnail would be bigger so let's just use the original.
|
// Thumbnail would be bigger so let's just use the original.
|
||||||
await doc.update({
|
await ImageType.update(doc, {
|
||||||
sizes: {
|
sizes: {
|
||||||
thumbnail: doc.sizes.full
|
thumbnail: doc.sizes.full
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,43 +1,43 @@
|
|||||||
import { TypeSpec } from 'pouchorm';
|
import { TypeHandler } from 'pouchtype';
|
||||||
|
|
||||||
import { sha1 } from '../utils/crypto.js';
|
import { sha1 } from '../utils/crypto.js';
|
||||||
import { blobToArrayBuffer } from '../utils/conversion.js';
|
import { blobToArrayBuffer } from '../utils/conversion.js';
|
||||||
import { PouchDB, db } from '../services/db.js';
|
import { PouchDB, db } from '../services/db.js';
|
||||||
|
|
||||||
class FileSpec extends TypeSpec {
|
class FileHandler extends TypeHandler {
|
||||||
static getUniqueID(doc) {
|
getUniqueID(doc) {
|
||||||
return doc.digest.substr(0, 16);
|
return doc.digest.substr(0, 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
static getURL(doc, attachmentName = 'data') {
|
getURL(doc, attachmentName = 'data') {
|
||||||
const end = attachmentName ? '/' + attachmentName : '';
|
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('/')) {
|
if (path.endsWith('/')) {
|
||||||
path = path.substr(0, path.length - 1);
|
path = path.substr(0, path.length - 1);
|
||||||
}
|
}
|
||||||
const [_, db, id, attname] = path.split('/');
|
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('/')) {
|
if (path.endsWith('/')) {
|
||||||
path = path.substr(0, path.length - 1);
|
path = path.substr(0, path.length - 1);
|
||||||
}
|
}
|
||||||
const [_, db, id, attname] = path.split('/');
|
const [_, db, id, attname] = path.split('/');
|
||||||
const doc = await FileType.find(id);
|
const doc = await this.get(id);
|
||||||
if (attname) {
|
if (attname) {
|
||||||
return await doc.getAttachment(attname);
|
return await this.getAttachment(doc, attname);
|
||||||
}
|
}
|
||||||
return doc;
|
return doc;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async upload(blob) {
|
async upload(blob) {
|
||||||
const digest = await sha1(await blobToArrayBuffer(blob));
|
const digest = await sha1(await blobToArrayBuffer(blob));
|
||||||
const lastModified = blob.lastModified ? new Date(blob.lastModified) : new Date();
|
const lastModified = blob.lastModified ? new Date(blob.lastModified) : new Date();
|
||||||
return await FileType.getOrCreate({
|
return await this.getOrCreate({
|
||||||
name: blob.name,
|
name: blob.name,
|
||||||
mimetype: blob.type,
|
mimetype: blob.type,
|
||||||
size: blob.size,
|
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');
|
||||||
|
|||||||
@ -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 { blobToArrayBuffer, deepAssign } from '../utils/conversion.js';
|
||||||
import { backgroundTask } from '../utils/event.js';
|
import { backgroundTask } from '../utils/event.js';
|
||||||
import { FileType } from './file.js';
|
import { FileType } from './file.js';
|
||||||
import { error } from '../utils/console.js';
|
import { error } from '../utils/console.js';
|
||||||
|
|
||||||
class ImageSpec extends TypeSpec {
|
class ImageHandler extends TypeHandler {
|
||||||
static async upload(blob) {
|
async upload(blob) {
|
||||||
const f = await FileType.upload(blob, false);
|
const f = await FileType.upload(blob, false);
|
||||||
const doc = await ImageType.getOrCreate({
|
const doc = await this.getOrCreate({
|
||||||
digest: f.digest,
|
digest: f.digest,
|
||||||
originalDate: f.lastModified,
|
originalDate: f.lastModified,
|
||||||
importing: true,
|
importing: true,
|
||||||
@ -23,18 +23,19 @@ class ImageSpec extends TypeSpec {
|
|||||||
return doc;
|
return doc;
|
||||||
}
|
}
|
||||||
|
|
||||||
static getUniqueID(doc) {
|
getUniqueID(doc) {
|
||||||
return doc.digest.substr(0, 16);
|
return doc.digest.substr(0, 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(cascade = true) {
|
async remove(docOrId, cascade = true) {
|
||||||
if (cascade) {
|
if (cascade) {
|
||||||
new Set(Object.keys(this.sizes)).forEach(async key => {
|
const doc = typeof docOrId === 'string' ? await this.get(docOrId) : docOrId;
|
||||||
const f = await FileType.getDocFromURL(this.sizes[key]);
|
|
||||||
f.delete();
|
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) {
|
// static validate(doc) {
|
||||||
@ -79,10 +80,12 @@ const processImportables = backgroundTask(async function _processImportables(ima
|
|||||||
const imageData = await FileType.getFromURL(sizes.full);
|
const imageData = await FileType.getFromURL(sizes.full);
|
||||||
|
|
||||||
const img = new Image();
|
const img = new Image();
|
||||||
const imageProps = await new Promise(resolve => {
|
await new Promise(resolve => {
|
||||||
img.onload = () => {
|
img.onload = () => {
|
||||||
resolve({ width: img.width, height: img.height });
|
image.width = img.width;
|
||||||
|
image.height = img.height;
|
||||||
URL.revokeObjectURL(img.src);
|
URL.revokeObjectURL(img.src);
|
||||||
|
resolve();
|
||||||
};
|
};
|
||||||
img.src = URL.createObjectURL(imageData);
|
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
|
tags.DateTimeOriginal ? new Date(tags.DateTimeOriginal * 1000).toISOString() : image.originalDate
|
||||||
).toISOString();
|
).toISOString();
|
||||||
|
|
||||||
deepAssign(imageProps, {
|
ImageType.update(
|
||||||
|
image,
|
||||||
|
{
|
||||||
originalDate,
|
originalDate,
|
||||||
orientation: tags.Orientation,
|
orientation: tags.Orientation,
|
||||||
digest,
|
digest,
|
||||||
@ -112,18 +117,19 @@ const processImportables = backgroundTask(async function _processImportables(ima
|
|||||||
altitude: tags.GPSAltitude,
|
altitude: tags.GPSAltitude,
|
||||||
heading: tags.GPSImgDirection
|
heading: tags.GPSImgDirection
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
delete image.importing;
|
ImageType.update(image, { importing: undefined });
|
||||||
image.update(imageProps);
|
|
||||||
|
|
||||||
const module = await import('../context/generateThumbnails');
|
const module = await import('../context/generateThumbnails');
|
||||||
module.generateThumbnailForImage(image);
|
module.generateThumbnailForImage(image);
|
||||||
}, false);
|
}, 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));
|
||||||
|
|||||||
@ -22,7 +22,7 @@ import { injectStyle, styled } from '../utils/style.js';
|
|||||||
import { CLICKABLE } from './styles.js';
|
import { CLICKABLE } from './styles.js';
|
||||||
|
|
||||||
export function uploadImages(evt, files) {
|
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) {
|
if (evt.currentTarget) {
|
||||||
evt.currentTarget.value = null;
|
evt.currentTarget.value = null;
|
||||||
@ -120,7 +120,7 @@ export function AllImagesView(vm, params) {
|
|||||||
|
|
||||||
function deleteSelectedImages() {
|
function deleteSelectedImages() {
|
||||||
if (confirm(`Delete ${selectedIds.size} image(s)?`)) {
|
if (confirm(`Delete ${selectedIds.size} image(s)?`)) {
|
||||||
selectedIds.forEach(ImageType.delete);
|
selectedIds.forEach(ImageType.remove.bind(ImageType));
|
||||||
selectedIds.clear();
|
selectedIds.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -158,12 +158,10 @@ export function AllImagesView(vm, params) {
|
|||||||
containerScrollTop(evt.target.scrollTop);
|
containerScrollTop(evt.target.scrollTop);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageType.find(
|
ImageType.watch({
|
||||||
{
|
|
||||||
['sizes.thumbnail']: { $exists: true }
|
['sizes.thumbnail']: { $exists: true }
|
||||||
},
|
}).then(la => {
|
||||||
{ live: true }
|
const injectImages = imgArr => images.splice(0, images.length, ...imgArr);
|
||||||
).then(la => {
|
|
||||||
subscribeToRender(vm, [
|
subscribeToRender(vm, [
|
||||||
selectedIds,
|
selectedIds,
|
||||||
images,
|
images,
|
||||||
@ -171,8 +169,9 @@ export function AllImagesView(vm, params) {
|
|||||||
appBarStyle,
|
appBarStyle,
|
||||||
hover,
|
hover,
|
||||||
hoverSelectButton,
|
hoverSelectButton,
|
||||||
() => la.subscribe(res => images.splice(0, images.length, ...res))
|
() => la.subscribe(injectImages)
|
||||||
]);
|
]);
|
||||||
|
injectImages(la());
|
||||||
});
|
});
|
||||||
|
|
||||||
function renderSection({ title, sectionId, images: _images }) {
|
function renderSection({ title, sectionId, images: _images }) {
|
||||||
|
|||||||
@ -67,7 +67,7 @@ export function FocusView(vm, params) {
|
|||||||
|
|
||||||
async function clickTrash() {
|
async function clickTrash() {
|
||||||
if (confirm('Delete this image?')) {
|
if (confirm('Delete this image?')) {
|
||||||
await ImageType.delete(id());
|
await ImageType.remove(id());
|
||||||
navBack();
|
navBack();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -105,9 +105,9 @@ export function FocusView(vm, params) {
|
|||||||
if (!_id) {
|
if (!_id) {
|
||||||
return;
|
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 }
|
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 }
|
originalDate: { $lte: doc().originalDate }
|
||||||
},
|
},
|
||||||
|
|||||||
@ -8,7 +8,6 @@ import {
|
|||||||
defineElement as el
|
defineElement as el
|
||||||
} from '../utils/domvm.js';
|
} from '../utils/domvm.js';
|
||||||
import { ImageType } from '../data/image.js';
|
import { ImageType } from '../data/image.js';
|
||||||
import { AlbumType } from '../data/album.js';
|
|
||||||
import { AllImagesView, uploadImages } from './allImages.js';
|
import { AllImagesView, uploadImages } from './allImages.js';
|
||||||
import { FocusView } from './focus.js';
|
import { FocusView } from './focus.js';
|
||||||
import { Dropzone } from './components/dropzone.js';
|
import { Dropzone } from './components/dropzone.js';
|
||||||
|
|||||||
@ -92,7 +92,7 @@ export function SectionView(vm, params, key) {
|
|||||||
onmouseenter: [patchRefStyle, sectionSelectButtonRef, 'opacity: 0.7;'],
|
onmouseenter: [patchRefStyle, sectionSelectButtonRef, 'opacity: 0.7;'],
|
||||||
onmouseleave: [patchRefStyle, sectionSelectButtonRef, 'opacity: 0;'],
|
onmouseleave: [patchRefStyle, sectionSelectButtonRef, 'opacity: 0;'],
|
||||||
_data: {
|
_data: {
|
||||||
$$type: 'section',
|
type: 'section',
|
||||||
sectionImageIds: photos.map(extractID)
|
sectionImageIds: photos.map(extractID)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -4,22 +4,21 @@ import http from 'pouchdb-adapter-http';
|
|||||||
import replication from 'pouchdb-replication';
|
import replication from 'pouchdb-replication';
|
||||||
import find from 'pouchdb-find';
|
import find from 'pouchdb-find';
|
||||||
|
|
||||||
import { PouchORM } from 'pouchorm';
|
import { PouchType } from 'pouchtype';
|
||||||
import { B2Adapter } from './b2.js';
|
import { B2Adapter } from './b2.js';
|
||||||
|
|
||||||
export const PouchDB = core
|
export const PouchDB = core
|
||||||
.plugin(idb)
|
.plugin(idb)
|
||||||
.plugin(http)
|
.plugin(http)
|
||||||
.plugin(replication)
|
.plugin(replication)
|
||||||
.plugin(find)
|
.plugin(find);
|
||||||
// .plugin(
|
// .plugin(
|
||||||
// B2Adapter(
|
// B2Adapter(
|
||||||
// B2_ACCOUNT,
|
// B2_ACCOUNT,
|
||||||
// B2_API_KEY,
|
// B2_API_KEY,
|
||||||
// B2_BUCKET_ID
|
// B2_BUCKET_ID
|
||||||
// )
|
// )
|
||||||
// )
|
// );
|
||||||
.plugin(PouchORM);
|
|
||||||
|
|
||||||
export const db = new PouchDB('gallery');
|
export const db = new PouchDB('gallery');
|
||||||
|
|
||||||
|
|||||||
@ -42,7 +42,7 @@ export function PouchDBAttachmentProxy({ save, getFn, remove }) {
|
|||||||
// All documents must have a .name field.
|
// All documents must have a .name field.
|
||||||
const deletedFiles = [];
|
const deletedFiles = [];
|
||||||
const attachments = [];
|
const attachments = [];
|
||||||
docs.filter(d => d.$$type === 'file').forEach(f => {
|
docs.filter(d => d.type === 'file').forEach(f => {
|
||||||
if (f._deleted) {
|
if (f._deleted) {
|
||||||
deletedFiles.push(pouchGetAttachment.call(this, f._id, 'data'));
|
deletedFiles.push(pouchGetAttachment.call(this, f._id, 'data'));
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -54,7 +54,7 @@ export function changeElementStateMap(refStateMap, evt, node, vm) {
|
|||||||
|
|
||||||
export function nodeParentWithType(node, type) {
|
export function nodeParentWithType(node, type) {
|
||||||
let parentNode = node;
|
let parentNode = node;
|
||||||
while (parentNode && (!parentNode.data || parentNode.data.$$type !== type)) {
|
while (parentNode && (!parentNode.data || parentNode.data.type !== type)) {
|
||||||
parentNode = parentNode.parent;
|
parentNode = parentNode.parent;
|
||||||
}
|
}
|
||||||
if (!parentNode) {
|
if (!parentNode) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user