Blocks: improve button overlays

This commit is contained in:
Enrico Ros
2024-08-02 17:51:45 -07:00
parent 573143c57d
commit 2cb3c82bd5
4 changed files with 164 additions and 147 deletions
+3 -3
View File
@@ -15,7 +15,7 @@ import { RenderPlainChatText } from '~/modules/blocks/plaintext/RenderPlainChatT
import { RenderCode, RenderCodeMemo } from './code/RenderCode';
import { RenderMarkdown, RenderMarkdownMemo } from './markdown/RenderMarkdown';
import { RenderTextDiff } from './textdiff/RenderTextDiff';
import { heuristicIsBlockTextHTML, RenderHtml } from './html/RenderHtml';
import { heuristicIsBlockTextHTML, RenderHtmlResponse } from './html/RenderHtmlResponse';
import { heuristicLegacyImageBlocks, heuristicMarkdownImageReferenceBlocks, RenderImageURL } from './image/RenderImageURL';
@@ -236,7 +236,7 @@ export const AutoBlocksRenderer = React.forwardRef<HTMLDivElement, BlocksRendere
fontWeight: 'md', // JetBrains Mono has a lighter weight, so we need that extra bump
fontVariantLigatures: 'none',
lineHeight: themeScalingMap[props.contentScaling]?.blockLineHeight ?? 1.75,
minWidth: 260,
minWidth: 288,
minHeight: '2.75rem',
}), [fromAssistant, props.contentScaling, props.specialCodePlain]);
@@ -288,7 +288,7 @@ export const AutoBlocksRenderer = React.forwardRef<HTMLDivElement, BlocksRendere
const optimizeSubBlockWithMemo = props.optiAllowSubBlocksMemo && index !== autoBlocksMemo.length - 1;
const RenderCodeMemoOrNot = optimizeSubBlockWithMemo ? RenderCodeMemo : RenderCode;
const RenderMarkdownMemoOrNot = optimizeSubBlockWithMemo ? RenderMarkdownMemo : RenderMarkdown;
return block.type === 'htmlb' ? <RenderHtml key={'html-' + index} htmlBlock={block} sx={scaledCodeSx} />
return block.type === 'htmlb' ? <RenderHtmlResponse key={'html-' + index} htmlBlock={block} sx={scaledCodeSx} />
: block.type === 'codeb' ? <RenderCodeMemoOrNot key={'code-' + index} codeBlock={block} fitScreen={props.fitScreen} initialShowHTML={props.showUnsafeHtml} noCopyButton={props.specialDiagramMode} optimizeLightweight={false /*!optimizeSubBlockWithMemo*/} sx={scaledCodeSx} />
: block.type === 'imageb' ? <RenderImageURL key={'image-' + index} imageURL={block.url} expandableText={block.alt} onImageRegenerate={undefined /* we'd need to have selective fragment editing as there could be many of these URL images in a fragment */} scaledImageSx={scaledImageSx} variant='content-part' />
: block.type === 'diffb' ? <RenderTextDiff key={'text-diff-' + index} textDiffBlock={block} sx={scaledTypographySx} />
+154 -136
View File
@@ -19,7 +19,7 @@ import { ButtonCodePen, isCodePenSupported } from './ButtonCodePen';
import { ButtonJsFiddle, isJSFiddleSupported } from './ButtonJSFiddle';
import { ButtonStackBlitz, isStackBlitzSupported } from './ButtonStackBlitz';
import { RenderCodeHtmlIFrame } from './RenderCodeHtmlIFrame';
import { heuristicIsBlockTextHTML } from '../html/RenderHtml';
import { heuristicIsBlockTextHTML } from '../html/RenderHtmlResponse';
import { patchSvgString, RenderCodeMermaid } from './RenderCodeMermaid';
import { usePlantUmlSvg } from './RenderCodePlantUML';
@@ -35,14 +35,34 @@ export const OverlayButton = styled(IconButton)(({ theme, variant }) => ({
export const overlayButtonsSx: SxProps = {
position: 'absolute', top: 0, right: 0, zIndex: 2, /* top of message and its chips */
display: 'flex', flexDirection: 'row', gap: 1,
opacity: 0.1, transition: 'opacity 0.2s cubic-bezier(.17,.84,.44,1)',
// stick to the top-right corner
position: 'absolute',
top: 0,
right: 0,
zIndex: 2, // top of message and its chips
// stype
p: 0.5,
// layout
display: 'flex',
flexDirection: 'row',
gap: 1,
// faded-out defaults
opacity: 0.1,
pointerEvents: 'none',
transition: 'opacity 0.2s cubic-bezier(.17,.84,.44,1)',
// buttongroup: background
'& > div > button': {
// backgroundColor: 'background.surface',
// backdropFilter: 'blur(12px)',
},
// '& > div > button': {
// backgroundColor: 'background.surface',
// backdropFilter: 'blur(12px)',
// },
};
export const overlayButtonsActiveSx = {
opacity: 1,
pointerEvents: 'auto',
};
@@ -137,152 +157,150 @@ function RenderCodeImpl(props: RenderCodeImplProps) {
};
return (
<Box sx={{
position: 'relative', /* for overlay buttons to stick properly */
}}>
<Box
component='code'
className={`language-${inferredCodeLanguage || 'unknown'}${renderLineNumbers ? ' line-numbers' : ''}`}
sx={{
// position the overlay buttons
position: 'relative',
{/* Code render */}
<Box
component='code'
className={`language-${inferredCodeLanguage || 'unknown'}${renderLineNumbers ? ' line-numbers' : ''}`}
sx={{
p: isBorderless ? 0 : 1.5, // this block gets a thicker border (but we 'fullscreen' html in case there's no title)
overflowX: 'auto', // ensure per-block x-scrolling
whiteSpace: showSoftWrap ? 'break-spaces' : 'pre',
// style
p: isBorderless ? 0 : 1.5, // this block gets a thicker border (but we 'fullscreen' html in case there's no title)
overflowX: 'auto', // ensure per-block x-scrolling
whiteSpace: showSoftWrap ? 'break-spaces' : 'pre',
// layout
display: 'flex',
flexDirection: 'column',
// justifyContent: (renderMermaid || renderPlantUML) ? 'center' : undefined,
// layout
display: 'flex',
flexDirection: 'column',
// justifyContent: (renderMermaid || renderPlantUML) ? 'center' : undefined,
// fix for SVG diagrams over dark mode: https://github.com/enricoros/big-AGI/issues/520
'[data-joy-color-scheme="dark"] &': (renderPlantUML || renderMermaid) ? { backgroundColor: 'neutral.400' } : {},
// fix for SVG diagrams over dark mode: https://github.com/enricoros/big-AGI/issues/520
'[data-joy-color-scheme="dark"] &': (renderPlantUML || renderMermaid) ? { backgroundColor: 'neutral.500' } : {},
// fade in children buttons
'&:hover > .overlay-buttons': { opacity: 1 },
// fade in children buttons
'&:hover > .overlay-buttons': overlayButtonsActiveSx,
// lots more style, incl font, background, embossing, radius, etc.
...props.sx,
}}
>
// lots more style, incl font, background, embossing, radius, etc.
...props.sx,
}}
>
{/* Markdown Title (File/Type) */}
{showBlockTitle && (
<Sheet sx={{ backgroundColor: 'background.popup', boxShadow: 'xs', borderRadius: 'sm', border: '1px solid var(--joy-palette-neutral-outlinedBorder)', m: -0.5, mb: 1.5 }}>
<Typography level='body-sm' sx={{ px: 1, py: 0.5, color: 'text.primary' }}>
{blockTitle}
{/*{inferredCodeLanguage}*/}
</Typography>
</Sheet>
{/* Markdown Title (File/Type) */}
{showBlockTitle && (
<Sheet sx={{ backgroundColor: 'background.popup', boxShadow: 'xs', borderRadius: 'sm', border: '1px solid var(--joy-palette-neutral-outlinedBorder)', m: -0.5, mb: 1.5 }}>
<Typography level='body-sm' sx={{ px: 1, py: 0.5, color: 'text.primary' }}>
{blockTitle}
{/*{inferredCodeLanguage}*/}
</Typography>
</Sheet>
)}
{/* Renders HTML, or inline SVG, inline plantUML rendered, or highlighted code */}
{renderHTML
? <RenderCodeHtmlIFrame htmlCode={blockCode} />
: renderMermaid
? <RenderCodeMermaid mermaidCode={blockCode} fitScreen={fitScreen} />
: <Box component='div'
className='code-container'
dangerouslySetInnerHTML={{
__html:
renderSVG
? (patchSvgString(fitScreen, blockCode) || 'No SVG code')
: renderPlantUML
? (patchSvgString(fitScreen, plantUmlHtmlData) || (plantUmlError ? `PlantUML Error: ${plantUmlError.message}` : 'No PlantUML code'))
: highlightedCode,
}}
sx={{
...(renderSVG ? { lineHeight: 0 } : {}),
...(renderPlantUML ? { textAlign: 'center', mx: 'auto' } : {}),
}}
/>}
{/* Overlay Buttons */}
<Box className='overlay-buttons' sx={overlayButtonsSx}>
{/* Show HTML */}
{isHTML && (
<Tooltip title={optimizeLightweight ? null : renderHTML ? 'Hide' : 'Show Web Page'}>
<OverlayButton variant={renderHTML ? 'solid' : 'outlined'} color='danger' onClick={() => setShowHTML(!showHTML)}>
<HtmlIcon sx={{ fontSize: 'xl2' }} />
</OverlayButton>
</Tooltip>
)}
{/* Renders HTML, or inline SVG, inline plantUML rendered, or highlighted code */}
{renderHTML
? <RenderCodeHtmlIFrame htmlCode={blockCode} />
: renderMermaid
? <RenderCodeMermaid mermaidCode={blockCode} fitScreen={fitScreen} />
: <Box component='div'
className='code-container'
dangerouslySetInnerHTML={{
__html:
renderSVG
? (patchSvgString(fitScreen, blockCode) || 'No SVG code')
: renderPlantUML
? (patchSvgString(fitScreen, plantUmlHtmlData) || (plantUmlError ? `PlantUML Error: ${plantUmlError.message}` : 'No PlantUML code'))
: highlightedCode,
}}
sx={{
...(renderSVG ? { lineHeight: 0 } : {}),
...(renderPlantUML ? { textAlign: 'center', mx: 'auto' } : {}),
}}
/>}
{/* Show SVG */}
{isSVG && (
<Tooltip title={optimizeLightweight ? null : renderSVG ? 'Show Code' : 'Render SVG'}>
<OverlayButton variant={renderSVG ? 'solid' : 'outlined'} onClick={() => setShowSVG(!showSVG)}>
<ChangeHistoryTwoToneIcon />
</OverlayButton>
</Tooltip>
)}
{/* Overlay Buttons */}
<Box className='overlay-buttons' sx={{ ...overlayButtonsSx, p: 0.5 }}>
{/* Show HTML */}
{isHTML && (
<Tooltip title={optimizeLightweight ? null : renderHTML ? 'Hide' : 'Show Web Page'}>
<OverlayButton variant={renderHTML ? 'solid' : 'outlined'} color='danger' onClick={() => setShowHTML(!showHTML)}>
<HtmlIcon sx={{ fontSize: 'xl2' }} />
{/* Show Diagrams */}
{(isMermaid || isPlantUML) && (
<ButtonGroup aria-label='Diagram'>
{/* Toggle rendering */}
<Tooltip title={optimizeLightweight ? null : (renderMermaid || renderPlantUML) ? 'Show Code' : 'Render Mermaid'}>
<OverlayButton variant={(renderMermaid || renderPlantUML) ? 'solid' : 'outlined'} onClick={() => {
if (isMermaid) setShowMermaid(on => !on);
if (isPlantUML) setShowPlantUML(on => !on);
}}>
<SchemaIcon />
</OverlayButton>
</Tooltip>
)}
{/* Show SVG */}
{isSVG && (
<Tooltip title={optimizeLightweight ? null : renderSVG ? 'Show Code' : 'Render SVG'}>
<OverlayButton variant={renderSVG ? 'solid' : 'outlined'} onClick={() => setShowSVG(!showSVG)}>
<ChangeHistoryTwoToneIcon />
</OverlayButton>
</Tooltip>
)}
{/* Show Diagrams */}
{(isMermaid || isPlantUML) && (
<ButtonGroup aria-label='Diagram'>
{/* Toggle rendering */}
<Tooltip title={optimizeLightweight ? null : (renderMermaid || renderPlantUML) ? 'Show Code' : 'Render Mermaid'}>
<OverlayButton variant={(renderMermaid || renderPlantUML) ? 'solid' : 'outlined'} onClick={() => {
if (isMermaid) setShowMermaid(on => !on);
if (isPlantUML) setShowPlantUML(on => !on);
}}>
<SchemaIcon />
</OverlayButton>
</Tooltip>
{/* Fit-To-Screen */}
{((isMermaid && showMermaid) || (isPlantUML && showPlantUML && !plantUmlError) || (isSVG && showSVG && canScaleSVG)) && (
<Tooltip title={optimizeLightweight ? null : fitScreen ? 'Original Size' : 'Fit Screen'}>
<OverlayButton variant={fitScreen ? 'solid' : 'outlined'} onClick={() => setFitScreen(on => !on)}>
<FitScreenIcon />
</OverlayButton>
</Tooltip>
)}
</ButtonGroup>
)}
{/* New Code Window Buttons */}
{(canJSFiddle || canCodePen || canStackBlitz) && (
<ButtonGroup aria-label='Open code in external editors'>
{canJSFiddle && <ButtonJsFiddle code={blockCode} language={inferredCodeLanguage!} />}
{canCodePen && <ButtonCodePen code={blockCode} language={inferredCodeLanguage!} />}
{canStackBlitz && <ButtonStackBlitz code={blockCode} title={blockTitle} language={inferredCodeLanguage!} />}
</ButtonGroup>
)}
<ButtonGroup aria-label='Text Options'>
{/* Soft Wrap toggle */}
{renderCode && (
<Tooltip title={optimizeLightweight ? null : 'Toggle Soft Wrap'}>
<OverlayButton disabled={!renderCode} variant={(showSoftWrap && renderCode) ? 'solid' : 'outlined'} onClick={() => setShowSoftWrap(!showSoftWrap)}>
<WrapTextIcon />
</OverlayButton>
</Tooltip>
)}
{/* Line Numbers toggle */}
{renderCode && (
<Tooltip title={optimizeLightweight ? null : 'Toggle Line Numbers'}>
<OverlayButton disabled={cannotRenderLineNumbers} variant={(renderLineNumbers && renderCode) ? 'solid' : 'outlined'} onClick={() => setShowLineNumbers(!showLineNumbers)}>
<NumbersRoundedIcon />
</OverlayButton>
</Tooltip>
)}
{/* Copy */}
{props.noCopyButton !== true && (
<Tooltip title={optimizeLightweight ? null : 'Copy Code'}>
<OverlayButton variant='outlined' onClick={handleCopyToClipboard}>
<ContentCopyIcon />
{/* Fit-To-Screen */}
{((isMermaid && showMermaid) || (isPlantUML && showPlantUML && !plantUmlError) || (isSVG && showSVG && canScaleSVG)) && (
<Tooltip title={optimizeLightweight ? null : fitScreen ? 'Original Size' : 'Fit Screen'}>
<OverlayButton variant={fitScreen ? 'solid' : 'outlined'} onClick={() => setFitScreen(on => !on)}>
<FitScreenIcon />
</OverlayButton>
</Tooltip>
)}
</ButtonGroup>
</Box>
)}
{/* New Code Window Buttons */}
{(canJSFiddle || canCodePen || canStackBlitz) && (
<ButtonGroup aria-label='Open code in external editors'>
{canJSFiddle && <ButtonJsFiddle code={blockCode} language={inferredCodeLanguage!} />}
{canCodePen && <ButtonCodePen code={blockCode} language={inferredCodeLanguage!} />}
{canStackBlitz && <ButtonStackBlitz code={blockCode} title={blockTitle} language={inferredCodeLanguage!} />}
</ButtonGroup>
)}
<ButtonGroup aria-label='Text Options'>
{/* Soft Wrap toggle */}
{renderCode && (
<Tooltip title={optimizeLightweight ? null : 'Toggle Soft Wrap'}>
<OverlayButton disabled={!renderCode} variant={(showSoftWrap && renderCode) ? 'solid' : 'outlined'} onClick={() => setShowSoftWrap(!showSoftWrap)}>
<WrapTextIcon />
</OverlayButton>
</Tooltip>
)}
{/* Line Numbers toggle */}
{renderCode && (
<Tooltip title={optimizeLightweight ? null : 'Toggle Line Numbers'}>
<OverlayButton disabled={cannotRenderLineNumbers} variant={(renderLineNumbers && renderCode) ? 'solid' : 'outlined'} onClick={() => setShowLineNumbers(!showLineNumbers)}>
<NumbersRoundedIcon />
</OverlayButton>
</Tooltip>
)}
{/* Copy */}
{props.noCopyButton !== true && (
<Tooltip title={optimizeLightweight ? null : 'Copy Code'}>
<OverlayButton variant='outlined' onClick={handleCopyToClipboard}>
<ContentCopyIcon />
</OverlayButton>
</Tooltip>
)}
</ButtonGroup>
</Box>
</Box>
);
}
@@ -8,7 +8,7 @@ import WebIcon from '@mui/icons-material/Web';
import { copyToClipboard } from '~/common/util/clipboardUtils';
import type { HtmlBlock } from '../blocks.types';
import { OverlayButton, overlayButtonsSx } from '../code/RenderCode';
import { OverlayButton, overlayButtonsActiveSx, overlayButtonsSx } from '../code/RenderCode';
import { RenderCodeHtmlIFrame } from '~/modules/blocks/code/RenderCodeHtmlIFrame';
@@ -18,7 +18,7 @@ export function heuristicIsBlockTextHTML(text: string): boolean {
}
export function RenderHtml(props: { htmlBlock: HtmlBlock, sx?: SxProps }) {
export function RenderHtmlResponse(props: { htmlBlock: HtmlBlock, sx?: SxProps }) {
const [showHTML, setShowHTML] = React.useState(false);
// remove the font* properties from sx
@@ -41,7 +41,7 @@ export function RenderHtml(props: { htmlBlock: HtmlBlock, sx?: SxProps }) {
p: 1.5, // this block gets a thicker border
display: 'block',
overflowX: 'auto',
'&:hover > .overlay-buttons': { opacity: 1 },
'&:hover > .overlay-buttons': overlayButtonsActiveSx,
...sx,
}}
>
@@ -68,7 +68,7 @@ export function RenderHtml(props: { htmlBlock: HtmlBlock, sx?: SxProps }) {
}
{/* External HTML Buttons */}
<Box className='overlay-buttons' sx={{ ...overlayButtonsSx, p: 1.5 }}>
<Box className='overlay-buttons' sx={overlayButtonsSx}>
<Tooltip title={showHTML ? 'Hide' : 'Show Web Page'} variant='solid'>
<OverlayButton variant={showHTML ? 'solid' : 'outlined'} color='danger' onClick={() => setShowHTML(!showHTML)}>
<WebIcon />
+3 -4
View File
@@ -14,7 +14,7 @@ import { GoodTooltip } from '~/common/components/GoodTooltip';
import { Link } from '~/common/components/Link';
import type { ImageBlock } from '../blocks.types';
import { OverlayButton, overlayButtonsSx } from '../code/RenderCode';
import { OverlayButton, overlayButtonsActiveSx, overlayButtonsSx } from '../code/RenderCode';
const mdImageReferenceRegex = /^!\[([^\]]*)]\(([^)]+)\)$/;
@@ -164,8 +164,8 @@ export const RenderImageURL = (props: {
// resizeable image
'& picture': { display: 'flex', justifyContent: 'center' },
'& img': { maxWidth: '100%', maxHeight: '100%' },
'&:hover .overlay-buttons': { opacity: 1 },
'&:hover .overlay-text': { opacity: 1 },
'&:hover .overlay-buttons': overlayButtonsActiveSx,
'&:hover .overlay-text': overlayButtonsActiveSx,
// layout
display: 'flex',
@@ -233,7 +233,6 @@ export const RenderImageURL = (props: {
{/* [overlay] Buttons */}
<Box className='overlay-buttons' sx={{
...overlayButtonsSx,
p: 0.5,
display: 'grid',
gridTemplateColumns: 'auto auto',
gap: 0.5,