appbar should be dumber
There's no need to have one object cross views.
This commit is contained in:
parent
27552dfb21
commit
bc4b78103c
@ -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,
|
||||
|
||||
@ -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'
|
||||
});
|
||||
|
||||
@ -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
|
||||
);
|
||||
|
||||
@ -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()
|
||||
)
|
||||
|
||||
@ -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()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user