From 86ccbfbb13e8545fb689c8f1d44377157b28f76e Mon Sep 17 00:00:00 2001 From: Timothy Farrell Date: Fri, 24 Nov 2017 23:02:24 -0600 Subject: [PATCH] Use streams for a little better memory reuse. --- packages/gallery/src/app.js | 5 +- packages/gallery/src/interface/album.js | 49 +++++++++++-------- .../gallery/src/interface/attachmentImage.js | 9 +--- packages/gallery/src/interface/dropzone.js | 22 +++------ packages/gallery/src/interface/gallery.js | 24 ++++----- packages/gallery/src/services/style.js | 10 +++- packages/gallery/src/utils/domvm.js | 2 +- packages/gallery/src/utils/event.js | 7 +++ 8 files changed, 70 insertions(+), 58 deletions(-) diff --git a/packages/gallery/src/app.js b/packages/gallery/src/app.js index 4bd97cd..8e1c245 100644 --- a/packages/gallery/src/app.js +++ b/packages/gallery/src/app.js @@ -1,13 +1,16 @@ -import { createView } from './utils/domvm.js'; +import { createView, config } from './utils/domvm.js'; import * as styles from './app.css'; import { GalleryView } from './interface/gallery.js'; import { router } from './services/router.js'; +import { streamConfig } from './utils/event.js'; import { EventEmitter } from 'events'; EventEmitter.defaultMaxListeners = 1000; // https://github.com/pouchdb/pouchdb/issues/6123 +config({ stream: streamConfig }); + // Attach our root view to the DOM createView(GalleryView, {}).mount(document.body); diff --git a/packages/gallery/src/interface/album.js b/packages/gallery/src/interface/album.js index 13d9def..1c864b2 100644 --- a/packages/gallery/src/interface/album.js +++ b/packages/gallery/src/interface/album.js @@ -4,7 +4,7 @@ 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 { prop, computed, bundle } from 'frptools'; +import { prop, computed } from 'frptools'; export function AlbumView(vm, doc) { const model = prop({}, pouchDocHash); @@ -15,25 +15,28 @@ export function AlbumView(vm, doc) { const title = computed(d => d.title, [model]); // always update let laCleanup = null; + const refresh = _ => vm.redraw(); + const subscriptions = [ + images.subscribe(refresh), + model.subscribe(async album => { + if (!album.findImages) { + return; + } + const imagesLiveArray = await album.findImages(true); - model.subscribe(async album => { - if (!album.findImages) { - return; - } - const imagesLiveArray = await album.findImages(true); + if (laCleanup) { + laCleanup(); + } - if (laCleanup) { - laCleanup(); - } + function refresh() { + images(imagesLiveArray()); + vm.redraw(); + } - function refresh() { - images(imagesLiveArray()); - vm.redraw(); - } - - laCleanup = imagesLiveArray.subscribe(refresh); - imagesLiveArray.ready.subscribe(refresh); - }); + laCleanup = imagesLiveArray.subscribe(refresh); + imagesLiveArray.ready.subscribe(refresh); + }) + ]; function removeImageFromAlbum(image) { model().removeImage(image); @@ -47,13 +50,19 @@ export function AlbumView(vm, doc) { Array.from(evt.currentTarget.files).forEach(f => album.addImageBlob(f)); } + function cleanup() { + if (laCleanup) { + laCleanup(); + } + subscriptions.forEach(s => s()); + } + model(doc); return function(vm, album, key, opts) { - model(album); - return el('.album', [ - el('h2', [title(), el('button', { onclick: [removeAlbum, album] }, 'X')]), + el('h2', title), + el('button', { onclick: [removeAlbum, album] }, 'X'), el('input#fInput', { type: 'file', multiple: true, diff --git a/packages/gallery/src/interface/attachmentImage.js b/packages/gallery/src/interface/attachmentImage.js index 4264b5c..86bc629 100644 --- a/packages/gallery/src/interface/attachmentImage.js +++ b/packages/gallery/src/interface/attachmentImage.js @@ -3,7 +3,7 @@ import { prop, computed, bundle } from 'frptools'; import { ImageType } from '../data/image.js'; import { FileType } from '../data/file.js'; -import { pouchDocArrayHash, pouchDocHash } from '../utils/conversion.js'; +import { pouchDocHash } from '../utils/conversion.js'; export function AttachmentImageView(vm, image) { const model = prop(image, pouchDocHash); @@ -42,17 +42,12 @@ export function AttachmentImageView(vm, image) { } function cleanup() { - redrawOff(); URL.revokeObjectURL(blobURL()); } - const redrawOff = imageURL.subscribe(() => vm.redraw()); - return function render(vm, doc) { - model(doc); - return el('img', { - src: imageURL(), + src: imageURL, onerror: loadImageFromBlob, _key: id(), _hooks: { diff --git a/packages/gallery/src/interface/dropzone.js b/packages/gallery/src/interface/dropzone.js index 212cb62..5275156 100644 --- a/packages/gallery/src/interface/dropzone.js +++ b/packages/gallery/src/interface/dropzone.js @@ -1,5 +1,6 @@ import { prop, computed } from 'frptools'; -import { injectStyle, el } from '../services/style'; +import { injectStyle } from '../services/style'; +import { defineElement as el } from '../utils/domvm.js'; const CSS_DROPZONE = { width: '200px', @@ -14,10 +15,13 @@ const CSS_DROPZONE_ACTIVE = { }; export function Dropzone(vm, model) { - const { ondrop, ondragenter, ondragleave } = model; + const { ondrop, ondragenter, ondragleave, className, activeClassName, children } = model; + + const baseClassName = className || injectStyle(CSS_DROPZONE); + const hoverClassName = `${baseClassName} ${activeClassName || injectStyle(CSS_DROPZONE_ACTIVE)}`; const enterCounter = prop(0); - enterCounter.subscribe(() => vm.redraw()); + const class_ = computed(c => (c === 0 ? baseClassName : hoverClassName), [enterCounter]); function onDragOver(evt) { // allows the browser to accept drops. @@ -50,16 +54,6 @@ export function Dropzone(vm, model) { } return function render(vm, model) { - const { className, activeClassName, class: _class, children } = model; - - const class_ = Object.assign( - { - [className || injectStyle(CSS_DROPZONE)]: true, - [activeClassName || injectStyle(CSS_DROPZONE_ACTIVE)]: enterCounter() > 0 - }, - _class || {} - ); - return el( 'div', { @@ -69,7 +63,7 @@ export function Dropzone(vm, model) { ondragleave: onDragLeave, ondrop: onDrop }, - ...children + children() ); }; } diff --git a/packages/gallery/src/interface/gallery.js b/packages/gallery/src/interface/gallery.js index 0a2f49e..b420efb 100644 --- a/packages/gallery/src/interface/gallery.js +++ b/packages/gallery/src/interface/gallery.js @@ -1,14 +1,15 @@ -import { defineView as vw } from '../utils/domvm.js'; +import { prop } from 'frptools'; + +import { defineView as vw, defineElement as el } from '../utils/domvm.js'; import { ImageType } from '../data/image.js'; import { AlbumType } from '../data/album.js'; import { ThumbnailView } from './thumbnail.js'; import { AlbumView } from './album.js'; import { Dropzone } from './dropzone.js'; import { router, routeChanged } from '../services/router.js'; -import { injectStyle, styled, el } from '../services/style.js'; +import { injectStyle, styled } from '../services/style.js'; export function GalleryView(vm, model) { - const { db } = model; const NAV_OPTIONS = { images: { data: ImageType.find( @@ -27,7 +28,7 @@ export function GalleryView(vm, model) { let data = null; let laCleanup = null; - let title = ''; + const title = prop(''); function uploadImages(files) { Array.from(files).forEach(ImageType.upload); @@ -53,15 +54,13 @@ export function GalleryView(vm, model) { laCleanup(); } const o = NAV_OPTIONS[route.name]; - title = o.title; - vm.redraw(); + title(o.title); return o.data.then(la => { data = la; laCleanup = data.subscribe(() => { vm.redraw(); }); - data.ready.subscribe(() => vm.redraw); }); }); @@ -70,13 +69,12 @@ export function GalleryView(vm, model) { el('a', { href: router.href('images') }, 'Images'), el('a', { href: router.href('albums') }, 'Albums'), el('h1', title), - ...(title === 'Images' + ...(title() === 'Images' ? data().map(i => { return vw( ThumbnailView, { doc: i, - showTags: true, remove: deleteImage }, i._hash() @@ -93,9 +91,7 @@ export function GalleryView(vm, model) { return el('h1', 'Loading...'); } - return el( - '.gallery', - { class: slate }, + return el('.gallery', { class: slate }, [ header([ el('div', { css: { fontSize: '20pt' } }, 'Gallery'), headerRight( @@ -114,11 +110,11 @@ export function GalleryView(vm, model) { type: 'file', multiple: true, // FIXME - these don't carry through to the input tag accept: 'image/jpeg', - children: renderDropzone() + children: renderDropzone }, 'dz' ) - ); + ]); }; } diff --git a/packages/gallery/src/services/style.js b/packages/gallery/src/services/style.js index 4e76890..2019945 100644 --- a/packages/gallery/src/services/style.js +++ b/packages/gallery/src/services/style.js @@ -2,6 +2,7 @@ import Styletron from 'styletron'; import { injectStyle as _injectStyle } from 'styletron-utils'; import { defineElement } from '../utils/domvm.js'; import { isObject } from '../utils/comparators.js'; +import { streamConfig } from '../utils/event.js'; const styletronSingleton = new Styletron(); @@ -12,7 +13,11 @@ export function injectStyle(...styles) { export function el(sig, ...attrsOrChildren) { let attrs = {}; let children = attrsOrChildren; - if (attrsOrChildren.length && isObject(attrsOrChildren[0])) { + if ( + attrsOrChildren.length && + isObject(attrsOrChildren[0]) && + !streamConfig.is(attrsOrChildren[0]) + ) { attrs = attrsOrChildren[0]; children = attrsOrChildren.slice(1); if (isObject(attrs.css)) { @@ -33,6 +38,9 @@ export function el(sig, ...attrsOrChildren) { .join(' '); } } + if (children.length === 1 && streamConfig.is(attrsOrChildren[0])) { + children = children[0]; + } return defineElement(sig, attrs, children); } diff --git a/packages/gallery/src/utils/domvm.js b/packages/gallery/src/utils/domvm.js index cf442ad..5f8d726 100644 --- a/packages/gallery/src/utils/domvm.js +++ b/packages/gallery/src/utils/domvm.js @@ -1,2 +1,2 @@ // export * from 'domvm/dist/dev/domvm.dev.js'; -export * from 'domvm/dist/micro/domvm.micro.js'; +export * from 'domvm/dist/mini/domvm.mini.js'; diff --git a/packages/gallery/src/utils/event.js b/packages/gallery/src/utils/event.js index 452ff87..9d61aba 100644 --- a/packages/gallery/src/utils/event.js +++ b/packages/gallery/src/utils/event.js @@ -98,3 +98,10 @@ export function backgroundTask(fn, initialDelay = 500) { return wrapper; } + +export const streamConfig = { + is: s => s && typeof s.subscribe === 'function', + val: s => s(), + sub: (s, fn) => s.subscribe(fn), + unsub: s => s() +};