Files
big-agi/src/modules/blocks/enhanced-code/EnhancedRenderCode.tsx
T
2024-08-17 20:05:40 -07:00

210 lines
6.5 KiB
TypeScript

import * as React from 'react';
import type { SxProps } from '@mui/joy/styles/types';
import { Box, ColorPaletteProp, IconButton, Typography } from '@mui/joy';
import CodeIcon from '@mui/icons-material/Code';
import MoreVertIcon from '@mui/icons-material/MoreVert';
import type { ContentScaling } from '~/common/app.theme';
import { ExpanderControlledBox } from '~/common/components/ExpanderControlledBox';
import { TooltipOutlined } from '~/common/components/TooltipOutlined';
import { EnhancedRenderCodeMenu } from './EnhancedRenderCodeMenu';
import { RenderCodeMemo } from '../code/RenderCode';
import { enhancedCodePanelTitleTooltipSx, RenderCodePanelFrame } from '../code/RenderCodePanelFrame';
import { getCodeCollapseManager } from './codeCollapseManager';
import { useLiveFilePatch } from './livefile-patch/useLiveFilePatch';
export function EnhancedRenderCode(props: {
semiStableId: string | undefined,
title: string,
code: string,
isPartial: boolean,
fitScreen: boolean,
isMobile: boolean,
initialShowHTML?: boolean,
noCopyButton?: boolean,
optimizeLightweight?: boolean,
codeSx?: SxProps,
language?: string,
color?: ColorPaletteProp;
contentScaling: ContentScaling;
// onLiveFileCreate?: () => void,
}) {
// state
const [contextMenuAnchor, setContextMenuAnchor] = React.useState<HTMLElement | null>(null);
const [isCodeCollapsed, setIsCodeCollapsed] = React.useState(false);
// LiveFile - patch state
const { button: liveFileButton, actionBar: liveFileActionBar } = useLiveFilePatch(
props.title, props.code, props.isPartial,
props.isMobile,
);
// handlers
const handleCloseContextMenu = React.useCallback(() => setContextMenuAnchor(null), []);
const handleToggleCodeCollapse = React.useCallback(() => {
setIsCodeCollapsed(c => !c);
handleCloseContextMenu();
}, [handleCloseContextMenu]);
const handleToggleContextMenu = React.useCallback((event: React.MouseEvent<HTMLElement>) => {
event.preventDefault(); // added for the Right mouse click (to prevent the menu)
event.stopPropagation();
setContextMenuAnchor(anchor => anchor ? null : event.currentTarget);
}, []);
// effects
React.useEffect(() => {
return getCodeCollapseManager().addCollapseAllListener((collapseAll: boolean) => {
setIsCodeCollapsed(collapseAll);
handleCloseContextMenu();
});
}, [handleCloseContextMenu]);
// components
const headerTooltipContents = React.useMemo(() => (
<Box sx={enhancedCodePanelTitleTooltipSx}>
{/* This is what we have */}
<div><strong>Code Block</strong></div>
<div></div>
<div>Title</div>
<div>{props.title || '(empty)'}</div>
{/*<div>Language</div>*/}
{/*<div>{props.language}</div>*/}
<div>Code Lines</div>
<div>{props.code.split('\n').length} lines</div>
<div>Code Length</div>
<div>{props.code.length} characters</div>
<div>semiStableId</div>
<div>{props.semiStableId || '(none)'}</div>
{/* This is what attachments carry */}
{/*<div>Attachment Title</div>*/}
{/*<div>{fragment.title}</div>*/}
{/*<div>Doc Title</div>*/}
{/*<div>{fragmentDocPart.l1Title}</div>*/}
{/*<div>Identifier</div>*/}
{/*<div>{fragmentDocPart.ref}</div>*/}
{/*<div>Render type</div>*/}
{/*<div>{fragmentDocPart.vdt}</div>*/}
{/*<div>Text Mime type</div>*/}
{/*<div>{fragmentDocPart.data?.mimeType || '(unknown)'}</div>*/}
{/*<div>Text Buffer Id</div>*/}
{/*<div>{fragmentId}</div>*/}
</Box>
), [props.code, props.semiStableId, props.title]);
const headerRow = React.useMemo(() => <>
{/* Icon and Title */}
<TooltipOutlined placement='top-start' color='neutral' title={headerTooltipContents}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<CodeIcon
aria-hidden
onClick={handleToggleCodeCollapse}
sx={{
transform: isCodeCollapsed ? 'rotate(-90deg)' : 'none',
transition: 'transform 0.2s cubic-bezier(.17,.84,.44,1)',
cursor: 'pointer',
}}
/>
<Typography level='title-sm'>
{props.title || 'Code'}
</Typography>
</Box>
</TooltipOutlined>
{/* LiveFile - Select */}
{liveFileButton}
{/* Menu Options button */}
<IconButton
size='sm'
onClick={handleToggleContextMenu}
onContextMenu={handleToggleContextMenu}
sx={{ mr: -0.5 }}
>
<MoreVertIcon />
</IconButton>
</>, [handleToggleCodeCollapse, handleToggleContextMenu, headerTooltipContents, isCodeCollapsed, liveFileButton, props.title]);
// const toolbarRow = React.useMemo(() => <>
// {props.onLiveFileCreate && (
// <Button
// size='sm'
// variant='outlined'
// color='neutral'
// startDecorator={<LiveHelpIcon />}
// onClick={props.onLiveFileCreate}
// >
// Create Live File
// </Button>
// )}
// {/* Add more toolbar items here */}
// </>, [props.onLiveFileCreate]);
// styles
const patchCodeSx = React.useMemo(() => ({
...props.codeSx,
my: 0,
borderTop: '1px solid',
borderTopColor: `neutral.outlinedBorder`,
borderTopLeftRadius: 0,
borderTopRightRadius: 0,
}), [props.codeSx]);
return (
<RenderCodePanelFrame
color={props.color || 'neutral'}
gutterBlock
noOuterShadow
contentScaling={props.contentScaling}
headerRow={headerRow}
subHeaderInline={liveFileActionBar}
onHeaderClick={/*props.isMobile ? handleToggleCodeCollapse :*/ undefined}
onHeaderContext={handleToggleContextMenu}
>
{/* Body of the message (it's a RenderCode with patched sx, for looks) */}
<ExpanderControlledBox expanded={!isCodeCollapsed}>
<RenderCodeMemo
semiStableId={props.semiStableId}
code={props.code} title={props.title} isPartial={props.isPartial}
fitScreen={props.fitScreen}
initialShowHTML={props.initialShowHTML}
noCopyButton={props.noCopyButton}
optimizeLightweight={props.optimizeLightweight}
sx={patchCodeSx}
/>
</ExpanderControlledBox>
{/* Context Menu */}
{contextMenuAnchor && (
<EnhancedRenderCodeMenu
anchor={contextMenuAnchor}
code={props.code} title={props.title}
onClose={handleCloseContextMenu}
isCollapsed={isCodeCollapsed}
onToggleCollapse={handleToggleCodeCollapse}
/>
)}
</RenderCodePanelFrame>
);
}