Untitled
import Tab from '@mui/material/Tab'; import React, { useEffect, useMemo, useRef, useState } from 'react'; import { TabContext, TabList, TabPanel, TabPanelProps } from '@mui/lab'; import { styled, useTheme } from '@mui/material/styles'; import ItemTextViewer from './ItemTextViewer'; import ItemNativeViewer from './ItemNativeViewer'; import FooterSpacer from '../common/FooterSpacer'; import ItemPdfViewer from './ItemPdfViewer'; import ItemApi from '../../api/ItemApi'; import { handleError } from '../../utils/errorUtils'; import CONSTANTS from '../common/constants'; import FaintInformational from '../common/FaintInformational'; import Informational from '../common/Informational'; import { Box } from '@mui/material'; import { useItemsContext } from '../item/context/ItemsContext'; import { getViewerTypeForItemDefinitionId } from './ItemViewerUtils'; import { useCasesAppContext } from '../case/CasesAppContext'; import { useResizeObserver } from '../../hooks'; import { useMutation, useQuery } from '@tanstack/react-query'; export type ItemArtifact = { id: number; itemId: number; artifactType: string; extension: string; path: string; }; type ItemViewerProps = { itemId: number; itemDefinitionId: number; isReOrientatedHorizontally: boolean; }; const ItemViewer: React.FC<ItemViewerProps> = ({ itemId, itemDefinitionId, isReOrientatedHorizontally, }) => { const { caseId } = useCasesAppContext(); const { isPending, data, error } = useItemArtifacts(itemId, caseId); if (error) { handleError( error, 'Failed to retrieve item artifacts types, defaulting to Original', ); } const itemArtifacts = (data ?? []) as ItemArtifact[]; const hasArtifacts = itemArtifacts.length > 0; const hasMarkupArtifact = itemArtifacts.some( ({ artifactType }) => artifactType === CONSTANTS.ARTIFACT_TYPE.MARKUP, ); const artifactsToTabs = useMemo( () => itemArtifacts.map((artifact) => artifact.artifactType === CONSTANTS.ARTIFACT_TYPE.WEB_NATIVE ? CONSTANTS.ITEM_VIEWER_TAB_TYPE.ORIGINAL : (artifact.artifactType as TabType), ), [itemArtifacts], ); const { itemDefinitions, itemKinds } = useItemsContext(); const [containerRef, scrollButtons] = useScrollButtonsOnResize(); const [preferredTab, setPreferredTab] = useState<TabType | null>(null); const handleTabChange = (_: React.SyntheticEvent, newValue: TabType) => { setPreferredTab(newValue); }; const activeTab = useMemo(() => { // Allow users to create Markup on the fly if it doesn't exits if ( (preferredTab && artifactsToTabs.includes(preferredTab)) || preferredTab === CONSTANTS.ITEM_VIEWER_TAB_TYPE.MARKUP ) return preferredTab; if (artifactsToTabs.includes(CONSTANTS.ITEM_VIEWER_TAB_TYPE.PDF)) return CONSTANTS.ITEM_VIEWER_TAB_TYPE.PDF; if (artifactsToTabs.includes(CONSTANTS.ITEM_VIEWER_TAB_TYPE.ORIGINAL)) return CONSTANTS.ITEM_VIEWER_TAB_TYPE.ORIGINAL; if (artifactsToTabs.includes(CONSTANTS.ITEM_VIEWER_TAB_TYPE.TEXT)) return CONSTANTS.ITEM_VIEWER_TAB_TYPE.TEXT; // if (artifactsToTabs.includes(CONSTANTS.ITEM_VIEWER_TAB_TYPE.MARKUP)) // return CONSTANTS.ITEM_VIEWER_TAB_TYPE.MARKUP; return CONSTANTS.ITEM_VIEWER_TAB_TYPE.PDF; }, [preferredTab, itemArtifacts]); const isNativePdfViewer = useMemo(() => { const viewers = getViewerTypeForItemDefinitionId( itemDefinitionId, itemDefinitions, itemKinds, ); return viewers.Native === CONSTANTS.ITEM_KIND_DOCUMENT; }, [itemId, itemDefinitionId, itemArtifacts]); const isPdfTab = activeTab === CONSTANTS.ITEM_VIEWER_TAB_TYPE.PDF; const isMarkupTab = activeTab === CONSTANTS.ITEM_VIEWER_TAB_TYPE.MARKUP; const isOriginalTab = activeTab === CONSTANTS.ITEM_VIEWER_TAB_TYPE.ORIGINAL && isNativePdfViewer; let loading = isPending; if (isMarkupTab && !hasMarkupArtifact) { const { isPending: isCreateMarkupArtifact, data: markupArtifact, error, } = useCreateItemMarkupArtifact(itemId, caseId, !isPending); if (error) { handleError(error, 'Failed to create Markup Artifact'); } else { itemArtifacts.push(markupArtifact); } loading = isCreateMarkupArtifact; } const showItemPdfViewerTab = useMemo(() => { if (!itemId) return false; if (loading) return false; if (!hasArtifacts) return false; return isPdfTab || isMarkupTab || isOriginalTab; }, [itemId, loading, hasArtifacts, isPdfTab, isMarkupTab, isOriginalTab]); const pdfViewerArtifactType = useMemo(() => { if (isPdfTab) return CONSTANTS.ARTIFACT_TYPE.PDF; if (isMarkupTab) return CONSTANTS.ARTIFACT_TYPE.MARKUP; return CONSTANTS.ARTIFACT_TYPE.NATIVE; }, [isPdfTab, isMarkupTab, activeTab]); return ( <div id="viewer-outline" ref={containerRef} style={{ alignContent: 'center', display: 'flex', flexDirection: 'column', height: '100%', width: '100%', borderColor: 'divider', overflow: 'hidden', }} > <TabContext value={activeTab}> <ItemViewerTabList itemId={itemId} scrollButtons={scrollButtons} artifactsToTabs={artifactsToTabs} onChange={handleTabChange} /> <ItemViewerTabPanelTabs itemId={itemId} itemDefinitionId={itemDefinitionId} isHorizontal={isReOrientatedHorizontally} loading={loading} hasArtifacts={hasArtifacts} isNativePdfViewer={isNativePdfViewer} itemArtifacts={itemArtifacts} /> </TabContext> <ItemViewerPdfTabPanel show={showItemPdfViewerTab} itemId={itemId} isHorizontal={isReOrientatedHorizontally} loading={loading} itemArtifacts={itemArtifacts} pdfViewerArtifactType={pdfViewerArtifactType} editable={isMarkupTab} /> {!isReOrientatedHorizontally ? <FooterSpacer /> : null} </div> ); }; export default ItemViewer; type ScrollButton = boolean | 'auto' | undefined; const useScrollButtonsOnResize = (): [ React.RefObject<HTMLDivElement>, ScrollButton, ] => { const ref = useRef<HTMLDivElement>(null); const [scrollButtons, setScrollButtons] = useState<ScrollButton>(false); useResizeObserver(ref, () => { if (ref.current) { const actualWidth = ref.current.offsetWidth; if (actualWidth < 300) { setScrollButtons('auto'); } else { setScrollButtons(false); } } }); return [ref, scrollButtons]; }; const TabHeader = styled(Tab)({ minHeight: '30px', height: '30px', }); type TabContainerProps = TabPanelProps & { isHorizontal: boolean; }; const ItemViewerTabPanelContainer = styled(TabPanel, { shouldForwardProp: (prop) => prop !== 'isHorizontal', })<TabContainerProps>(({ isHorizontal }) => ({ height: isHorizontal ? '100%' : `calc(100% - 99px)`, width: '100%', overflow: 'hidden', padding: 0, })); type TabType = 'Original' | 'Pdf' | 'Text'; const ItemViewerTabList: React.FC<{ itemId: number; scrollButtons: ScrollButton; artifactsToTabs: string[]; onChange: (event: React.SyntheticEvent, value: TabType) => void; }> = ({ itemId, scrollButtons, artifactsToTabs, onChange }) => { const theme = useTheme(); return ( <TabList onChange={onChange} sx={{ width: 'auto', minHeight: '28px', height: '28px', background: theme.palette.gridHeaderRowBackGroundColor.main, borderBottom: `1px solid ${theme.palette.divider}`, }} variant="scrollable" scrollButtons={scrollButtons} aria-label="documber viewer tabs" > <TabHeader label={CONSTANTS.ITEM_VIEWER_TAB_TYPE.ORIGINAL} value={CONSTANTS.ITEM_VIEWER_TAB_TYPE.ORIGINAL} disabled={ !artifactsToTabs.includes(CONSTANTS.ITEM_VIEWER_TAB_TYPE.ORIGINAL) } /> <TabHeader label={CONSTANTS.ITEM_VIEWER_TAB_TYPE.PDF} value={CONSTANTS.ITEM_VIEWER_TAB_TYPE.PDF} disabled={!artifactsToTabs.includes(CONSTANTS.ITEM_VIEWER_TAB_TYPE.PDF)} /> <TabHeader key={CONSTANTS.ITEM_VIEWER_TAB_TYPE.TEXT} label={CONSTANTS.ITEM_VIEWER_TAB_TYPE.TEXT} value={CONSTANTS.ITEM_VIEWER_TAB_TYPE.TEXT} disabled={ !artifactsToTabs.includes(CONSTANTS.ITEM_VIEWER_TAB_TYPE.TEXT) } /> <TabHeader key={CONSTANTS.ITEM_VIEWER_TAB_TYPE.MARKUP} label={CONSTANTS.ITEM_VIEWER_TAB_TYPE.MARKUP} value={CONSTANTS.ITEM_VIEWER_TAB_TYPE.MARKUP} disabled={!itemId} /> </TabList> ); }; const ItemViewerTabPanelTabs: React.FC<{ itemId: number; isHorizontal: boolean; itemDefinitionId: number; loading: boolean; hasArtifacts: boolean; isNativePdfViewer: boolean; itemArtifacts: ItemArtifact[]; }> = ({ itemId, isHorizontal, itemDefinitionId, loading, hasArtifacts, isNativePdfViewer, itemArtifacts, }) => { if (!itemId) return <FaintInformational message="Please select a Document" />; if (loading) return <div>Loading...</div>; if (!hasArtifacts) return ( <Informational message="Document does not have any artifacts" serverity="info" /> ); return ( <> <ItemViewerTextTabPanel itemId={itemId} isHorizontal={isHorizontal} /> {!isNativePdfViewer && ( <ItemViewerOriginalTabPanel itemId={itemId} itemDefinitionId={itemDefinitionId} isHorizontal={isHorizontal} itemArtifacts={itemArtifacts} /> )} </> ); }; const ItemViewerOriginalTabPanel: React.FC<{ itemId: number; isHorizontal: boolean; itemDefinitionId: number; itemArtifacts: ItemArtifact[]; }> = ({ itemId, itemDefinitionId, isHorizontal, itemArtifacts }) => { const ref = useRef<HTMLDivElement>(null); const theme = useTheme(); return ( <ItemViewerTabPanelContainer isHorizontal={isHorizontal} value={CONSTANTS.ITEM_VIEWER_TAB_TYPE.ORIGINAL} ref={ref} sx={{ backgroundColor: theme.palette.background.default }} > <ItemNativeViewer itemId={itemId} itemDefinitionId={itemDefinitionId} containerRef={ref} itemArtifacts={itemArtifacts ?? []} /> </ItemViewerTabPanelContainer> ); }; const ItemViewerTextTabPanel: React.FC<{ itemId: number; isHorizontal: boolean; }> = ({ itemId, isHorizontal }) => { return ( <ItemViewerTabPanelContainer isHorizontal={isHorizontal} value={CONSTANTS.ITEM_VIEWER_TAB_TYPE.TEXT} > <ItemTextViewer itemId={itemId} /> </ItemViewerTabPanelContainer> ); }; const ItemViewerPdfTabPanel: React.FC<{ itemId: number; isHorizontal: boolean; show: boolean; loading: boolean; itemArtifacts: ItemArtifact[]; pdfViewerArtifactType: string; editable: boolean; }> = ({ itemId, show, isHorizontal, loading, pdfViewerArtifactType, itemArtifacts, editable, }) => { return ( <Box sx={{ display: show ? 'block' : 'none', height: isHorizontal ? '100%' : `calc(100% - 99px)`, width: '100%', overflow: 'hidden', padding: 0, }} > <ItemPdfViewer itemId={itemId} artifactType={pdfViewerArtifactType} itemArtifacts={itemArtifacts} editable={editable} loading={loading} /> </Box> ); }; const useItemArtifacts = (itemId: number, caseId: number) => { return useQuery({ queryKey: ['ItemApi.getItemArtifacts', itemId, caseId], queryFn: async ({ signal }) => await ItemApi.getItemArtifacts(itemId, caseId, { signal }), }); }; const useCreateItemMarkupArtifact = ( itemId: number, caseId: number, enabled: boolean, ) => { return useQuery({ queryKey: ['ItemApi.createItemMarkup', itemId, caseId], queryFn: async ({ signal }) => await ItemApi.createItemMarkup(itemId, caseId, { signal }), enabled, }); };
Leave a Comment