Use streams for a little better memory reuse.
This commit is contained in:
parent
be0f613ce0
commit
a86974f08a
@ -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);
|
||||||
|
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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: {
|
||||||
|
|||||||
@ -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()
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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'
|
||||||
)
|
)
|
||||||
);
|
]);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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';
|
||||||
|
|||||||
@ -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()
|
||||||
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user