From ccd0fafd9ec7984151ee30404f009c43f02279fa Mon Sep 17 00:00:00 2001 From: Timothy Farrell Date: Tue, 18 Apr 2017 09:06:26 -0500 Subject: [PATCH] Add album view --- packages/gallery/src/app.js | 116 +++++++++++------- .../gallery/src/context/manageImageTags.js | 6 +- packages/gallery/src/data/image.js | 24 +++- packages/gallery/src/data/indexType.js | 14 ++- packages/gallery/src/index.html | 11 +- 5 files changed, 119 insertions(+), 52 deletions(-) diff --git a/packages/gallery/src/app.js b/packages/gallery/src/app.js index e6b5012..179b738 100644 --- a/packages/gallery/src/app.js +++ b/packages/gallery/src/app.js @@ -1,62 +1,94 @@ import * as image from './data/image.js'; +import * as index from './data/indexType.js'; import { getDatabase } from './services/db.js'; import * as imageTag from './context/manageImageTags.js'; import './context/generateThumbnails.js'; -document.querySelector('#fInput').onchange = async evt => { - image.add(evt.currentTarget.files); -}; - window.__DEV__ = true; window.db = getDatabase(); image.imported.subscribe(refresh); +const header = document.querySelector('h1'); +const container = document.querySelector('#app'); +const displaySelector = document.querySelector('#display'); + +// Events +displaySelector.onchange = refresh; +document.querySelector('#fInput').onchange = async evt => { + image.add(evt.currentTarget.files); +}; // To test the output: function refresh() { - setTimeout(() => history.go(0), 100); + setTimeout(render, 100); } -db.allDocs({ include_docs: true, attachments: true }).then(async results => { - results.rows.forEach(r => { - const doc = r.doc; - }); +function renderThumbnail(id, name, doc, tags) { + const c = document.createElement('div'); + const e = document.createElement('img'); - function renderThumbnail(id, name, doc, tags) { - const c = document.createElement('div'); - const e = document.createElement('img'); + c.appendChild(e); + c.id = id; + c.className = 'image'; + e.title = `${id} ${name}`; + e.src = `data:${doc.content_type};base64,${doc.data}`; + e.dataset.id = id; + e.onclick = evt => image.remove(evt.currentTarget.dataset.id).then(refresh); - c.appendChild(e); - c.id = id; - c.className = 'image'; - e.title = `${id} ${name}`; - e.src = `data:${doc.content_type};base64,${doc.data}`; - e.dataset.id = id; - e.onclick = evt => image.remove(evt.currentTarget.dataset.id).then(refresh); + Object.entries(tags) + .filter(([_, visible]) => visible) + .forEach(([title, _]) => { + const t = document.createElement('span'); + t.textContent = title; + t.className = 'tag'; + t.onclick = evt => imageTag.remove(title, id).then(refresh); + c.appendChild(t); + }); + return c; +} - Object.entries(tags) - .filter(([_, visible]) => visible) - .forEach(([title, _]) => { - const t = document.createElement('span'); - t.textContent = title; - t.className = 'tag'; - t.onclick = evt => imageTag.remove(title, id).then(refresh); - c.appendChild(t); - }); - return c; - } - - results.rows.forEach(r => { - for (let aName in r.doc._attachments) { - if (aName !== 'thumbnail') { - continue; - } - document.body.appendChild( - renderThumbnail(r.doc._id, aName, r.doc._attachments[aName], r.doc.tags) - ); +function renderImage(imageRow, imageContainer, showTags = true) { + for (let aName in imageRow.doc._attachments) { + if (aName !== 'thumbnail') { + continue; } - }); + imageContainer.appendChild( + renderThumbnail( + imageRow.doc._id, + aName, + imageRow.doc._attachments[aName], + showTags ? imageRow.doc.tags : [] + ) + ); + } +} + +async function renderAlbum(indexRow) { + const doc = indexRow.doc; + const l = document.createElement('h2'); + l.innerText = indexRow.doc.props.title; + container.appendChild(l); + + const albumContainer = document.createElement('div'); + container.appendChild(albumContainer); + + const results = await image.find(doc.members, { attachments: true }); + results.rows.filter(i => i.doc).forEach(i => renderImage(i, albumContainer, false)); +} + +async function render() { + container.innerHTML = ''; + + if (displaySelector.value === 'i') { + header.innerText = 'Images'; + const results = await image.find({ attachments: true }); + results.rows.forEach(i => renderImage(i, container)); + } else { + header.innerText = 'Albums'; + const results = await index.find({ attachments: true }); + results.rows.forEach(renderAlbum); + } Array.from(document.querySelectorAll('.image')).forEach(i => { const b = document.createElement('button'); @@ -64,4 +96,6 @@ db.allDocs({ include_docs: true, attachments: true }).then(async results => { b.textContent = '+'; i.appendChild(b); }); -}); +} + +render(); diff --git a/packages/gallery/src/context/manageImageTags.js b/packages/gallery/src/context/manageImageTags.js index 51eaa76..09c5d90 100644 --- a/packages/gallery/src/context/manageImageTags.js +++ b/packages/gallery/src/context/manageImageTags.js @@ -3,7 +3,7 @@ import * as index from '../data/indexType.js'; export async function add(title, imageId, visible = true) { const trimmedTitle = title.trim(); - await index.add(trimmedTitle, [imageId]); + await index.add(trimmedTitle, { title: trimmedTitle }, [imageId]); return image.update(imageId, { tags: { [trimmedTitle]: visible } }); @@ -14,3 +14,7 @@ export async function remove(title, imageId) { 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)); +}); diff --git a/packages/gallery/src/data/image.js b/packages/gallery/src/data/image.js index 729b536..17759ba 100644 --- a/packages/gallery/src/data/image.js +++ b/packages/gallery/src/data/image.js @@ -8,13 +8,26 @@ import { Event, backgroundTask } from '../utils/event.js'; const db = getDatabase(); const PROCESS_PREFIX = 'importing'; +const PREFIX = 'image'; // Events export const imported = new Event('Image.imported'); +export const removed = new Event('Image.removed'); // Methods +const getId = id => (id.startsWith(PREFIX) ? id : `${PREFIX}_${id}`); + export async function find(keys, options = {}) { - return await db.allDocs(Object.assign({ include_docs: true }, options, { keys })); + 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}_0`; + opts.endkey = `${PREFIX}_\ufff0`; + } + return await db.allDocs(opts); } export async function add(imageFileList) { @@ -45,6 +58,9 @@ export async function remove(ids, rev) { try { const doc = rev ? { _id: ids, _rev: rev } : await db.get(ids); await db.remove(doc); + if (doc._id.startsWith(PREFIX)) { + removed.fire(doc); + } return true; } catch (e) { if (e.status !== 404) { @@ -76,8 +92,8 @@ export async function addAttachment(doc, key, blob) { // Internal Functions const processImportables = backgroundTask(async function _processImportables() { const result = await db.allDocs({ - startkey: `${PROCESS_PREFIX}_0`, - endkey: `${PROCESS_PREFIX}_z`, + startkey: `${PROCESS_PREFIX}_`, + endkey: `${PROCESS_PREFIX}_\ufff0`, include_docs: true, attachments: true, binary: true, @@ -97,7 +113,7 @@ const processImportables = backgroundTask(async function _processImportables() { tags.DateTimeOriginal ? new Date(tags.DateTimeOriginal * 1000).toISOString() : doc.modifiedDate ); const { _id, _rev } = doc; - const id = `image_${originalDate.getTime().toString(36)}_${digest.substr(0, 6)}`; + const id = `${PREFIX}_${originalDate.getTime().toString(36)}_${digest.substr(0, 6)}`; let continueProcessing = true; try { diff --git a/packages/gallery/src/data/indexType.js b/packages/gallery/src/data/indexType.js index e02cb1a..389210b 100644 --- a/packages/gallery/src/data/indexType.js +++ b/packages/gallery/src/data/indexType.js @@ -13,13 +13,23 @@ export const hashString = name => const getId = id => (id.startsWith(PREFIX) ? id : `${PREFIX}_${hashString(id)}`); export async function find(keys, options = {}) { - return await db.allDocs(Object.assign({ include_docs: true }, options, { keys: keys.map(getId) })); + 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, members = []) { +export async function add(id, props = {}, members = []) { const _id = getId(id); const [results, created] = await getOrCreate({ _id, + props, members: [] }); diff --git a/packages/gallery/src/index.html b/packages/gallery/src/index.html index a22bb68..097dc69 100644 --- a/packages/gallery/src/index.html +++ b/packages/gallery/src/index.html @@ -1,8 +1,11 @@ -
- Images - -
+ + +

+