diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable-XY-1-Google-Chrome-win32.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable-XY-1-Google-Chrome-win32.png index 106ae3478..1bff206b4 100644 Binary files a/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable-XY-1-Google-Chrome-win32.png and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable-XY-1-Google-Chrome-win32.png differ diff --git a/e2e/playwright/text-to-cad-tests.spec.ts b/e2e/playwright/text-to-cad-tests.spec.ts index fdc4dd0fa..f90828111 100644 --- a/e2e/playwright/text-to-cad-tests.spec.ts +++ b/e2e/playwright/text-to-cad-tests.spec.ts @@ -31,15 +31,13 @@ test.describe('Text-to-CAD tests', () => { ) await expect(submittingToastMessage).toBeVisible() - await page.waitForTimeout(5000) - const generatingToastMessage = page.getByText( `Generating parametric model...` ) - await expect(generatingToastMessage).toBeVisible() + await expect(generatingToastMessage).toBeVisible({ timeout: 10000 }) const successToastMessage = page.getByText(`Text-to-CAD successful`) - await expect(successToastMessage).toBeVisible() + await expect(successToastMessage).toBeVisible({ timeout: 15000 }) await expect(page.getByText('Copied')).not.toBeVisible() @@ -101,15 +99,13 @@ test.describe('Text-to-CAD tests', () => { ) await expect(submittingToastMessage).toBeVisible() - await page.waitForTimeout(5000) - const generatingToastMessage = page.getByText( `Generating parametric model...` ) - await expect(generatingToastMessage).toBeVisible() + await expect(generatingToastMessage).toBeVisible({ timeout: 10000 }) const successToastMessage = page.getByText(`Text-to-CAD successful`) - await expect(successToastMessage).toBeVisible() + await expect(successToastMessage).toBeVisible({ timeout: 15000 }) await expect(page.getByText('Copied')).not.toBeVisible() @@ -121,13 +117,12 @@ test.describe('Text-to-CAD tests', () => { // Find the toast. // Look out for the toast message await expect(submittingToastMessage).toBeVisible() - - await page.waitForTimeout(5000) - - await expect(generatingToastMessage).toBeVisible() + await expect(generatingToastMessage).toBeVisible({ timeout: 10000 }) // Expect 2 success toasts. - await expect(successToastMessage).toHaveCount(2) + await expect(successToastMessage).toHaveCount(2, { + timeout: 15000, + }) await expect(page.getByText('a 2x4 lego')).toBeVisible() await expect(page.getByText('a 2x6 lego')).toBeVisible() }) @@ -150,15 +145,13 @@ test.describe('Text-to-CAD tests', () => { ) await expect(submittingToastMessage).toBeVisible() - await page.waitForTimeout(5000) - const generatingToastMessage = page.getByText( `Generating parametric model...` ) - await expect(generatingToastMessage).toBeVisible() + await expect(generatingToastMessage).toBeVisible({ timeout: 10000 }) const successToastMessage = page.getByText(`Text-to-CAD successful`) - await expect(successToastMessage).toBeVisible() + await expect(successToastMessage).toBeVisible({ timeout: 15000 }) // Hit copy to clipboard. const rejectButton = page.getByRole('button', { name: 'Reject' }) @@ -319,11 +312,9 @@ test.describe('Text-to-CAD tests', () => { // Look out for the toast message await expect(submittingToastMessage).toBeVisible() - await page.waitForTimeout(5000) + await expect(generatingToastMessage).toBeVisible({ timeout: 10000 }) - await expect(generatingToastMessage).toBeVisible() - - await expect(successToastMessage).toBeVisible() + await expect(successToastMessage).toBeVisible({ timeout: 15000 }) }) test('sending a bad prompt fails, can ignore toast, can start over from command bar', async ({ @@ -391,11 +382,9 @@ test.describe('Text-to-CAD tests', () => { // Look out for the toast message await expect(submittingToastMessage).toBeVisible() - await page.waitForTimeout(5000) + await expect(generatingToastMessage).toBeVisible({ timeout: 10000 }) - await expect(generatingToastMessage).toBeVisible() - - await expect(successToastMessage).toBeVisible() + await expect(successToastMessage).toBeVisible({ timeout: 15000 }) await expect(page.getByText('Copied')).not.toBeVisible() @@ -448,16 +437,13 @@ test.describe('Text-to-CAD tests', () => { ) await expect(submittingToastMessage).toBeVisible() - await page.waitForTimeout(1000) - const generatingToastMessage = page.getByText( `Generating parametric model...` ) - await expect(generatingToastMessage).toBeVisible() - await page.waitForTimeout(5000) + await expect(generatingToastMessage).toBeVisible({ timeout: 10000 }) const successToastMessage = page.getByText(`Text-to-CAD successful`) - await expect(successToastMessage).toBeVisible() + await expect(successToastMessage).toBeVisible({ timeout: 15000 }) await expect(page.getByText(promptWithNewline)).toBeVisible() }) diff --git a/src/components/ToastTextToCad.tsx b/src/components/ToastTextToCad.tsx index 780dd824e..6d5f46c66 100644 --- a/src/components/ToastTextToCad.tsx +++ b/src/components/ToastTextToCad.tsx @@ -5,7 +5,7 @@ import { isDesktop } from 'lib/isDesktop' import { PATHS } from 'lib/paths' import toast from 'react-hot-toast' import { TextToCad_type } from '@kittycad/lib/dist/types/src/models' -import { useEffect, useRef, useState } from 'react' +import { useCallback, useEffect, useRef, useState } from 'react' import { Box3, Color, @@ -121,10 +121,40 @@ export function ToastTextToCadSuccess({ }) { const wrapperRef = useRef(null) const canvasRef = useRef(null) + const animationRequestRef = useRef() const [hasCopied, setHasCopied] = useState(false) const [showCopiedUi, setShowCopiedUi] = useState(false) const modelId = data.id + const animate = useCallback( + ({ + renderer, + scene, + camera, + controls, + isFirstRender = false, + }: { + renderer: WebGLRenderer + scene: Scene + camera: OrthographicCamera + controls: OrbitControls + isFirstRender?: boolean + }) => { + if ( + !wrapperRef.current || + !(isFirstRender || animationRequestRef.current) + ) + return + animationRequestRef.current = requestAnimationFrame(() => + animate({ renderer, scene, camera, controls }) + ) + // required if controls.enableDamping or controls.autoRotate are set to true + controls.update() + renderer.render(scene, camera) + }, + [] + ) + useEffect(() => { if (!canvasRef.current) return @@ -132,7 +162,6 @@ export function ToastTextToCadSuccess({ const renderer = new WebGLRenderer({ canvas, antialias: true, alpha: true }) renderer.setSize(CANVAS_SIZE, CANVAS_SIZE) renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)) - renderer.setAnimationLoop(animate) const scene = new Scene() const ambientLight = new DirectionalLight(new Color('white'), 8.0) @@ -155,13 +184,6 @@ export function ToastTextToCadSuccess({ return } - function animate() { - requestAnimationFrame(animate) - // required if controls.enableDamping or controls.autoRotate are set to true - controls.update() - renderer.render(scene, camera) - } - loader.parse( buffer, '', @@ -212,6 +234,8 @@ export function ToastTextToCadSuccess({ camera.updateProjectionMatrix() controls.update() + // render the scene once... + renderer.render(scene, camera) }, // called when loading has errors function (error) { @@ -221,8 +245,26 @@ export function ToastTextToCadSuccess({ } ) + // ...and set a mouseover listener on the canvas to enable the orbit controls + canvasRef.current.addEventListener('mouseover', () => { + renderer.setAnimationLoop(() => + animate({ renderer, scene, camera, controls, isFirstRender: true }) + ) + }) + canvasRef.current.addEventListener('mouseout', () => { + renderer.setAnimationLoop(null) + if (animationRequestRef.current) { + cancelAnimationFrame(animationRequestRef.current) + animationRequestRef.current = undefined + } + }) + return () => { renderer.dispose() + if (animationRequestRef.current) { + cancelAnimationFrame(animationRequestRef.current) + animationRequestRef.current = undefined + } } }, [])