import { useEffect, useRef } from 'react'; import collection from './collection.json'; import { dimensions, fieldPosition, fieldCount, scatter, edgePosition, seed } from './layout.js'; import { sampleMotion, transition } from './motion.js'; import { relevanceAppearance } from './relevance.js'; import { imageURL, tileURL, shareURL } from './assets.js'; import { averageColor, createImageLoader, createPlaceholderLoader, thumbnailWidth } from './image-loader.js'; /** * z-index given to the tile the pointer is over. * * Clears every seeded value above (1 + floor(seed(id) * 100), so 1..100) and * every other z-index the field uses -- vignette 300, grain 301, search pill * 400, footer 401 -- so the lifted tile sits above its neighbours without * pushing the search box under the user's photographs. */ const LIFTED_Z = 200; /** A single population of photographs, one coordinate plane, one animation clock. */ export default function ImageField({ results, onSelect, paused, query = '', products = [] }) { const surface = useRef(null); const engine = useRef(null); const selection = useRef(onSelect); selection.current = onSelect; // The query and products are read inside the long-lived effect, so they are // mirrored into a ref rather than becoming effect dependencies. Making them // dependencies would tear down and rebuild every tile -- and re-request every // thumbnail -- on every keystroke. const controls = useRef({ query, products }); controls.current = { query, products }; useEffect(() => { const root = surface.current; const nodes = new Map(); const thumbnails = createImageLoader(); const placeholders = createPlaceholderLoader(); const motion = matchMedia('(prefers-reduced-motion: reduce)'); const state = { width: root.clientWidth, height: root.clientHeight, results: [], pool: [], paused: false, pointer: { x: 0, y: 0 }, x: 0, y: 0 }; let frame; let previous = performance.now(), time = 0; function create(image, target, initial = false) { const button = document.createElement('button'); // `featured` is set here, at creation, rather than toggled per frame like // `exact`: it comes from the record itself and does not change for a // given tile while it is on screen, so there is nothing to re-evaluate. button.className = image.featured ? 'image-tile featured' : 'image-tile'; button.type = 'button'; button.dataset.imageId = image.id; button.style.backgroundColor = averageColor(image.blur); /** * A share link on the tile itself, so a photograph can be sent without * opening it first. * * The tile stays a