added hack for preview to prevent preview blinking and jumping

This commit is contained in:
Nikita 2024-12-27 20:25:00 +03:00
parent e4aefcdaf9
commit 295ecf6aa5
3 changed files with 116 additions and 34 deletions

View file

@ -1,4 +1,5 @@
import { isEqual } from 'lodash'; import { isEqual } from 'lodash';
import clsx from 'clsx';
/** /**
* Styles * Styles
@ -8,49 +9,123 @@ import './style.scss';
/** /**
* WordPress dependencies * WordPress dependencies
*/ */
import { memo } from '@wordpress/element'; import { memo, useState, useEffect, useRef } from '@wordpress/element';
import { BlockPreview } from '@wordpress/block-editor'; import { BlockPreview } from '@wordpress/block-editor';
function RenderPreview({ response, className, style }) {
return (
<div
className={clsx('mind-popup-response__preview', className)}
style={style}
>
<BlockPreview
// Since the preview does not render properly first block align full, we need to create the wrapper Group block with our custom styles.
// Align classes rendered properly only for the inner blocks.
blocks={[
{
name: 'core/group',
clientId: 'a9b75f7e-55c7-4f2b-93bb-00cf24181278',
isValid: true,
attributes: {
align: 'full',
layout: {
type: 'constrained',
},
className: 'alignfull',
},
innerBlocks: response,
},
]}
viewportWidth={0}
additionalStyles={[
{
css: `
.is-root-container > div {
margin-top: 0;
}
`,
},
]}
/>
</div>
);
}
const AIResponse = memo( const AIResponse = memo(
function AIResponse({ response, loading }) { function AIResponse({ response, loading }) {
const [activePreview, setActivePreview] = useState(1);
const [preview1Data, setPreview1Data] = useState([]);
const [preview2Data, setPreview2Data] = useState([]);
const transitionTimeoutRef = useRef(null);
// This implementation make me cry, but it works for now.
// In short, when we have a single preview and update the response,
// it rerenders and we see a blink. To avoid this, we have two previews
// and we switch between them on each update.
useEffect(() => {
if (!response.length) {
return;
}
// Clear any existing timeout
if (transitionTimeoutRef.current) {
clearTimeout(transitionTimeoutRef.current);
}
// Update the inactive preview with new data
if (activePreview === 1) {
setPreview2Data(response);
} else {
setPreview1Data(response);
}
// Wait for the next frame to start transition.
// Small delay to ensure new content is rendered.
transitionTimeoutRef.current = setTimeout(() => {
setActivePreview(activePreview === 1 ? 2 : 1);
}, 50);
return () => {
if (transitionTimeoutRef.current) {
clearTimeout(transitionTimeoutRef.current);
}
};
}, [response]);
if (!response.length && !loading) { if (!response.length && !loading) {
return null; return null;
} }
const hiddenPreviewStyles = {
position: 'absolute',
top: 0,
left: 0,
right: 0,
};
return ( return (
<div className="mind-popup-response"> <div className="mind-popup-response">
{response.length > 0 && ( {(preview1Data.length > 0 || preview2Data.length > 0) && (
<div className="mind-popup-response__preview"> <div style={{ position: 'relative' }}>
<BlockPreview <RenderPreview
// Since the preview does not render properly first block align full, we need to create the wrapper Group block with our custom styles. response={preview1Data}
// Align classes rendered properly only for the inner blocks. style={{
blocks={[ ...(activePreview === 1
{ ? {}
name: 'core/group', : hiddenPreviewStyles),
clientId: opacity: activePreview === 1 ? 1 : 0,
'a9b75f7e-55c7-4f2b-93bb-00cf24181278', zIndex: activePreview === 1 ? 2 : 1,
isValid: true, }}
attributes: { />
// "tagName": "div", <RenderPreview
align: 'full', response={preview2Data}
layout: { style={{
type: 'constrained', ...(activePreview === 2
}, ? {}
className: 'alignfull', : hiddenPreviewStyles),
}, opacity: activePreview === 2 ? 1 : 0,
innerBlocks: response, zIndex: activePreview === 2 ? 2 : 1,
}, }}
]}
viewportWidth={0}
additionalStyles={[
{
css: `
.is-root-container > div {
margin-top: 0;
}
`,
},
]}
/> />
</div> </div>
)} )}

View file

@ -12,6 +12,13 @@
} }
} }
.mind-popup-response__preview {
/* GPU acceleration */
will-change: opacity;
backface-visibility: hidden;
transform: translateZ(0);
}
@keyframes mind-cursor-blink { @keyframes mind-cursor-blink {
0%, 0%,
100% { 100% {

View file

@ -13,7 +13,7 @@ export default class BlocksStreamProcessor {
// Add throttled dispatch // Add throttled dispatch
this.throttledDispatch = this.throttle( this.throttledDispatch = this.throttle(
this.performDispatch.bind(this), this.performDispatch.bind(this),
150 200
); );
} }