import type { ElementType, ReactNode } from 'react'; import React, { ComponentProps, useCallback, useMemo, useState } from 'react'; import cx from 'classnames'; import escapeRegExp from 'lodash/escapeRegExp'; import type { Job, ReportMetricRow, WebpackChunk } from '@bundle-stats/utils'; import { COMPONENT, SECTIONS } from '@bundle-stats/utils'; import type { ReportMetricAssetRow, SortAction } from '../../types'; import config from '../../config.json'; import I18N from '../../i18n'; import { MetricsDisplayGroupBy, MetricsDisplayType } from '../../constants'; import { useMetricsDisplayType } from '../../hooks/metrics-display-type'; import { Box } from '../../layout/box'; import { FlexStack } from '../../layout/flex-stack'; import { Stack } from '../../layout/stack'; import { Dialog, useDialogState } from '../../ui/dialog'; import { InputSearch } from '../../ui/input-search'; import { Table } from '../../ui/table'; import { Filters } from '../../ui/filters'; import { EmptySet } from '../../ui/empty-set'; import { Toolbar } from '../../ui/toolbar'; import { AssetInfo } from '../asset-info'; import { AssetName } from '../asset-name'; import { ComponentLink } from '../component-link'; import { MetricsTable } from '../metrics-table'; import { MetricsTableExport } from '../metrics-table-export'; import { MetricsTableOptions } from '../metrics-table-options'; import { MetricsTableTitle } from '../metrics-table-title'; import { MetricsDisplaySelector } from '../metrics-display-selector'; import { MetricsTableHeader } from '../metrics-table-header'; import { MetricsTreemap, getTreemapNodes, getTreemapNodesGroupedByPath } from '../metrics-treemap'; import * as BUNDLE_ASSETS_I18N from './bundle-assets.i18n'; import { getFilters } from './bundle-assets.utils'; import css from './bundle-assets.module.css'; const DISPLAY_TYPE_GROUPS = { [MetricsDisplayType.TREEMAP]: [MetricsDisplayGroupBy.FOLDER], }; interface ViewMetricsTreemapProps { metricsTableTitle: ReactNode; jobs: Array; items: Array; displayType: { value: MetricsDisplayType; groupBy?: string }; emptyMessage: ReactNode; showEntryInfo: (entryId: string) => void; updateSearch: (search: string) => void; search: string; } const ViewMetricsTreemap = (props: ViewMetricsTreemapProps) => { const { metricsTableTitle, jobs, items, displayType, emptyMessage, showEntryInfo, updateSearch, search, } = props; // Get treenodes based on group const treeNodes = useMemo(() => { if (displayType.groupBy === 'folder') { return getTreemapNodesGroupedByPath(items); } return getTreemapNodes(items); }, [items, displayType.groupBy]); // Search based on the group path on group title click const onGroupClick = useCallback( (groupPath: string) => { // Clear seach when groupPath is emty (root) if (groupPath === '') { updateSearch(''); return; } // Search by group path // 1. use `^` to match only the string beggining // 2. add `/` suffix to exactly match the directory const newSearch = `^${escapeRegExp(groupPath)}/`; // Reset search when toggling the same groupPath if (newSearch === search) { updateSearch(''); return; } updateSearch(newSearch); }, [updateSearch, search], ); return ( <>
); }; export interface BundleAssetsProps extends ComponentProps { jobs: Array; chunks: Array; items: Array; allItems: Array; filters: Record; updateFilters: (newFilters: Record) => void; resetFilters: () => void; resetAllFilters: () => void; totalRowCount?: number; entryId?: string; sort: SortAction; updateSort: (params: SortAction) => void; search: string; updateSearch: (search: string) => void; customComponentLink?: ElementType; hideEntryInfo: () => void; showEntryInfo: (entryId: string) => void; } export const BundleAssets = (props: BundleAssetsProps) => { const { className = '', jobs, chunks, items, allItems, updateFilters, resetFilters, resetAllFilters, totalRowCount = 0, filters, entryId = '', sort, updateSort, search, updateSearch, customComponentLink: CustomComponentLink = ComponentLink, hideEntryInfo, showEntryInfo, } = props; const jobLabels = jobs?.map((job) => job?.label); const [displayType, setDisplayType] = useMetricsDisplayType(DISPLAY_TYPE_GROUPS); const filterFieldsData = useMemo( () => getFilters({ compareMode: jobs?.length > 1, filters, chunks }), [jobs, filters, chunks], ); const metricsTableTitle = useMemo( () => ( ), [items, totalRowCount], ); const EntryComponentLink = useCallback( ({ entryId: assetEntryId, ...assetNameRestProps }: { entryId: string } & ComponentProps) => ( ), [CustomComponentLink, filters, search, sort], ); const renderRowHeader = useCallback( (row: ReportMetricRow) => ( ), [EntryComponentLink], ); const emptyMessage = useMemo( () => ( ), [totalRowCount, resetFilters, resetAllFilters], ); const entryItem = useMemo(() => { if (!entryId) { return null; } return allItems.find(({ key }) => key === entryId); }, [allItems, entryId]); const exportDialog = useDialogState(); const [exportSourceType, setExportSourceType] = useState(); const handleExportClick = useCallback( (sourceType: string) => { setExportSourceType(sourceType); exportDialog.toggle(); }, [exportDialog, setExportSourceType], ); return ( <> ( )} > {displayType.value === MetricsDisplayType.TABLE && ( )} {displayType.value === MetricsDisplayType.TREEMAP && ( )} {entryItem && ( )} {exportDialog.open && ( )} ); };