Next.js website for Rocky Mountain Vending company featuring: - Product catalog with Stripe integration - Service areas and parts pages - Admin dashboard with Clerk authentication - SEO optimized pages with JSON-LD structured data Co-authored-by: Cursor <cursoragent@cursor.com>
124 lines
3.1 KiB
Text
124 lines
3.1 KiB
Text
/**
|
|
* @license
|
|
* Copyright 2021 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import {FunctionComponent} from 'preact';
|
|
import {useEffect, useState} from 'preact/hooks';
|
|
|
|
import {NavigationIcon, SnapshotIcon, TimespanIcon} from './icons';
|
|
import {getFilmstripFrames, getScreenDimensions} from './util';
|
|
import {Util} from '../../shared/util.js';
|
|
|
|
const ANIMATION_FRAME_DURATION_MS = 500;
|
|
|
|
const Separator: FunctionComponent = () => {
|
|
return <div className="Separator" role="separator"></div>;
|
|
};
|
|
|
|
const FlowStepIcon: FunctionComponent<{mode: LH.Result.GatherMode}> = ({mode}) => {
|
|
return <>
|
|
{
|
|
mode === 'navigation' && <NavigationIcon/>
|
|
}
|
|
{
|
|
mode === 'timespan' && <TimespanIcon/>
|
|
}
|
|
{
|
|
mode === 'snapshot' && <SnapshotIcon/>
|
|
}
|
|
</>;
|
|
};
|
|
|
|
const FlowSegment: FunctionComponent<{mode?: LH.Result.GatherMode}> = ({mode}) => {
|
|
return (
|
|
<div className="FlowSegment">
|
|
<div className="FlowSegment__top-line"/>
|
|
{
|
|
mode && <FlowStepIcon mode={mode}/>
|
|
}
|
|
<div className="FlowSegment__bottom-line"/>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const FlowStepAnimatedThumbnail: FunctionComponent<{
|
|
frames: Array<{data: string}>,
|
|
width: number,
|
|
height: number,
|
|
}> = ({frames, width, height}) => {
|
|
const [frameIndex, setFrameIndex] = useState(0);
|
|
// Handle a frame array of a different length being set.
|
|
const effectiveFrameIndex = frameIndex % frames.length;
|
|
|
|
useEffect(() => {
|
|
const interval = setInterval(
|
|
() => setFrameIndex(i => (i + 1) % frames.length),
|
|
ANIMATION_FRAME_DURATION_MS
|
|
);
|
|
|
|
return () => clearInterval(interval);
|
|
}, [frames.length]);
|
|
|
|
return (
|
|
<img
|
|
className="FlowStepThumbnail"
|
|
data-testid="FlowStepAnimatedThumbnail"
|
|
src={frames[effectiveFrameIndex].data}
|
|
style={{width, height}}
|
|
alt="Animated screenshots of a page tested by Lighthouse"
|
|
/>
|
|
);
|
|
};
|
|
|
|
const FlowStepThumbnail: FunctionComponent<{
|
|
lhr: LH.Result,
|
|
width?: number,
|
|
height?: number,
|
|
}> = ({lhr, width, height}) => {
|
|
const frames = getFilmstripFrames(lhr);
|
|
|
|
// Resize the image to fit the viewport aspect ratio.
|
|
const dimensions = getScreenDimensions(lhr);
|
|
if (width && height === undefined) {
|
|
height = dimensions.height * width / dimensions.width;
|
|
} else if (height && width === undefined) {
|
|
width = dimensions.width * height / dimensions.height;
|
|
}
|
|
|
|
if (!width || !height) {
|
|
// eslint-disable-next-line no-console
|
|
console.warn(new Error('FlowStepThumbnail requested without any dimensions').stack);
|
|
return <></>;
|
|
}
|
|
|
|
let thumbnail;
|
|
if (frames?.length) {
|
|
thumbnail = frames[frames.length - 1].data;
|
|
if (lhr.gatherMode === 'timespan') {
|
|
return <FlowStepAnimatedThumbnail frames={frames} width={width} height={height} />;
|
|
}
|
|
} else {
|
|
thumbnail = Util.getFullPageScreenshot(lhr)?.screenshot.data;
|
|
}
|
|
|
|
return <>
|
|
{
|
|
thumbnail &&
|
|
<img
|
|
className="FlowStepThumbnail"
|
|
src={thumbnail}
|
|
style={{width, height}}
|
|
alt="Screenshot of a page tested by Lighthouse"
|
|
/>
|
|
}
|
|
</>;
|
|
};
|
|
|
|
export {
|
|
Separator,
|
|
FlowStepIcon,
|
|
FlowSegment,
|
|
FlowStepThumbnail,
|
|
};
|