From 9c465cd4052ba7fbda53df055a10f89376dffa27 Mon Sep 17 00:00:00 2001 From: Timothy Farrell Date: Mon, 17 Apr 2017 22:25:58 -0500 Subject: [PATCH] Tag support --- packages/gallery/src/app.js | 51 +++++++++++++--- .../gallery/src/context/manageImageTags.js | 16 +++++ packages/gallery/src/data/image.js | 1 + packages/gallery/src/data/indexType.js | 58 +++++++++++++++++++ packages/gallery/src/services/db.js | 13 +++++ 5 files changed, 131 insertions(+), 8 deletions(-) create mode 100644 packages/gallery/src/context/manageImageTags.js create mode 100644 packages/gallery/src/data/indexType.js diff --git a/packages/gallery/src/app.js b/packages/gallery/src/app.js index fe0ec22..e6b5012 100644 --- a/packages/gallery/src/app.js +++ b/packages/gallery/src/app.js @@ -1,5 +1,6 @@ import * as image from './data/image.js'; import { getDatabase } from './services/db.js'; +import * as imageTag from './context/manageImageTags.js'; import './context/generateThumbnails.js'; @@ -17,16 +18,50 @@ function refresh() { setTimeout(() => history.go(0), 100); } -imagedb.allDocs({ include_docs: true, attachments: true }).then(results => { +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'); + + 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; + } + results.rows.forEach(r => { for (let aName in r.doc._attachments) { - const a = r.doc._attachments[aName]; - const e = document.createElement('img'); - document.body.appendChild(e); - e.title = `${r.doc._id} ${aName}`; - e.src = `data:${a.content_type};base64,${a.data}`; - e.dataset.id = r.doc._id; - e.onclick = evt => image.remove(evt.currentTarget.dataset.id).then(refresh); + if (aName !== 'thumbnail') { + continue; + } + document.body.appendChild( + renderThumbnail(r.doc._id, aName, r.doc._attachments[aName], r.doc.tags) + ); } }); + + Array.from(document.querySelectorAll('.image')).forEach(i => { + const b = document.createElement('button'); + b.onclick = evt => imageTag.add(prompt('Tag Name'), i.id).then(refresh); + b.textContent = '+'; + i.appendChild(b); + }); }); diff --git a/packages/gallery/src/context/manageImageTags.js b/packages/gallery/src/context/manageImageTags.js new file mode 100644 index 0000000..51eaa76 --- /dev/null +++ b/packages/gallery/src/context/manageImageTags.js @@ -0,0 +1,16 @@ +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, [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); +} diff --git a/packages/gallery/src/data/image.js b/packages/gallery/src/data/image.js index 2b26233..729b536 100644 --- a/packages/gallery/src/data/image.js +++ b/packages/gallery/src/data/image.js @@ -25,6 +25,7 @@ export async function add(imageFileList) { size: f.size, modifiedDate: new Date(f.lastModified).toISOString(), uploadedDate: new Date().toISOString(), + tags: {}, _attachments: { image: { content_type: f.type, diff --git a/packages/gallery/src/data/indexType.js b/packages/gallery/src/data/indexType.js new file mode 100644 index 0000000..e02cb1a --- /dev/null +++ b/packages/gallery/src/data/indexType.js @@ -0,0 +1,58 @@ +import { log, error } from '../services/console.js'; +import { getDatabase, getOrCreate } from '../services/db.js'; + +const db = getDatabase(); +const PREFIX = 'index'; + +// 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 = {}) { + return await db.allDocs(Object.assign({ include_docs: true }, options, { keys: keys.map(getId) })); +} + +export async function add(id, members = []) { + const _id = getId(id); + const [results, created] = await getOrCreate({ + _id, + 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); + } + + 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); + } else { + await db.remove(doc); + } + } +} diff --git a/packages/gallery/src/services/db.js b/packages/gallery/src/services/db.js index 595b68d..4637811 100644 --- a/packages/gallery/src/services/db.js +++ b/packages/gallery/src/services/db.js @@ -15,3 +15,16 @@ export function getDatabase(name = 'gallery') { } return dbs.get(name); } + +export async function getOrCreate(doc) { + try { + const results = await db.get(doc._id); + return [results, false]; + } catch (e) { + if (e.status === 404) { + const results = db.put(doc); + return [results, true]; + } + throw e; + } +}