From bc4b78103c841274e85a782c9f8755798ed3c819 Mon Sep 17 00:00:00 2001 From: Timothy Farrell Date: Fri, 5 Jan 2018 15:55:12 -0600 Subject: [PATCH] appbar should be dumber There's no need to have one object cross views. --- packages/gallery/src/interface/allImages.js | 155 +++++++++--------- .../src/interface/components/appbar.js | 116 ++++--------- packages/gallery/src/interface/focus.js | 90 +++++----- packages/gallery/src/interface/gallery.js | 8 +- packages/gallery/src/interface/sectionView.js | 6 +- 5 files changed, 153 insertions(+), 222 deletions(-) diff --git a/packages/gallery/src/interface/allImages.js b/packages/gallery/src/interface/allImages.js index b78252d..2224bed 100644 --- a/packages/gallery/src/interface/allImages.js +++ b/packages/gallery/src/interface/allImages.js @@ -15,6 +15,7 @@ import { ImageType } from '../data/image.js'; import { pouchDocArrayHash, pouchDocHash, hashSet, extractID } from '../utils/conversion.js'; import { SectionView } from './sectionView.js'; import { Icon } from './components/icon.js'; +import { AppBar } from './components/appbar.js'; import { injectStyle, styled } from '../services/style.js'; import { CLICKABLE, FILL_STYLE } from './styles.js'; @@ -26,15 +27,68 @@ export function uploadImages(evt, files) { } } -export function AllImagesView(vm, params, key, context) { - const { appbar, appbarView } = context; +export function AllImagesView(vm, params) { const model = prop({}, pouchDocHash); const images = container([], pouchDocArrayHash); + const containerScrollTop = prop(0); const selectedIds = container(new Set(), hashSet); - const appBarTitle = computed(s => (s.size > 0 ? `${s.size} selected` : 'Photos'), [selectedIds]); const selectMode = computed(sIds => sIds.size > 0, [selectedIds]); + const appBarTitle = computed(s => (s.size > 0 ? `${s.size} selected` : 'Photos'), [selectedIds]); + const appBarStyle = computed( + t => ({ + boxShadow: t === 0 ? 'none' : `0px 3px 3px rgba(0, 0, 0, .2)` + }), + [containerScrollTop] + ); + const appBarUp = computed(s => (s ? { name: 'x', action: deSelect } : undefined), [selectMode]); + const appBarActions = computed( + s => + s + ? [ + trashButtonContainer( + { + onclick: deleteSelectedImages + }, + [ + Icon({ + name: 'trash', + size: 0.75 + }) + ] + ) + ] + : [ + uploadButton([ + el( + 'label', + { + for: 'uploadButton', + style: CLICKABLE + }, + [ + Icon({ + name: 'upload', + size: 0.75, + title: 'Upload' + }) + ] + ) + ]), + el('input', { + id: 'uploadButton', + name: 'uploadButton', + type: 'file', + multiple: true, + accept: '.jpg,.jpeg,.png,.gif', + onchange: uploadImages, + class: injectStyle({ display: 'none' }) + }) + ], + [selectMode] + ); + const sections = computed( imageArr => { const sectionMap = imageArr.reduce((acc, i) => { @@ -52,50 +106,8 @@ export function AllImagesView(vm, params, key, context) { [images] ); - function renderAppBarButtons() { - if (selectMode()) { - return [ - trashButtonContainer( - { - onclick: deleteSelectedImages - }, - [ - Icon({ - name: 'trash', - size: 0.75 - }) - ] - ) - ]; - } - - return [ - uploadButton([ - el( - 'label', - { - for: 'uploadButton', - style: CLICKABLE - }, - [ - Icon({ - name: 'upload', - size: 0.75, - title: 'Upload' - }) - ] - ) - ]), - el('input', { - id: 'uploadButton', - name: 'uploadButton', - type: 'file', - multiple: true, - accept: '.jpg,.jpeg,.png,.gif', - onchange: uploadImages, - class: injectStyle({ display: 'none' }) - }) - ]; + function deSelect() { + selectedIds.clear(); } function deleteSelectedImages() { @@ -146,26 +158,7 @@ export function AllImagesView(vm, params, key, context) { } function handleContentScroll(evt) { - appbar.companionScrollTop(evt.target.scrollTop); - } - - function pushAppBarState() { - const up = selectMode() - ? { - name: 'x', - onclick: () => selectedIds.clear() - } - : undefined; - - appbar.pushState({ - title: appBarTitle, - actions: renderAppBarButtons, - up - }); - } - - function popAppBarState() { - appbar.popState(); + containerScrollTop(evt.target.scrollTop); } ImageType.find( @@ -174,17 +167,13 @@ export function AllImagesView(vm, params, key, context) { }, { live: true } ).then(la => { - pushAppBarState(); - selectMode.subscribe(mode => { - popAppBarState(); - pushAppBarState(); - }), - subscribeToRender(vm, [ - selectedIds, - images, - selectMode, - () => la.subscribe(res => images.splice(0, images.length, ...res)) - ]); + subscribeToRender(vm, [ + selectedIds, + images, + selectMode, + appBarStyle, + () => la.subscribe(res => images.splice(0, images.length, ...res)) + ]); }); function renderSection({ title, sectionId, images: _images }) { @@ -196,8 +185,7 @@ export function AllImagesView(vm, params, key, context) { selectedIds, selectMode: selectMode() }, - sectionId, - context + sectionId ); } @@ -207,7 +195,12 @@ export function AllImagesView(vm, params, key, context) { class: 'allImages' }, [ - iv(appbarView), + AppBar({ + style: appBarStyle(), + title: appBarTitle(), + actions: appBarActions(), + up: appBarUp() + }), allImagesContent( { onscroll: handleContentScroll, diff --git a/packages/gallery/src/interface/components/appbar.js b/packages/gallery/src/interface/components/appbar.js index de63ef6..60fa4c4 100644 --- a/packages/gallery/src/interface/components/appbar.js +++ b/packages/gallery/src/interface/components/appbar.js @@ -1,92 +1,46 @@ import { prop, computed, container, pick } from 'frptools'; import { Icon } from './icon.js'; -import { router } from '../../services/router.js'; -import { defineElement as el, subscribeToRender } from '../../utils/domvm.js'; +import { defineElement as el } from '../../utils/domvm.js'; import { injectStyle, styled } from '../../services/style.js'; import { CLICKABLE } from '../styles.js'; -let seq = 0; +export function AppBar(params) { + const { title, up, actions } = params; + const props = Object.assign({}, params); -export function AppBarView(vm, params, key, opts) { - const stateStack = container([], arr => arr.length && arr[0]._seq); - const companionScrollTop = prop(0); + delete props.title; + delete props.up; + delete props.actions; - const currentState = computed(stack => stack[0] || {}, [stateStack]); - const title = computed(pick('title', ''), [currentState]); - const renderActions = computed(pick('actions'), [currentState]); - const up = computed(pick('up'), [currentState]); - const upButton = computed(pick('name', 'arrow_left'), [up]); - const upAction = computed( - upState => (upState.onclick ? upState.onclick : [popState, upState.navigateTo]), - [up] - ); - const stateStyle = computed(pick('style', {}), [currentState]); + const upProps = Object.assign({}, up || {}); + const { button, action } = upProps; - const boxShadowStyle = computed(t => (t === 0 ? 'none' : `0px 3px 3px rgba(0, 0, 0, .2)`), [ - companionScrollTop + delete upProps.button; + delete upProps.action; + + return appBarContainer(props, [ + up + ? upButtonContainer( + { + onclick: up.action + }, + [ + Icon( + Object.assign( + { + name: up.button || 'arrow_left', + size: 0.75 + }, + upProps + ) + ) + ] + ) + : null, + titleContainer(title), + actionContainer(actions) ]); - - const containerStyle = computed( - (boxShadow, style) => ({ - css: Object.assign({ boxShadow }, style) - }), - [boxShadowStyle, stateStyle] - ); - - if (opts.appbar) { - throw new Error('Cannot have more than one AppBar.'); - } - - function pushState(newState) { - companionScrollTop(0); - stateStack.unshift(Object.assign({ _seq: seq++ }, newState)); - } - - function popState(navigateTo) { - companionScrollTop(0); - stateStack.shift(); - if (navigateTo) { - router.goto(navigateTo); - } - } - - function replaceState(newState) { - companionScrollTop(0); - stateStack._.shift(); - stateStack.unshift(Object.assign({ _seq: seq++ }, newState)); - } - - opts.appbar = { - pushState, - popState, - replaceState, - companionScrollTop - }; - - subscribeToRender(vm, [containerStyle, renderActions, up, title]); - - return (vm, params) => { - const _buttons = renderActions() || (() => {}); - - return appBarContainer(containerStyle(), [ - up() - ? upButtonContainer( - { - onclick: upAction() - }, - [ - Icon({ - name: upButton(), - size: 0.75 - }) - ] - ) - : null, - titleContainer(title()), - headerRight(_buttons()) - ]); - }; } const appBarContainer = styled({ @@ -96,7 +50,7 @@ const appBarContainer = styled({ display: 'flex', alignItems: 'center', width: '100%', - transition: 'opacity .13s cubic-bezier(0.0,0.0,0.2,1)' + transition: 'opacity .5s cubic-bezier(0.0,0.0,0.2,1)' }); const upButtonContainer = styled( @@ -106,7 +60,7 @@ const upButtonContainer = styled( CLICKABLE ); -const headerRight = styled({ +const actionContainer = styled({ display: 'flex', alignItems: 'center' }); diff --git a/packages/gallery/src/interface/focus.js b/packages/gallery/src/interface/focus.js index 2023464..dbc5b0a 100644 --- a/packages/gallery/src/interface/focus.js +++ b/packages/gallery/src/interface/focus.js @@ -15,19 +15,28 @@ import { pouchDocHash, pick } from '../utils/conversion.js'; import { AttachmentImageView } from './components/attachmentImage.js'; import { Overlay } from './components/overlay.js'; import { Icon } from './components/icon.js'; +import { AppBar } from './components/appbar.js'; import { styled, injectStyle } from '../services/style.js'; import { error } from '../services/console.js'; import { CLICKABLE, FILL_STYLE } from './styles.js'; -export function FocusView(vm, params, key, { appbar, appbarView }) { +export function FocusView(vm, params) { const id = prop(); const doc = prop({}, pouchDocHash); - const { body } = document; const nextLink = prop(); const prevLink = prop(); const mouseActive = prop(true); let mouseMoveTimeout = null; + const appBarStyle = computed( + mA => ({ + position: 'fixed', + opacity: mA ? 1 : 0, + backgroundImage: 'linear-gradient(0deg,rgba(0,0,0,0),rgba(0,0,0,0.4))' + }), + [mouseActive], + s => s.opacity + ); const imageStyle = computed( ({ width: iw, height: ih }, { width: vw, height: vh }) => { const imageRatio = iw / ih; @@ -51,20 +60,9 @@ export function FocusView(vm, params, key, { appbar, appbarView }) { }, [doc, fullViewportSize] ); - const appbarState = computed( - mA => ({ - title: '', - actions: renderAppBarButtons, - style: { position: 'fixed', opacity: mA ? 1 : 0 }, - up: { - navigateTo: 'home' - } - }), - [mouseActive] - ); function navBack() { - appbar.popState('home'); + router.goto('home'); } async function clickTrash() { @@ -93,35 +91,6 @@ export function FocusView(vm, params, key, { appbar, appbarView }) { mouseActive(!mouseActive()); }; - function renderAppBarButtons() { - return [ - trashButtonContainer( - { - onclick: clickTrash - }, - [ - Icon({ - name: 'trash', - size: 0.75 - }) - ] - ) - ]; - } - - // Set the appbar title. - - appbar.pushState({ - title: '', - actions: renderAppBarButtons, - style: { - position: 'fixed' - }, - up: { - navigateTo: 'home' - } - }); - // Subscribe to our changables. subscribeToRender( vm, @@ -129,15 +98,14 @@ export function FocusView(vm, params, key, { appbar, appbarView }) { doc, nextLink, prevLink, - () => appbarState.subscribe(appbar.replaceState), + appBarStyle, // Look for our image and set it. () => id.subscribe(async _id => { if (!_id) { return; } - const image = await ImageType.find(_id); - doc(image); + doc(await ImageType.find(_id)); Promise.all([ ImageType.find({ _id: { $lt: _id } }, { limit: 2, sort: [{ _id: 'desc' }] }), @@ -156,6 +124,7 @@ export function FocusView(vm, params, key, { appbar, appbarView }) { // Start navigation id(params.vars.id); + mouseMove(); return function() { const _id = doc() && doc()._id; @@ -172,13 +141,32 @@ export function FocusView(vm, params, key, { appbar, appbarView }) { onclick: mouseClick }, [ - iv(appbarView), + AppBar({ + style: appBarStyle(), + title: '', + up: { action: navBack, fill: 'white' }, + actions: [ + trashButtonContainer( + { + onclick: clickTrash + }, + [ + Icon({ + name: 'trash', + size: 0.75, + fill: 'white' + }) + ] + ) + ] + }), focusContent([ prevLink() ? prevClickZone({ href: prevLink() }, [ Icon({ name: 'chevron_left', - size: 0.75 + size: 0.75, + fill: 'white' }) ]) : null, @@ -190,7 +178,8 @@ export function FocusView(vm, params, key, { appbar, appbarView }) { ? nextClickZone({ href: nextLink() }, [ Icon({ name: 'chevron_right', - size: 0.75 + size: 0.75, + fill: 'white' }) ]) : null @@ -230,7 +219,8 @@ const focusContainer = styled( display: 'flex', overflow: 'hidden', flexDirection: 'column', - alignItems: 'center' + alignItems: 'center', + backgroundColor: 'black' }, FILL_STYLE ); diff --git a/packages/gallery/src/interface/gallery.js b/packages/gallery/src/interface/gallery.js index 4a151a6..3675813 100644 --- a/packages/gallery/src/interface/gallery.js +++ b/packages/gallery/src/interface/gallery.js @@ -13,19 +13,15 @@ import { AllImagesView, uploadImages } from './allImages.js'; import { FocusView } from './focus.js'; import { Dropzone } from './components/dropzone.js'; import { Overlay } from './components/overlay.js'; -import { AppBarView } from './components/appbar.js'; import { Icon } from './components/icon.js'; import { routeChanged } from '../services/router.js'; import { injectStyle, styled } from '../services/style.js'; import { FILL_STYLE } from './styles.js'; export function GalleryView(vm) { - const context = {}; const routeName = prop(); const routeParams = prop(); - context.appbarView = cv(AppBarView, {}, 'appbar', context); - routeChanged.subscribe(function onRouteChange(name, params) { routeName(name); routeParams(params); @@ -37,8 +33,8 @@ export function GalleryView(vm) { content([ renderSwitch( { - photos: [AllImagesView, {}, 'allImages', context], - focus: [FocusView, routeParams(), `focus_${routeParams() && routeParams().id}`, context] + photos: [AllImagesView, {}, 'allImages'], + focus: [FocusView, routeParams(), `focus_${routeParams() && routeParams().id}`] }, routeName() ) diff --git a/packages/gallery/src/interface/sectionView.js b/packages/gallery/src/interface/sectionView.js index 805c716..d790669 100644 --- a/packages/gallery/src/interface/sectionView.js +++ b/packages/gallery/src/interface/sectionView.js @@ -22,8 +22,7 @@ const CONTENT_MARGIN_WIDTH = 2 * CONTENT_MARGIN; const aspectRatio = img => img.width / img.height; -export function SectionView(vm, params, key, context) { - const { appbar } = context; +export function SectionView(vm, params, key) { const { title, photos } = params; const sectionSelectButtonRef = `secSel${key}`; @@ -72,8 +71,7 @@ export function SectionView(vm, params, key, context) { width, height }, - photo._hash(), - context + photo._hash() ); }