/** @jsx h */ import { h, render } from 'preact'; import { cx } from '@algolia/ui-components-shared'; import type { StatsComponentCSSClasses, StatsComponentTemplates, } from '../../components/Stats/Stats'; import Stats from '../../components/Stats/Stats'; import type { StatsConnectorParams, StatsRenderState, StatsWidgetDescription, } from '../../connectors/stats/connectStats'; import connectStats from '../../connectors/stats/connectStats'; import { getContainerNode, createDocumentationMessageGenerator, } from '../../lib/utils'; import { prepareTemplateProps } from '../../lib/templating'; import { component } from '../../lib/suit'; import type { Renderer, Template, WidgetFactory } from '../../types'; import type { PreparedTemplateProps } from '../../lib/templating'; import { formatNumber } from '../../lib/formatNumber'; const withUsage = createDocumentationMessageGenerator({ name: 'stats' }); const suit = component('Stats'); type TextTemplateProps = { hasManyResults: boolean; hasNoResults: boolean; hasOneResult: boolean; hasNoSortedResults: boolean; hasOneSortedResults: boolean; hasManySortedResults: boolean; }; export type StatsCSSClasses = Partial<{ /** * CSS class to add to the root element. */ root: string | string[]; /** * CSS class to add to the text span element. */ text: string | string[]; }>; export type StatsTemplates = Partial<{ /** * Text template, provided with `hasManyResults`, `hasNoResults`, `hasOneResult`, `hasNoSortedResults`, `hasOneSortedResults`, `hasManySortedResults`, `hitsPerPage`, `nbHits`, `nbSortedHits`, `nbPages`, `areHitsSorted`, `page`, `processingTimeMS`, `query`. */ text: Template; }>; export type StatsWidgetParams = { /** * CSS Selector or HTMLElement to insert the widget. */ container: string | HTMLElement; /** * Templates to use for the widget. */ templates?: StatsTemplates; /** * CSS classes to add. */ cssClasses?: StatsCSSClasses; }; export type StatsWidget = WidgetFactory< StatsWidgetDescription & { $$widgetType: 'ais.stats' }, StatsConnectorParams, StatsWidgetParams >; export const defaultTemplates: StatsComponentTemplates = { text(props) { return `${ props.areHitsSorted ? getSortedResultsSentence(props) : getResultsSentence(props) } found in ${props.processingTimeMS}ms`; }, }; function getSortedResultsSentence({ nbHits, hasNoSortedResults, hasOneSortedResults, hasManySortedResults, nbSortedHits, }: TextTemplateProps & StatsRenderState) { const suffix = `sorted out of ${formatNumber(nbHits)}`; if (hasNoSortedResults) { return `No relevant results ${suffix}`; } if (hasOneSortedResults) { return `1 relevant result ${suffix}`; } if (hasManySortedResults) { return `${formatNumber(nbSortedHits || 0)} relevant results ${suffix}`; } return ''; } function getResultsSentence({ nbHits, hasNoResults, hasOneResult, hasManyResults, }: TextTemplateProps & StatsRenderState) { if (hasNoResults) { return 'No results'; } if (hasOneResult) { return '1 result'; } if (hasManyResults) { return `${formatNumber(nbHits)} results`; } return ''; } const renderer = ({ renderState, cssClasses, containerNode, templates, }: { renderState: { templateProps?: PreparedTemplateProps; }; cssClasses: StatsComponentCSSClasses; containerNode: HTMLElement; templates: StatsTemplates; }): Renderer> => ( { hitsPerPage, nbHits, nbSortedHits, areHitsSorted, nbPages, page, processingTimeMS, query, instantSearchInstance, }, isFirstRendering ) => { if (isFirstRendering) { renderState.templateProps = prepareTemplateProps({ defaultTemplates, templatesConfig: instantSearchInstance.templatesConfig, templates, }); return; } render( , containerNode ); }; /** * The `stats` widget is used to display useful insights about the current results. * * By default, it will display the **number of hits** and the time taken to compute the * results inside the engine. */ const stats: StatsWidget = (widgetParams) => { const { container, cssClasses: userCssClasses = {}, templates = {}, } = widgetParams || {}; if (!container) { throw new Error(withUsage('The `container` option is required.')); } const containerNode = getContainerNode(container); const cssClasses: StatsComponentCSSClasses = { root: cx(suit(), userCssClasses.root), text: cx(suit({ descendantName: 'text' }), userCssClasses.text), }; const specializedRenderer = renderer({ containerNode, cssClasses, templates, renderState: {}, }); const makeWidget = connectStats(specializedRenderer, () => render(null, containerNode) ); return { ...makeWidget({}), $$widgetType: 'ais.stats', }; }; export default stats;