142 lines
3.6 KiB
JavaScript
142 lines
3.6 KiB
JavaScript
import partition from 'linear-partitioning';
|
|
|
|
import {
|
|
defineView as vw,
|
|
defineElement as el,
|
|
patchRefStyle,
|
|
patchNodeStyle,
|
|
subscribeToRender,
|
|
availableViewportSize
|
|
} from '../utils/domvm.js';
|
|
import { injectStyle, styled } from '../services/style.js';
|
|
import { DEFAULT_TRANSITION, CLICKABLE, IMAGE_MARGIN, CONTENT_MARGIN } from './styles.js';
|
|
import { Icon } from './components/icon.js';
|
|
import { SectionPhoto } from './sectionPhoto.js';
|
|
import { extractID } from '../utils/conversion.js';
|
|
|
|
const OPTIMAL_IMAGE_HEIGHT = 140;
|
|
const ROW_HEIGHT_CUTOFF_MODIFIER = 2;
|
|
|
|
const IMAGE_MARGIN_WIDTH = 2 * IMAGE_MARGIN;
|
|
const CONTENT_MARGIN_WIDTH = 2 * CONTENT_MARGIN;
|
|
|
|
const aspectRatio = img => img.width / img.height;
|
|
|
|
export function SectionView(vm, params, key, context) {
|
|
const { appbar } = context;
|
|
const { title, photos } = params;
|
|
const sectionSelectButtonRef = `secSel${key}`;
|
|
|
|
function calculateSections(photos) {
|
|
const { width: vw } = availableViewportSize();
|
|
const availableWidth = vw - CONTENT_MARGIN_WIDTH;
|
|
const totalImageRatio = photos.reduce((acc, img) => acc + aspectRatio(img), 0);
|
|
const rowCount = Math.ceil(totalImageRatio * OPTIMAL_IMAGE_HEIGHT / availableWidth);
|
|
const rowRatios = partition(photos.map(aspectRatio), rowCount);
|
|
|
|
let index = 0;
|
|
|
|
const result = rowRatios.map(row => {
|
|
const rowTotal = row.reduce((acc, r) => acc + r, 0);
|
|
const imageRatio = row[0];
|
|
const portion = imageRatio / rowTotal;
|
|
let rowHeight = availableWidth * portion / aspectRatio(photos[index]);
|
|
if (rowHeight > OPTIMAL_IMAGE_HEIGHT * ROW_HEIGHT_CUTOFF_MODIFIER) {
|
|
rowHeight = OPTIMAL_IMAGE_HEIGHT * ROW_HEIGHT_CUTOFF_MODIFIER;
|
|
}
|
|
|
|
const rowResult = row.map((imageRatio, imgIndex) => ({
|
|
photo: photos[imgIndex + index],
|
|
width: imageRatio * rowHeight - IMAGE_MARGIN_WIDTH,
|
|
height: rowHeight
|
|
}));
|
|
|
|
index += row.length;
|
|
return rowResult;
|
|
});
|
|
return result;
|
|
}
|
|
|
|
subscribeToRender(vm, [availableViewportSize]);
|
|
|
|
return function render(vm, params) {
|
|
const { selectedIds, selectMode } = params;
|
|
|
|
function photoTemplate({ photo, width, height }) {
|
|
return vw(
|
|
SectionPhoto,
|
|
{
|
|
doc: photo,
|
|
isSelected: selectedIds.has(photo._id),
|
|
selectMode,
|
|
width,
|
|
height
|
|
},
|
|
photo._hash(),
|
|
context
|
|
);
|
|
}
|
|
|
|
function sectionRowTemplate(photos) {
|
|
return sectionRow(photos.map(photoTemplate));
|
|
}
|
|
|
|
return sectionContainer(
|
|
{
|
|
class: 'section',
|
|
onmouseenter: [patchRefStyle, sectionSelectButtonRef, 'opacity: 0.7;'],
|
|
onmouseleave: [patchRefStyle, sectionSelectButtonRef, 'opacity: 0;'],
|
|
_data: {
|
|
type: 'section',
|
|
sectionImageIds: photos.map(extractID)
|
|
}
|
|
},
|
|
[
|
|
sectionTitle([
|
|
title,
|
|
sectionSelectButton(
|
|
{
|
|
class: 'sectionSelectButton',
|
|
_ref: sectionSelectButtonRef,
|
|
onmouseenter: [patchNodeStyle, 'opacity: 1;'],
|
|
onmouseleave: [patchNodeStyle, 'opacity: 0.7;'],
|
|
css: {
|
|
opacity: selectMode ? 0.7 : 0
|
|
}
|
|
},
|
|
[Icon({ name: 'check_circle', size: 0.25 })]
|
|
)
|
|
]),
|
|
sectionContent(calculateSections(photos).map(sectionRowTemplate))
|
|
]
|
|
);
|
|
};
|
|
}
|
|
|
|
const sectionContainer = styled({
|
|
margin: `${CONTENT_MARGIN}px`,
|
|
flexDirection: 'column'
|
|
});
|
|
|
|
const sectionTitle = styled({
|
|
display: 'flex',
|
|
alignItems: 'center'
|
|
});
|
|
|
|
const sectionContent = styled({
|
|
display: 'flex',
|
|
alignItems: 'flex-start',
|
|
flexDirection: 'column'
|
|
});
|
|
|
|
const sectionRow = styled({
|
|
flexDirection: 'row',
|
|
flex: 1,
|
|
userSelect: 'none'
|
|
});
|
|
|
|
const sectionSelectButton = styled(DEFAULT_TRANSITION, CLICKABLE, {
|
|
paddingLeft: '0.5em',
|
|
opacity: 0
|
|
});
|