Add back albums
Removing an image from an album does not delete it. Deleteing an images does not remove its link from the album.
This commit is contained in:
parent
9b07868edd
commit
3206345999
@ -16,7 +16,7 @@
|
|||||||
"domvm": "~3.2.1",
|
"domvm": "~3.2.1",
|
||||||
"exif-parser": "~0.1.9",
|
"exif-parser": "~0.1.9",
|
||||||
"extract-text-webpack-plugin": "^3.0.2",
|
"extract-text-webpack-plugin": "^3.0.2",
|
||||||
"frptools": "2.0.0",
|
"frptools": "2.1.0",
|
||||||
"pica": "~2.0.8",
|
"pica": "~2.0.8",
|
||||||
"pouchdb-adapter-http": "~6.3.4",
|
"pouchdb-adapter-http": "~6.3.4",
|
||||||
"pouchdb-adapter-idb": "~6.3.4",
|
"pouchdb-adapter-idb": "~6.3.4",
|
||||||
|
|||||||
@ -1,20 +0,0 @@
|
|||||||
import * as image from '../data/image.js';
|
|
||||||
import * as index from '../data/indexType.js';
|
|
||||||
|
|
||||||
export async function add(title, imageId, visible = true) {
|
|
||||||
const trimmedTitle = title.trim();
|
|
||||||
await index.add(trimmedTitle, { title: trimmedTitle }, [imageId]);
|
|
||||||
return image.update(imageId, {
|
|
||||||
tags: { [trimmedTitle]: visible }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function remove(title, imageId) {
|
|
||||||
const id = index.hashString(title);
|
|
||||||
await image.update(imageId, { tags: { [title]: undefined } });
|
|
||||||
await index.removeMember(title, imageId);
|
|
||||||
}
|
|
||||||
|
|
||||||
image.removed.subscribe(image => {
|
|
||||||
Object.keys(image.tags).forEach(t => index.removeMember(t, image._id));
|
|
||||||
});
|
|
||||||
53
packages/gallery/src/data/album.js
Normal file
53
packages/gallery/src/data/album.js
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { PouchDB, TypeSpec } from '../services/db.js';
|
||||||
|
import { log } from '../services/console.js';
|
||||||
|
|
||||||
|
class AlbumSpec extends TypeSpec {
|
||||||
|
static getUniqueID(doc) {
|
||||||
|
return doc.title
|
||||||
|
.trim()
|
||||||
|
.replace(/[ \-~!@#$%^&]/g, '_')
|
||||||
|
.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
async addMember(member, position) {
|
||||||
|
const currentPosition = this.members.indexOf(member);
|
||||||
|
const newPosition = position ? position : this.members.length;
|
||||||
|
if (currentPosition !== -1) {
|
||||||
|
this.members.splice(currentPosition, 1);
|
||||||
|
}
|
||||||
|
this.members.splice(newPosition, 0, member);
|
||||||
|
await this.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
async removeMember(member) {
|
||||||
|
const currentPosition = this.members.indexOf(member);
|
||||||
|
|
||||||
|
if (currentPosition !== -1) {
|
||||||
|
this.members.splice(currentPosition, 1);
|
||||||
|
await this.save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// static validate(doc) {
|
||||||
|
// // TODO actually validate perhaps against a JSON schema
|
||||||
|
//
|
||||||
|
// const schema = {
|
||||||
|
// title: t.REQUIRED_STRING,
|
||||||
|
// members: {
|
||||||
|
// type: "array",
|
||||||
|
// items: t.STRING
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AlbumType = PouchDB.registerType('Album', AlbumSpec);
|
||||||
|
|
||||||
|
// ImageType.watch({_deleted: true}, true)
|
||||||
|
// .then(la => {
|
||||||
|
// la.subscribe() );
|
||||||
|
//
|
||||||
|
// image.removed.subscribe(image => {
|
||||||
|
// Object.keys(image.tags).forEach(t => index.removeMember(t, image._id));
|
||||||
|
// })
|
||||||
@ -1,5 +1,4 @@
|
|||||||
import { PouchDB, TypeSpec } from '../services/db.js';
|
import { PouchDB, TypeSpec } from '../services/db.js';
|
||||||
import { log } from '../services/console.js';
|
|
||||||
import { sha256 } from '../utils/crypto.js';
|
import { sha256 } from '../utils/crypto.js';
|
||||||
import { blobToArrayBuffer } from '../utils/conversion.js';
|
import { blobToArrayBuffer } from '../utils/conversion.js';
|
||||||
|
|
||||||
|
|||||||
@ -1,82 +0,0 @@
|
|||||||
import { log, error } from '../services/console.js';
|
|
||||||
import { getDatabase, getOrCreate } from '../services/db.js';
|
|
||||||
import { Event } from '../utils/event.js';
|
|
||||||
|
|
||||||
const db = getDatabase();
|
|
||||||
const PREFIX = 'index';
|
|
||||||
export const SELECTOR = {
|
|
||||||
_id: {
|
|
||||||
$gt: `${PREFIX}_`,
|
|
||||||
$lt: `${PREFIX}_\ufff0`
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Events
|
|
||||||
export const added = new Event('Index.added');
|
|
||||||
export const removed = new Event('Index.removed');
|
|
||||||
|
|
||||||
// Methods
|
|
||||||
export const hashString = name =>
|
|
||||||
name
|
|
||||||
.trim()
|
|
||||||
.replace(/[ \-~!@#$%^&]/g, '_')
|
|
||||||
.toLowerCase();
|
|
||||||
const getId = id => (id.startsWith(PREFIX) ? id : `${PREFIX}_${hashString(id)}`);
|
|
||||||
|
|
||||||
export async function find(keys, options = {}) {
|
|
||||||
let opts = { include_docs: true };
|
|
||||||
if (Array.isArray(keys)) {
|
|
||||||
Object.assign(opts, options);
|
|
||||||
opts.keys = keys.map(getId);
|
|
||||||
} else {
|
|
||||||
Object.assign(opts, keys);
|
|
||||||
opts.startkey = `${PREFIX}_`;
|
|
||||||
opts.endkey = `${PREFIX}_\ufff0`;
|
|
||||||
}
|
|
||||||
return await db.allDocs(opts);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function add(id, props = {}, members = []) {
|
|
||||||
const _id = getId(id);
|
|
||||||
const [results, created] = await getOrCreate({
|
|
||||||
_id,
|
|
||||||
props,
|
|
||||||
members: []
|
|
||||||
});
|
|
||||||
|
|
||||||
if (members.length) {
|
|
||||||
members.forEach(async m => await addMember(_id, m));
|
|
||||||
}
|
|
||||||
|
|
||||||
return created || results.ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function addMember(id, member) {
|
|
||||||
const results = await find([id]);
|
|
||||||
const doc = results.rows[0].doc;
|
|
||||||
|
|
||||||
if (doc.members.indexOf(member) === -1) {
|
|
||||||
doc.members.push(member);
|
|
||||||
await db.put(doc);
|
|
||||||
added.fire(doc._id, member);
|
|
||||||
}
|
|
||||||
|
|
||||||
return doc;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function removeMember(id, member) {
|
|
||||||
const results = await find([id]);
|
|
||||||
const doc = results.rows[0].doc;
|
|
||||||
const idx = doc.members.indexOf(member);
|
|
||||||
|
|
||||||
if (idx !== -1) {
|
|
||||||
if (doc.members.length > 1) {
|
|
||||||
doc.members.splice(idx, 1);
|
|
||||||
await db.put(doc);
|
|
||||||
removed.fire(doc._id, member);
|
|
||||||
} else {
|
|
||||||
await db.remove(doc);
|
|
||||||
removed.fire(doc._id, member);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,41 +1,70 @@
|
|||||||
import { defineView, defineElement as el } from 'domvm';
|
import { defineView, defineElement as el } from 'domvm';
|
||||||
import * as image from '../data/image.js';
|
|
||||||
|
import { ImageType } from '../data/image.js';
|
||||||
|
import { FileType } from '../data/file.js';
|
||||||
|
import { pouchDocArrayHash, pouchDocHash } from '../utils/conversion.js';
|
||||||
import { ThumbnailView } from './thumbnail.js';
|
import { ThumbnailView } from './thumbnail.js';
|
||||||
import { LiveArray } from '../utils/livearray.js';
|
import { prop, computed, bundle } from 'frptools';
|
||||||
|
|
||||||
export function AlbumView(vm, model) {
|
export function AlbumView(vm, params) {
|
||||||
const { remove, db } = model;
|
const model = prop({}, pouchDocHash);
|
||||||
let data = null;
|
const images = prop([], pouchDocArrayHash);
|
||||||
let title = null;
|
|
||||||
|
|
||||||
function removeImageFromAlbum(id, rev) {
|
const id = computed(pouchDocHash, [model]);
|
||||||
remove(title, id);
|
const members = computed(d => d.members, [model]); // always update
|
||||||
}
|
const title = computed(d => d.title, [model]); // always update
|
||||||
|
|
||||||
return function(vm, model, key, opts) {
|
let laCleanup = null;
|
||||||
const { doc, remove } = model;
|
|
||||||
const { props } = doc;
|
|
||||||
|
|
||||||
if (title !== props.title) {
|
id.subscribe(async () => {
|
||||||
if (data) {
|
const la = await ImageType.find(
|
||||||
data.cleanup();
|
|
||||||
}
|
|
||||||
title = props.title;
|
|
||||||
const SELECTOR = Object.assign(
|
|
||||||
{
|
{
|
||||||
[`tags.${title}`]: { $eq: true }
|
_id: { $in: members() }
|
||||||
},
|
},
|
||||||
image.SELECTOR
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
data = LiveArray(db, SELECTOR);
|
function refresh() {
|
||||||
data.subscribe(() => vm.redraw());
|
images(la());
|
||||||
|
vm.redraw();
|
||||||
}
|
}
|
||||||
const images = data();
|
|
||||||
|
if (laCleanup) {
|
||||||
|
laCleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
laCleanup = la.subscribe(refresh);
|
||||||
|
la.ready.subscribe(refresh);
|
||||||
|
});
|
||||||
|
|
||||||
|
function removeImageFromAlbum(image) {
|
||||||
|
model().removeMember(image._id);
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeAlbum() {
|
||||||
|
model().delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
function uploadImages(album, evt) {
|
||||||
|
Promise.all(Array.from(evt.currentTarget.files).map(ImageType.upload)).then(images => {
|
||||||
|
images.forEach(i => album.addMember(i._id));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
model(params.doc);
|
||||||
|
|
||||||
|
return function(vm, params, key, opts) {
|
||||||
|
model(params.doc);
|
||||||
|
|
||||||
return el('.album', [
|
return el('.album', [
|
||||||
el('h2', [title]),
|
el('h2', [title(), el('button', { onclick: removeAlbum }, 'X')]),
|
||||||
...images.map(i => {
|
el('input#fInput', {
|
||||||
|
type: 'file',
|
||||||
|
multiple: true,
|
||||||
|
accept: 'image/jpeg',
|
||||||
|
onchange: [uploadImages, model()]
|
||||||
|
}),
|
||||||
|
...images().map(i => {
|
||||||
return defineView(
|
return defineView(
|
||||||
ThumbnailView,
|
ThumbnailView,
|
||||||
{
|
{
|
||||||
@ -43,7 +72,7 @@ export function AlbumView(vm, model) {
|
|||||||
showTags: false,
|
showTags: false,
|
||||||
remove: removeImageFromAlbum
|
remove: removeImageFromAlbum
|
||||||
},
|
},
|
||||||
i._id
|
id()
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
]);
|
]);
|
||||||
|
|||||||
@ -3,28 +3,32 @@ import { prop, computed, bundle } from 'frptools';
|
|||||||
|
|
||||||
import { ImageType } from '../data/image.js';
|
import { ImageType } from '../data/image.js';
|
||||||
import { FileType } from '../data/file.js';
|
import { FileType } from '../data/file.js';
|
||||||
import { pouchDocComparator } from '../utils/comparators.js';
|
import { pouchDocArrayHash, pouchDocHash } from '../utils/conversion.js';
|
||||||
|
|
||||||
export function AttachmentImageView(vm, doc) {
|
export function AttachmentImageView(vm, image) {
|
||||||
const model = bundle({
|
const model = prop(image, pouchDocHash);
|
||||||
_id: prop(doc._id),
|
const id = computed(pouchDocHash, [model]);
|
||||||
_rev: prop(doc._rev),
|
const sizes = computed(d => d.sizes, [model]); // always update
|
||||||
sizes: prop(doc.sizes)
|
|
||||||
});
|
|
||||||
|
|
||||||
const blobURL = prop('');
|
const blobURL = prop('');
|
||||||
const imageURL = computed((sizes, bURL) => bURL || sizes.thumbnail || sizes.full, [
|
const imageURL = computed((sizes, bURL) => bURL || sizes.thumbnail || sizes.full, [
|
||||||
model.sizes,
|
sizes,
|
||||||
blobURL
|
blobURL
|
||||||
]);
|
]);
|
||||||
const _key = computed((id, rev) => id + rev, [model._id, model._rev]);
|
|
||||||
|
model.subscribe(() => {
|
||||||
|
if (blobURL()) {
|
||||||
|
URL.revokeObjectURL(blobURL());
|
||||||
|
blobURL('');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
async function loadImageFromBlob() {
|
async function loadImageFromBlob() {
|
||||||
const options = ['thumbnail', 'full'].filter(o => model.sizes().hasOwnProperty(o));
|
const options = ['thumbnail', 'full'].filter(o => sizes().hasOwnProperty(o));
|
||||||
|
|
||||||
for (let attempt of options) {
|
for (let attempt of options) {
|
||||||
try {
|
try {
|
||||||
const data = await FileType.getFromURL(model.sizes()[attempt]);
|
const data = await FileType.getFromURL(sizes()[attempt]);
|
||||||
|
|
||||||
if (blobURL()) {
|
if (blobURL()) {
|
||||||
URL.revokeObjectURL(blobURL());
|
URL.revokeObjectURL(blobURL());
|
||||||
@ -45,16 +49,12 @@ export function AttachmentImageView(vm, doc) {
|
|||||||
const redrawOff = imageURL.subscribe(() => vm.redraw());
|
const redrawOff = imageURL.subscribe(() => vm.redraw());
|
||||||
|
|
||||||
return function render(vm, doc) {
|
return function render(vm, doc) {
|
||||||
if (!pouchDocComparator(doc, { _id: model._id(), _rev: model._rev() })) {
|
|
||||||
URL.revokeObjectURL(blobURL());
|
|
||||||
blobURL('');
|
|
||||||
}
|
|
||||||
model(doc);
|
model(doc);
|
||||||
|
|
||||||
return el('img', {
|
return el('img', {
|
||||||
src: imageURL(),
|
src: imageURL(),
|
||||||
onerror: loadImageFromBlob,
|
onerror: loadImageFromBlob,
|
||||||
_key: _key(),
|
_key: id(),
|
||||||
_hooks: {
|
_hooks: {
|
||||||
didRemove: cleanup
|
didRemove: cleanup
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import { defineView as vw } from 'domvm';
|
import { defineView as vw } from 'domvm';
|
||||||
import { ImageType } from '../data/image.js';
|
import { ImageType } from '../data/image.js';
|
||||||
// import * as index from '../data/indexType.js';
|
import { AlbumType } from '../data/album.js';
|
||||||
// import * as imageTag from '../context/manageImageTags.js';
|
|
||||||
import { ThumbnailView } from './thumbnail.js';
|
import { ThumbnailView } from './thumbnail.js';
|
||||||
import { AlbumView } from './album.js';
|
import { AlbumView } from './album.js';
|
||||||
import { router, routeChanged } from '../services/router.js';
|
import { router, routeChanged } from '../services/router.js';
|
||||||
@ -18,11 +17,11 @@ export function GalleryView(vm, model) {
|
|||||||
true
|
true
|
||||||
),
|
),
|
||||||
title: 'Images'
|
title: 'Images'
|
||||||
|
},
|
||||||
|
albums: {
|
||||||
|
data: AlbumType.find({}, true),
|
||||||
|
title: 'Albums'
|
||||||
}
|
}
|
||||||
// albums: {
|
|
||||||
// selector: index.SELECTOR,
|
|
||||||
// title: 'Albums'
|
|
||||||
// }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let data = null;
|
let data = null;
|
||||||
@ -33,12 +32,26 @@ export function GalleryView(vm, model) {
|
|||||||
Array.from(evt.currentTarget.files).forEach(ImageType.upload);
|
Array.from(evt.currentTarget.files).forEach(ImageType.upload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function deleteImage(i) {
|
||||||
|
ImageType.delete(i._id);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addAlbum() {
|
||||||
|
const a = new AlbumType({
|
||||||
|
title: prompt('Album Name'),
|
||||||
|
members: []
|
||||||
|
});
|
||||||
|
a.save();
|
||||||
|
}
|
||||||
|
|
||||||
routeChanged.subscribe(function onRouteChange(router, route) {
|
routeChanged.subscribe(function onRouteChange(router, route) {
|
||||||
if (laCleanup) {
|
if (laCleanup) {
|
||||||
laCleanup();
|
laCleanup();
|
||||||
}
|
}
|
||||||
const o = NAV_OPTIONS[route.name];
|
const o = NAV_OPTIONS[route.name];
|
||||||
title = o.title;
|
title = o.title;
|
||||||
|
vm.redraw();
|
||||||
|
|
||||||
return o.data.then(la => {
|
return o.data.then(la => {
|
||||||
data = la;
|
data = la;
|
||||||
laCleanup = data.subscribe(() => {
|
laCleanup = data.subscribe(() => {
|
||||||
@ -52,6 +65,7 @@ export function GalleryView(vm, model) {
|
|||||||
return el('.gallery', [
|
return el('.gallery', [
|
||||||
header([
|
header([
|
||||||
el('div', { css: { fontSize: '20pt' } }, 'Gallery'),
|
el('div', { css: { fontSize: '20pt' } }, 'Gallery'),
|
||||||
|
el('button', { onclick: addAlbum }, 'Add Album'),
|
||||||
el('input#fInput', {
|
el('input#fInput', {
|
||||||
type: 'file',
|
type: 'file',
|
||||||
multiple: true,
|
multiple: true,
|
||||||
@ -73,7 +87,7 @@ export function GalleryView(vm, model) {
|
|||||||
doc: i,
|
doc: i,
|
||||||
showTags: true,
|
showTags: true,
|
||||||
// addTag: imageTag.add,
|
// addTag: imageTag.add,
|
||||||
remove: i.delete.bind(i)
|
remove: deleteImage
|
||||||
// removeTag: imageTag.remove
|
// removeTag: imageTag.remove
|
||||||
},
|
},
|
||||||
i._id + i._rev
|
i._id + i._rev
|
||||||
@ -83,8 +97,7 @@ export function GalleryView(vm, model) {
|
|||||||
return vw(
|
return vw(
|
||||||
AlbumView,
|
AlbumView,
|
||||||
{
|
{
|
||||||
doc: a,
|
doc: a
|
||||||
db
|
|
||||||
// addTag: imageTag.add,
|
// addTag: imageTag.add,
|
||||||
// remove: imageTag.remove
|
// remove: imageTag.remove
|
||||||
},
|
},
|
||||||
|
|||||||
@ -24,7 +24,7 @@ export function ThumbnailView(vm, model) {
|
|||||||
el(
|
el(
|
||||||
`figure#${doc._id}.image`,
|
`figure#${doc._id}.image`,
|
||||||
{
|
{
|
||||||
onclick: { img: [remove, id, rev] }
|
onclick: { img: [remove, doc] }
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
vw(AttachmentImageView, doc, doc._id + doc._rev),
|
vw(AttachmentImageView, doc, doc._id + doc._rev),
|
||||||
|
|||||||
@ -95,19 +95,21 @@ export function PouchORM(PouchDB) {
|
|||||||
|
|
||||||
const instantiate = doc => new cls(doc);
|
const instantiate = doc => new cls(doc);
|
||||||
|
|
||||||
async function find(idOrQuery, live = false) {
|
async function find(idOrSelector, live = false) {
|
||||||
if (typeof idOrQuery === 'string') {
|
if (typeof idOrSelector === 'string') {
|
||||||
return instantiate(await _db.get(idOrQuery));
|
return instantiate(await _db.get(idOrSelector));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isSelector = isObject(idOrSelector);
|
||||||
|
|
||||||
const selector = Object.assign(
|
const selector = Object.assign(
|
||||||
{ _deleted: { exists: false } },
|
isSelector && idOrSelector._deleted ? { _deleted: true } : { _deleted: { exists: false } },
|
||||||
isObject(idOrQuery) ? idOrQuery : { _id: { $gt: `${prefix}_0`, $lt: `${prefix}_\ufff0` } }
|
isSelector ? idOrSelector : { _id: { $gt: `${prefix}_0`, $lt: `${prefix}_\ufff0` } }
|
||||||
);
|
);
|
||||||
if (live) {
|
if (live) {
|
||||||
return LiveArray(_db, idOrQuery, instantiate);
|
return LiveArray(_db, idOrSelector, instantiate);
|
||||||
}
|
}
|
||||||
return (await _db.find({ selector: idOrQuery })).docs.map(instantiate);
|
return (await _db.find({ selector: idOrSelector })).docs.map(instantiate);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getOrCreate(props) {
|
async function getOrCreate(props) {
|
||||||
@ -123,6 +125,18 @@ export function PouchORM(PouchDB) {
|
|||||||
return doc;
|
return doc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function _delete(id) {
|
||||||
|
try {
|
||||||
|
const doc = await find(id);
|
||||||
|
doc._deleted = true;
|
||||||
|
await _db.put(doc);
|
||||||
|
} catch (e) {
|
||||||
|
if (e.status !== 404) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Object.defineProperties(cls.prototype, {
|
Object.defineProperties(cls.prototype, {
|
||||||
_name: { value: name },
|
_name: { value: name },
|
||||||
_prefix: { value: prefix },
|
_prefix: { value: prefix },
|
||||||
@ -133,6 +147,7 @@ export function PouchORM(PouchDB) {
|
|||||||
Object.defineProperties(cls, {
|
Object.defineProperties(cls, {
|
||||||
getOrCreate: { value: getOrCreate },
|
getOrCreate: { value: getOrCreate },
|
||||||
find: { value: find },
|
find: { value: find },
|
||||||
|
delete: { value: _delete },
|
||||||
db: { value: _db },
|
db: { value: _db },
|
||||||
name: { value: name }
|
name: { value: name }
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { readAsArrayBuffer } from 'pouchdb-binary-utils';
|
import { readAsArrayBuffer } from 'pouchdb-binary-utils';
|
||||||
|
import { isObject } from './comparators';
|
||||||
|
|
||||||
export function bufferToHexString(buffer) {
|
export function bufferToHexString(buffer) {
|
||||||
const hexCodes = [];
|
const hexCodes = [];
|
||||||
@ -20,6 +21,14 @@ export function blobToArrayBuffer(blob) {
|
|||||||
return new Promise(resolve => readAsArrayBuffer(blob, resolve));
|
return new Promise(resolve => readAsArrayBuffer(blob, resolve));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const arrayHashWrapper = hash => arr => (Array.isArray(arr) ? arr.map(hash).join('?') : arr);
|
||||||
|
|
||||||
|
export function pouchDocHash(d) {
|
||||||
|
return isObject(d) ? `${d._id}:${d._rev}` : d;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const pouchDocArrayHash = arrayHashWrapper(pouchDocHash);
|
||||||
|
|
||||||
export function deepAssign(to, ...rest) {
|
export function deepAssign(to, ...rest) {
|
||||||
for (let src of rest) {
|
for (let src of rest) {
|
||||||
for (let prop in src) {
|
for (let prop in src) {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { prop, computed } from 'frptools';
|
import { prop, computed } from 'frptools';
|
||||||
|
|
||||||
import { Watcher } from './watcher.js';
|
import { Watcher } from './watcher.js';
|
||||||
import { pouchDocArrayComparator } from './comparators.js';
|
import { pouchDocArrayHash } from './conversion.js';
|
||||||
|
|
||||||
// LiveArray is a subscribable property function that always returns the db results that match the provided selector and calls subscribers when the results change.
|
// LiveArray is a subscribable property function that always returns the db results that match the provided selector and calls subscribers when the results change.
|
||||||
export function LiveArray(db, selector, mapper) {
|
export function LiveArray(db, selector, mapper) {
|
||||||
@ -11,7 +11,7 @@ export function LiveArray(db, selector, mapper) {
|
|||||||
|
|
||||||
const ready = prop(false);
|
const ready = prop(false);
|
||||||
const data = prop({ docs: [] });
|
const data = prop({ docs: [] });
|
||||||
const docs = computed(r => r.docs.map(_mapper), [data], pouchDocArrayComparator);
|
const docs = computed(r => r.docs.map(_mapper), [data], pouchDocArrayHash);
|
||||||
|
|
||||||
const cleanup = () => {
|
const cleanup = () => {
|
||||||
docs.unsubscribeAll();
|
docs.unsubscribeAll();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user