Use streams for a little better memory reuse.

This commit is contained in:
Timothy Farrell 2017-11-24 23:02:24 -06:00
parent 55828f1351
commit 86ccbfbb13
8 changed files with 70 additions and 58 deletions

View File

@ -1,13 +1,16 @@
import { createView } from './utils/domvm.js'; import { createView, config } from './utils/domvm.js';
import * as styles from './app.css'; import * as styles from './app.css';
import { GalleryView } from './interface/gallery.js'; import { GalleryView } from './interface/gallery.js';
import { router } from './services/router.js'; import { router } from './services/router.js';
import { streamConfig } from './utils/event.js';
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
EventEmitter.defaultMaxListeners = 1000; // https://github.com/pouchdb/pouchdb/issues/6123 EventEmitter.defaultMaxListeners = 1000; // https://github.com/pouchdb/pouchdb/issues/6123
config({ stream: streamConfig });
// Attach our root view to the DOM // Attach our root view to the DOM
createView(GalleryView, {}).mount(document.body); createView(GalleryView, {}).mount(document.body);

View File

@ -4,7 +4,7 @@ import { ImageType } from '../data/image.js';
import { FileType } from '../data/file.js'; import { FileType } from '../data/file.js';
import { pouchDocArrayHash, pouchDocHash } from '../utils/conversion.js'; import { pouchDocArrayHash, pouchDocHash } from '../utils/conversion.js';
import { ThumbnailView } from './thumbnail.js'; import { ThumbnailView } from './thumbnail.js';
import { prop, computed, bundle } from 'frptools'; import { prop, computed } from 'frptools';
export function AlbumView(vm, doc) { export function AlbumView(vm, doc) {
const model = prop({}, pouchDocHash); const model = prop({}, pouchDocHash);
@ -15,25 +15,28 @@ export function AlbumView(vm, doc) {
const title = computed(d => d.title, [model]); // always update const title = computed(d => d.title, [model]); // always update
let laCleanup = null; 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 (laCleanup) {
if (!album.findImages) { laCleanup();
return; }
}
const imagesLiveArray = await album.findImages(true);
if (laCleanup) { function refresh() {
laCleanup(); images(imagesLiveArray());
} vm.redraw();
}
function refresh() { laCleanup = imagesLiveArray.subscribe(refresh);
images(imagesLiveArray()); imagesLiveArray.ready.subscribe(refresh);
vm.redraw(); })
} ];
laCleanup = imagesLiveArray.subscribe(refresh);
imagesLiveArray.ready.subscribe(refresh);
});
function removeImageFromAlbum(image) { function removeImageFromAlbum(image) {
model().removeImage(image); model().removeImage(image);
@ -47,13 +50,19 @@ export function AlbumView(vm, doc) {
Array.from(evt.currentTarget.files).forEach(f => album.addImageBlob(f)); Array.from(evt.currentTarget.files).forEach(f => album.addImageBlob(f));
} }
function cleanup() {
if (laCleanup) {
laCleanup();
}
subscriptions.forEach(s => s());
}
model(doc); model(doc);
return function(vm, album, key, opts) { return function(vm, album, key, opts) {
model(album);
return el('.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', { el('input#fInput', {
type: 'file', type: 'file',
multiple: true, multiple: true,

View File

@ -3,7 +3,7 @@ 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 { pouchDocArrayHash, pouchDocHash } from '../utils/conversion.js'; import { pouchDocHash } from '../utils/conversion.js';
export function AttachmentImageView(vm, image) { export function AttachmentImageView(vm, image) {
const model = prop(image, pouchDocHash); const model = prop(image, pouchDocHash);
@ -42,17 +42,12 @@ export function AttachmentImageView(vm, image) {
} }
function cleanup() { function cleanup() {
redrawOff();
URL.revokeObjectURL(blobURL()); URL.revokeObjectURL(blobURL());
} }
const redrawOff = imageURL.subscribe(() => vm.redraw());
return function render(vm, doc) { return function render(vm, doc) {
model(doc);
return el('img', { return el('img', {
src: imageURL(), src: imageURL,
onerror: loadImageFromBlob, onerror: loadImageFromBlob,
_key: id(), _key: id(),
_hooks: { _hooks: {

View File

@ -1,5 +1,6 @@
import { prop, computed } from 'frptools'; 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 = { const CSS_DROPZONE = {
width: '200px', width: '200px',
@ -14,10 +15,13 @@ const CSS_DROPZONE_ACTIVE = {
}; };
export function Dropzone(vm, model) { 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); const enterCounter = prop(0);
enterCounter.subscribe(() => vm.redraw()); const class_ = computed(c => (c === 0 ? baseClassName : hoverClassName), [enterCounter]);
function onDragOver(evt) { function onDragOver(evt) {
// allows the browser to accept drops. // allows the browser to accept drops.
@ -50,16 +54,6 @@ export function Dropzone(vm, model) {
} }
return function render(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( return el(
'div', 'div',
{ {
@ -69,7 +63,7 @@ export function Dropzone(vm, model) {
ondragleave: onDragLeave, ondragleave: onDragLeave,
ondrop: onDrop ondrop: onDrop
}, },
...children children()
); );
}; };
} }

View File

@ -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 { ImageType } from '../data/image.js';
import { AlbumType } from '../data/album.js'; import { AlbumType } from '../data/album.js';
import { ThumbnailView } from './thumbnail.js'; import { ThumbnailView } from './thumbnail.js';
import { AlbumView } from './album.js'; import { AlbumView } from './album.js';
import { Dropzone } from './dropzone.js'; import { Dropzone } from './dropzone.js';
import { router, routeChanged } from '../services/router.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) { export function GalleryView(vm, model) {
const { db } = model;
const NAV_OPTIONS = { const NAV_OPTIONS = {
images: { images: {
data: ImageType.find( data: ImageType.find(
@ -27,7 +28,7 @@ export function GalleryView(vm, model) {
let data = null; let data = null;
let laCleanup = null; let laCleanup = null;
let title = ''; const title = prop('');
function uploadImages(files) { function uploadImages(files) {
Array.from(files).forEach(ImageType.upload); Array.from(files).forEach(ImageType.upload);
@ -53,15 +54,13 @@ export function GalleryView(vm, model) {
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(() => {
vm.redraw(); 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('images') }, 'Images'),
el('a', { href: router.href('albums') }, 'Albums'), el('a', { href: router.href('albums') }, 'Albums'),
el('h1', title), el('h1', title),
...(title === 'Images' ...(title() === 'Images'
? data().map(i => { ? data().map(i => {
return vw( return vw(
ThumbnailView, ThumbnailView,
{ {
doc: i, doc: i,
showTags: true,
remove: deleteImage remove: deleteImage
}, },
i._hash() i._hash()
@ -93,9 +91,7 @@ export function GalleryView(vm, model) {
return el('h1', 'Loading...'); return el('h1', 'Loading...');
} }
return el( return el('.gallery', { class: slate }, [
'.gallery',
{ class: slate },
header([ header([
el('div', { css: { fontSize: '20pt' } }, 'Gallery'), el('div', { css: { fontSize: '20pt' } }, 'Gallery'),
headerRight( headerRight(
@ -114,11 +110,11 @@ export function GalleryView(vm, model) {
type: 'file', type: 'file',
multiple: true, // FIXME - these don't carry through to the input tag multiple: true, // FIXME - these don't carry through to the input tag
accept: 'image/jpeg', accept: 'image/jpeg',
children: renderDropzone() children: renderDropzone
}, },
'dz' 'dz'
) )
); ]);
}; };
} }

View File

@ -2,6 +2,7 @@ import Styletron from 'styletron';
import { injectStyle as _injectStyle } from 'styletron-utils'; import { injectStyle as _injectStyle } from 'styletron-utils';
import { defineElement } from '../utils/domvm.js'; import { defineElement } from '../utils/domvm.js';
import { isObject } from '../utils/comparators.js'; import { isObject } from '../utils/comparators.js';
import { streamConfig } from '../utils/event.js';
const styletronSingleton = new Styletron(); const styletronSingleton = new Styletron();
@ -12,7 +13,11 @@ export function injectStyle(...styles) {
export function el(sig, ...attrsOrChildren) { export function el(sig, ...attrsOrChildren) {
let attrs = {}; let attrs = {};
let children = attrsOrChildren; let children = attrsOrChildren;
if (attrsOrChildren.length && isObject(attrsOrChildren[0])) { if (
attrsOrChildren.length &&
isObject(attrsOrChildren[0]) &&
!streamConfig.is(attrsOrChildren[0])
) {
attrs = attrsOrChildren[0]; attrs = attrsOrChildren[0];
children = attrsOrChildren.slice(1); children = attrsOrChildren.slice(1);
if (isObject(attrs.css)) { if (isObject(attrs.css)) {
@ -33,6 +38,9 @@ export function el(sig, ...attrsOrChildren) {
.join(' '); .join(' ');
} }
} }
if (children.length === 1 && streamConfig.is(attrsOrChildren[0])) {
children = children[0];
}
return defineElement(sig, attrs, children); return defineElement(sig, attrs, children);
} }

View File

@ -1,2 +1,2 @@
// export * from 'domvm/dist/dev/domvm.dev.js'; // export * from 'domvm/dist/dev/domvm.dev.js';
export * from 'domvm/dist/micro/domvm.micro.js'; export * from 'domvm/dist/mini/domvm.mini.js';

View File

@ -98,3 +98,10 @@ export function backgroundTask(fn, initialDelay = 500) {
return wrapper; return wrapper;
} }
export const streamConfig = {
is: s => s && typeof s.subscribe === 'function',
val: s => s(),
sub: (s, fn) => s.subscribe(fn),
unsub: s => s()
};