Add a promise-based toast when exporting (#2541)
* Add loading and success toasts to export engine command * Move doExport out to a test utility, test visibility of loading spinner * Add playwright test for export success toast * Update Cargo.lock * Remove loading assertion, it flashes too quickly for Playwright to pick up
This commit is contained in:
		@ -1,5 +1,5 @@
 | 
			
		||||
import { test, expect, Page } from '@playwright/test'
 | 
			
		||||
import { makeTemplate, getUtils } from './test-utils'
 | 
			
		||||
import { makeTemplate, getUtils, doExport } from './test-utils'
 | 
			
		||||
import waitOn from 'wait-on'
 | 
			
		||||
import { roundOff, uuidv4 } from 'lib/utils'
 | 
			
		||||
import { SaveSettingsPayload } from 'lib/settings/settingsTypes'
 | 
			
		||||
@ -3811,3 +3811,75 @@ test('Engine disconnect & reconnect in sketch mode', async ({ page }) => {
 | 
			
		||||
    page.getByRole('button', { name: 'Exit Sketch' })
 | 
			
		||||
  ).not.toBeVisible()
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
test('Successful export shows a success toast', async ({ page }) => {
 | 
			
		||||
  // FYI this test doesn't work with only engine running locally
 | 
			
		||||
  // And you will need to have the KittyCAD CLI installed
 | 
			
		||||
  const u = await getUtils(page)
 | 
			
		||||
  await page.addInitScript(async () => {
 | 
			
		||||
    ;(window as any).playwrightSkipFilePicker = true
 | 
			
		||||
    localStorage.setItem(
 | 
			
		||||
      'persistCode',
 | 
			
		||||
      `const topAng = 25
 | 
			
		||||
const bottomAng = 35
 | 
			
		||||
const baseLen = 3.5
 | 
			
		||||
const baseHeight = 1
 | 
			
		||||
const totalHeightHalf = 2
 | 
			
		||||
const armThick = 0.5
 | 
			
		||||
const totalLen = 9.5
 | 
			
		||||
const part001 = startSketchOn('-XZ')
 | 
			
		||||
  |> startProfileAt([0, 0], %)
 | 
			
		||||
  |> yLine(baseHeight, %)
 | 
			
		||||
  |> xLine(baseLen, %)
 | 
			
		||||
  |> angledLineToY({
 | 
			
		||||
        angle: topAng,
 | 
			
		||||
        to: totalHeightHalf,
 | 
			
		||||
      }, %, 'seg04')
 | 
			
		||||
  |> xLineTo(totalLen, %, 'seg03')
 | 
			
		||||
  |> yLine(-armThick, %, 'seg01')
 | 
			
		||||
  |> angledLineThatIntersects({
 | 
			
		||||
        angle: HALF_TURN,
 | 
			
		||||
        offset: -armThick,
 | 
			
		||||
        intersectTag: 'seg04'
 | 
			
		||||
      }, %)
 | 
			
		||||
  |> angledLineToY([segAng('seg04', %) + 180, ZERO], %)
 | 
			
		||||
  |> angledLineToY({
 | 
			
		||||
        angle: -bottomAng,
 | 
			
		||||
        to: -totalHeightHalf - armThick,
 | 
			
		||||
      }, %, 'seg02')
 | 
			
		||||
  |> xLineTo(segEndX('seg03', %) + 0, %)
 | 
			
		||||
  |> yLine(-segLen('seg01', %), %)
 | 
			
		||||
  |> angledLineThatIntersects({
 | 
			
		||||
        angle: HALF_TURN,
 | 
			
		||||
        offset: -armThick,
 | 
			
		||||
        intersectTag: 'seg02'
 | 
			
		||||
      }, %)
 | 
			
		||||
  |> angledLineToY([segAng('seg02', %) + 180, -baseHeight], %)
 | 
			
		||||
  |> xLineTo(ZERO, %)
 | 
			
		||||
  |> close(%)
 | 
			
		||||
  |> extrude(4, %)`
 | 
			
		||||
    )
 | 
			
		||||
  })
 | 
			
		||||
  await page.setViewportSize({ width: 1200, height: 500 })
 | 
			
		||||
  await page.goto('/')
 | 
			
		||||
  await u.waitForAuthSkipAppStart()
 | 
			
		||||
  await u.openDebugPanel()
 | 
			
		||||
  await u.expectCmdLog('[data-message-type="execution-done"]')
 | 
			
		||||
  await u.waitForCmdReceive('extrude')
 | 
			
		||||
  await page.waitForTimeout(1000)
 | 
			
		||||
  await u.clearAndCloseDebugPanel()
 | 
			
		||||
 | 
			
		||||
  await doExport(
 | 
			
		||||
    {
 | 
			
		||||
      type: 'gltf',
 | 
			
		||||
      storage: 'embedded',
 | 
			
		||||
      presentation: 'pretty',
 | 
			
		||||
    },
 | 
			
		||||
    page
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
  // This is the main thing we're testing,
 | 
			
		||||
  // We test the export functionality across all
 | 
			
		||||
  // file types in snapshot-tests.spec.ts
 | 
			
		||||
  await expect(page.getByText('Exported successfully')).toBeVisible()
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
@ -1,10 +1,10 @@
 | 
			
		||||
import { test, expect, Download } from '@playwright/test'
 | 
			
		||||
import { test, expect } from '@playwright/test'
 | 
			
		||||
import { secrets } from './secrets'
 | 
			
		||||
import { getUtils } from './test-utils'
 | 
			
		||||
import { Paths, doExport, getUtils } from './test-utils'
 | 
			
		||||
import { Models } from '@kittycad/lib'
 | 
			
		||||
import fsp from 'fs/promises'
 | 
			
		||||
import { spawn } from 'child_process'
 | 
			
		||||
import { APP_NAME, KCL_DEFAULT_LENGTH } from 'lib/constants'
 | 
			
		||||
import { KCL_DEFAULT_LENGTH } from 'lib/constants'
 | 
			
		||||
import JSZip from 'jszip'
 | 
			
		||||
import path from 'path'
 | 
			
		||||
import { TEST_SETTINGS, TEST_SETTINGS_KEY } from './storageStates'
 | 
			
		||||
@ -99,78 +99,6 @@ const part001 = startSketchOn('-XZ')
 | 
			
		||||
  await page.waitForTimeout(1000)
 | 
			
		||||
  await u.clearAndCloseDebugPanel()
 | 
			
		||||
 | 
			
		||||
  interface Paths {
 | 
			
		||||
    modelPath: string
 | 
			
		||||
    imagePath: string
 | 
			
		||||
    outputType: string
 | 
			
		||||
  }
 | 
			
		||||
  const doExport = async (
 | 
			
		||||
    output: Models['OutputFormat_type']
 | 
			
		||||
  ): Promise<Paths> => {
 | 
			
		||||
    await page.getByRole('button', { name: APP_NAME }).click()
 | 
			
		||||
    await expect(
 | 
			
		||||
      page.getByRole('button', { name: 'Export Part' })
 | 
			
		||||
    ).toBeVisible()
 | 
			
		||||
    await page.getByRole('button', { name: 'Export Part' }).click()
 | 
			
		||||
    await expect(page.getByTestId('command-bar')).toBeVisible()
 | 
			
		||||
 | 
			
		||||
    // Go through export via command bar
 | 
			
		||||
    await page.getByRole('option', { name: output.type, exact: false }).click()
 | 
			
		||||
    await page.locator('#arg-form').waitFor({ state: 'detached' })
 | 
			
		||||
    if ('storage' in output) {
 | 
			
		||||
      await page.getByTestId('arg-name-storage').waitFor({ timeout: 1000 })
 | 
			
		||||
      await page.getByRole('button', { name: 'storage', exact: false }).click()
 | 
			
		||||
      await page
 | 
			
		||||
        .getByRole('option', { name: output.storage, exact: false })
 | 
			
		||||
        .click()
 | 
			
		||||
      await page.locator('#arg-form').waitFor({ state: 'detached' })
 | 
			
		||||
    }
 | 
			
		||||
    await expect(page.getByText('Confirm Export')).toBeVisible()
 | 
			
		||||
 | 
			
		||||
    const getPromiseAndResolve = () => {
 | 
			
		||||
      let resolve: any = () => {}
 | 
			
		||||
      const promise = new Promise<Download>((r) => {
 | 
			
		||||
        resolve = r
 | 
			
		||||
      })
 | 
			
		||||
      return [promise, resolve]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const [downloadPromise1, downloadResolve1] = getPromiseAndResolve()
 | 
			
		||||
    let downloadCnt = 0
 | 
			
		||||
 | 
			
		||||
    page.on('download', async (download) => {
 | 
			
		||||
      if (downloadCnt === 0) {
 | 
			
		||||
        downloadResolve1(download)
 | 
			
		||||
      }
 | 
			
		||||
      downloadCnt++
 | 
			
		||||
    })
 | 
			
		||||
    await page.getByRole('button', { name: 'Submit command' }).click()
 | 
			
		||||
 | 
			
		||||
    // Handle download
 | 
			
		||||
    const download = await downloadPromise1
 | 
			
		||||
    const downloadLocationer = (extra = '', isImage = false) =>
 | 
			
		||||
      `./e2e/playwright/export-snapshots/${output.type}-${
 | 
			
		||||
        'storage' in output ? output.storage : ''
 | 
			
		||||
      }${extra}.${isImage ? 'png' : output.type}`
 | 
			
		||||
    const downloadLocation = downloadLocationer()
 | 
			
		||||
 | 
			
		||||
    await download.saveAs(downloadLocation)
 | 
			
		||||
 | 
			
		||||
    if (output.type === 'step') {
 | 
			
		||||
      // stable timestamps for step files
 | 
			
		||||
      const fileContents = await fsp.readFile(downloadLocation, 'utf-8')
 | 
			
		||||
      const newFileContents = fileContents.replace(
 | 
			
		||||
        /[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]+[0-9]+[0-9]\+[0-9]{2}:[0-9]{2}/g,
 | 
			
		||||
        '1970-01-01T00:00:00.0+00:00'
 | 
			
		||||
      )
 | 
			
		||||
      await fsp.writeFile(downloadLocation, newFileContents)
 | 
			
		||||
    }
 | 
			
		||||
    return {
 | 
			
		||||
      modelPath: downloadLocation,
 | 
			
		||||
      imagePath: downloadLocationer('', true),
 | 
			
		||||
      outputType: output.type,
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  const axisDirectionPair: Models['AxisDirectionPair_type'] = {
 | 
			
		||||
    axis: 'z',
 | 
			
		||||
    direction: 'positive',
 | 
			
		||||
@ -186,84 +114,114 @@ const part001 = startSketchOn('-XZ')
 | 
			
		||||
  // just note that only `type` and `storage` are used for selecting the drop downs is the app
 | 
			
		||||
  // the rest are only there to make typescript happy
 | 
			
		||||
  exportLocations.push(
 | 
			
		||||
    await doExport({
 | 
			
		||||
      type: 'step',
 | 
			
		||||
      coords: sysType,
 | 
			
		||||
    })
 | 
			
		||||
    await doExport(
 | 
			
		||||
      {
 | 
			
		||||
        type: 'step',
 | 
			
		||||
        coords: sysType,
 | 
			
		||||
      },
 | 
			
		||||
      page
 | 
			
		||||
    )
 | 
			
		||||
  )
 | 
			
		||||
  exportLocations.push(
 | 
			
		||||
    await doExport({
 | 
			
		||||
      type: 'ply',
 | 
			
		||||
      coords: sysType,
 | 
			
		||||
      selection: { type: 'default_scene' },
 | 
			
		||||
      storage: 'ascii',
 | 
			
		||||
      units: 'in',
 | 
			
		||||
    })
 | 
			
		||||
    await doExport(
 | 
			
		||||
      {
 | 
			
		||||
        type: 'ply',
 | 
			
		||||
        coords: sysType,
 | 
			
		||||
        selection: { type: 'default_scene' },
 | 
			
		||||
        storage: 'ascii',
 | 
			
		||||
        units: 'in',
 | 
			
		||||
      },
 | 
			
		||||
      page
 | 
			
		||||
    )
 | 
			
		||||
  )
 | 
			
		||||
  exportLocations.push(
 | 
			
		||||
    await doExport({
 | 
			
		||||
      type: 'ply',
 | 
			
		||||
      storage: 'binary_little_endian',
 | 
			
		||||
      coords: sysType,
 | 
			
		||||
      selection: { type: 'default_scene' },
 | 
			
		||||
      units: 'in',
 | 
			
		||||
    })
 | 
			
		||||
    await doExport(
 | 
			
		||||
      {
 | 
			
		||||
        type: 'ply',
 | 
			
		||||
        storage: 'binary_little_endian',
 | 
			
		||||
        coords: sysType,
 | 
			
		||||
        selection: { type: 'default_scene' },
 | 
			
		||||
        units: 'in',
 | 
			
		||||
      },
 | 
			
		||||
      page
 | 
			
		||||
    )
 | 
			
		||||
  )
 | 
			
		||||
  exportLocations.push(
 | 
			
		||||
    await doExport({
 | 
			
		||||
      type: 'ply',
 | 
			
		||||
      storage: 'binary_big_endian',
 | 
			
		||||
      coords: sysType,
 | 
			
		||||
      selection: { type: 'default_scene' },
 | 
			
		||||
      units: 'in',
 | 
			
		||||
    })
 | 
			
		||||
    await doExport(
 | 
			
		||||
      {
 | 
			
		||||
        type: 'ply',
 | 
			
		||||
        storage: 'binary_big_endian',
 | 
			
		||||
        coords: sysType,
 | 
			
		||||
        selection: { type: 'default_scene' },
 | 
			
		||||
        units: 'in',
 | 
			
		||||
      },
 | 
			
		||||
      page
 | 
			
		||||
    )
 | 
			
		||||
  )
 | 
			
		||||
  exportLocations.push(
 | 
			
		||||
    await doExport({
 | 
			
		||||
      type: 'stl',
 | 
			
		||||
      storage: 'ascii',
 | 
			
		||||
      coords: sysType,
 | 
			
		||||
      units: 'in',
 | 
			
		||||
      selection: { type: 'default_scene' },
 | 
			
		||||
    })
 | 
			
		||||
    await doExport(
 | 
			
		||||
      {
 | 
			
		||||
        type: 'stl',
 | 
			
		||||
        storage: 'ascii',
 | 
			
		||||
        coords: sysType,
 | 
			
		||||
        units: 'in',
 | 
			
		||||
        selection: { type: 'default_scene' },
 | 
			
		||||
      },
 | 
			
		||||
      page
 | 
			
		||||
    )
 | 
			
		||||
  )
 | 
			
		||||
  exportLocations.push(
 | 
			
		||||
    await doExport({
 | 
			
		||||
      type: 'stl',
 | 
			
		||||
      storage: 'binary',
 | 
			
		||||
      coords: sysType,
 | 
			
		||||
      units: 'in',
 | 
			
		||||
      selection: { type: 'default_scene' },
 | 
			
		||||
    })
 | 
			
		||||
    await doExport(
 | 
			
		||||
      {
 | 
			
		||||
        type: 'stl',
 | 
			
		||||
        storage: 'binary',
 | 
			
		||||
        coords: sysType,
 | 
			
		||||
        units: 'in',
 | 
			
		||||
        selection: { type: 'default_scene' },
 | 
			
		||||
      },
 | 
			
		||||
      page
 | 
			
		||||
    )
 | 
			
		||||
  )
 | 
			
		||||
  exportLocations.push(
 | 
			
		||||
    await doExport({
 | 
			
		||||
      // obj seems to be a little flaky, times out tests sometimes
 | 
			
		||||
      type: 'obj',
 | 
			
		||||
      coords: sysType,
 | 
			
		||||
      units: 'in',
 | 
			
		||||
    })
 | 
			
		||||
    await doExport(
 | 
			
		||||
      {
 | 
			
		||||
        // obj seems to be a little flaky, times out tests sometimes
 | 
			
		||||
        type: 'obj',
 | 
			
		||||
        coords: sysType,
 | 
			
		||||
        units: 'in',
 | 
			
		||||
      },
 | 
			
		||||
      page
 | 
			
		||||
    )
 | 
			
		||||
  )
 | 
			
		||||
  exportLocations.push(
 | 
			
		||||
    await doExport({
 | 
			
		||||
      type: 'gltf',
 | 
			
		||||
      storage: 'embedded',
 | 
			
		||||
      presentation: 'pretty',
 | 
			
		||||
    })
 | 
			
		||||
    await doExport(
 | 
			
		||||
      {
 | 
			
		||||
        type: 'gltf',
 | 
			
		||||
        storage: 'embedded',
 | 
			
		||||
        presentation: 'pretty',
 | 
			
		||||
      },
 | 
			
		||||
      page
 | 
			
		||||
    )
 | 
			
		||||
  )
 | 
			
		||||
  exportLocations.push(
 | 
			
		||||
    await doExport({
 | 
			
		||||
      type: 'gltf',
 | 
			
		||||
      storage: 'binary',
 | 
			
		||||
      presentation: 'pretty',
 | 
			
		||||
    })
 | 
			
		||||
    await doExport(
 | 
			
		||||
      {
 | 
			
		||||
        type: 'gltf',
 | 
			
		||||
        storage: 'binary',
 | 
			
		||||
        presentation: 'pretty',
 | 
			
		||||
      },
 | 
			
		||||
      page
 | 
			
		||||
    )
 | 
			
		||||
  )
 | 
			
		||||
  exportLocations.push(
 | 
			
		||||
    await doExport({
 | 
			
		||||
      type: 'gltf',
 | 
			
		||||
      storage: 'standard',
 | 
			
		||||
      presentation: 'pretty',
 | 
			
		||||
    })
 | 
			
		||||
    await doExport(
 | 
			
		||||
      {
 | 
			
		||||
        type: 'gltf',
 | 
			
		||||
        storage: 'standard',
 | 
			
		||||
        presentation: 'pretty',
 | 
			
		||||
      },
 | 
			
		||||
      page
 | 
			
		||||
    )
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
  // close page to disconnect websocket since we can only have one open atm
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,11 @@
 | 
			
		||||
import { test, expect, Page } from '@playwright/test'
 | 
			
		||||
import { test, expect, Page, Download } from '@playwright/test'
 | 
			
		||||
import { EngineCommand } from '../../src/lang/std/engineConnection'
 | 
			
		||||
import fsp from 'fs/promises'
 | 
			
		||||
import pixelMatch from 'pixelmatch'
 | 
			
		||||
import { PNG } from 'pngjs'
 | 
			
		||||
import { Protocol } from 'playwright-core/types/protocol'
 | 
			
		||||
import type { Models } from '@kittycad/lib'
 | 
			
		||||
import { APP_NAME } from 'lib/constants'
 | 
			
		||||
 | 
			
		||||
async function waitForPageLoad(page: Page) {
 | 
			
		||||
  // wait for 'Loading stream...' spinner
 | 
			
		||||
@ -277,3 +279,77 @@ export const makeTemplate: (
 | 
			
		||||
      ),
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface Paths {
 | 
			
		||||
  modelPath: string
 | 
			
		||||
  imagePath: string
 | 
			
		||||
  outputType: string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const doExport = async (
 | 
			
		||||
  output: Models['OutputFormat_type'],
 | 
			
		||||
  page: Page
 | 
			
		||||
): Promise<Paths> => {
 | 
			
		||||
  await page.getByRole('button', { name: APP_NAME }).click()
 | 
			
		||||
  await expect(page.getByRole('button', { name: 'Export Part' })).toBeVisible()
 | 
			
		||||
  await page.getByRole('button', { name: 'Export Part' }).click()
 | 
			
		||||
  await expect(page.getByTestId('command-bar')).toBeVisible()
 | 
			
		||||
 | 
			
		||||
  // Go through export via command bar
 | 
			
		||||
  await page.getByRole('option', { name: output.type, exact: false }).click()
 | 
			
		||||
  await page.locator('#arg-form').waitFor({ state: 'detached' })
 | 
			
		||||
  if ('storage' in output) {
 | 
			
		||||
    await page.getByTestId('arg-name-storage').waitFor({ timeout: 1000 })
 | 
			
		||||
    await page.getByRole('button', { name: 'storage', exact: false }).click()
 | 
			
		||||
    await page
 | 
			
		||||
      .getByRole('option', { name: output.storage, exact: false })
 | 
			
		||||
      .click()
 | 
			
		||||
    await page.locator('#arg-form').waitFor({ state: 'detached' })
 | 
			
		||||
  }
 | 
			
		||||
  await expect(page.getByText('Confirm Export')).toBeVisible()
 | 
			
		||||
 | 
			
		||||
  const getPromiseAndResolve = () => {
 | 
			
		||||
    let resolve: any = () => {}
 | 
			
		||||
    const promise = new Promise<Download>((r) => {
 | 
			
		||||
      resolve = r
 | 
			
		||||
    })
 | 
			
		||||
    return [promise, resolve]
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const [downloadPromise1, downloadResolve1] = getPromiseAndResolve()
 | 
			
		||||
  let downloadCnt = 0
 | 
			
		||||
 | 
			
		||||
  page.on('download', async (download) => {
 | 
			
		||||
    if (downloadCnt === 0) {
 | 
			
		||||
      downloadResolve1(download)
 | 
			
		||||
    }
 | 
			
		||||
    downloadCnt++
 | 
			
		||||
  })
 | 
			
		||||
  await page.getByRole('button', { name: 'Submit command' }).click()
 | 
			
		||||
 | 
			
		||||
  // Handle download
 | 
			
		||||
  const download = await downloadPromise1
 | 
			
		||||
  const downloadLocationer = (extra = '', isImage = false) =>
 | 
			
		||||
    `./e2e/playwright/export-snapshots/${output.type}-${
 | 
			
		||||
      'storage' in output ? output.storage : ''
 | 
			
		||||
    }${extra}.${isImage ? 'png' : output.type}`
 | 
			
		||||
  const downloadLocation = downloadLocationer()
 | 
			
		||||
 | 
			
		||||
  await download.saveAs(downloadLocation)
 | 
			
		||||
 | 
			
		||||
  if (output.type === 'step') {
 | 
			
		||||
    // stable timestamps for step files
 | 
			
		||||
    const fileContents = await fsp.readFile(downloadLocation, 'utf-8')
 | 
			
		||||
    const newFileContents = fileContents.replace(
 | 
			
		||||
      /[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]+[0-9]+[0-9]\+[0-9]{2}:[0-9]{2}/g,
 | 
			
		||||
      '1970-01-01T00:00:00.0+00:00'
 | 
			
		||||
    )
 | 
			
		||||
    await fsp.writeFile(downloadLocation, newFileContents)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    modelPath: downloadLocation,
 | 
			
		||||
    imagePath: downloadLocationer('', true),
 | 
			
		||||
    outputType: output.type,
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										15
									
								
								src-tauri/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										15
									
								
								src-tauri/Cargo.lock
									
									
									
										generated
									
									
									
								
							@ -2602,7 +2602,7 @@ dependencies = [
 | 
			
		||||
 "wasm-bindgen-futures",
 | 
			
		||||
 "web-sys",
 | 
			
		||||
 "winnow 0.5.40",
 | 
			
		||||
 "zip 1.3.0",
 | 
			
		||||
 "zip 2.1.1",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
@ -4500,9 +4500,9 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "serde"
 | 
			
		||||
version = "1.0.202"
 | 
			
		||||
version = "1.0.203"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395"
 | 
			
		||||
checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "serde_derive",
 | 
			
		||||
]
 | 
			
		||||
@ -4518,9 +4518,9 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "serde_derive"
 | 
			
		||||
version = "1.0.202"
 | 
			
		||||
version = "1.0.203"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838"
 | 
			
		||||
checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
@ -7106,15 +7106,16 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "zip"
 | 
			
		||||
version = "1.3.0"
 | 
			
		||||
version = "2.1.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "f1f4a27345eb6f7aa7bd015ba7eb4175fa4e1b462a29874b779e0bbcf96c6ac7"
 | 
			
		||||
checksum = "1dd56a4d5921bc2f99947ac5b3abe5f510b1be7376fdc5e9fce4a23c6a93e87c"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "arbitrary",
 | 
			
		||||
 "crc32fast",
 | 
			
		||||
 "crossbeam-utils",
 | 
			
		||||
 "displaydoc",
 | 
			
		||||
 "indexmap 2.2.6",
 | 
			
		||||
 "memchr",
 | 
			
		||||
 "thiserror",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -314,8 +314,9 @@ export const ModelingMachineProvider = ({
 | 
			
		||||
 | 
			
		||||
          return {}
 | 
			
		||||
        }),
 | 
			
		||||
        'Engine export': (_, event) => {
 | 
			
		||||
        'Engine export': async (_, event) => {
 | 
			
		||||
          if (event.type !== 'Export' || TEST) return
 | 
			
		||||
          console.log('exporting', event.data)
 | 
			
		||||
          const format = {
 | 
			
		||||
            ...event.data,
 | 
			
		||||
          } as Partial<Models['OutputFormat_type']>
 | 
			
		||||
@ -359,9 +360,16 @@ export const ModelingMachineProvider = ({
 | 
			
		||||
            format.selection = { type: 'default_scene' }
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          exportFromEngine({
 | 
			
		||||
            format: format as Models['OutputFormat_type'],
 | 
			
		||||
          }).catch((e) => toast.error('Error while exporting', e)) // TODO I think we need to throw the error from engineCommandManager
 | 
			
		||||
          toast.promise(
 | 
			
		||||
            exportFromEngine({
 | 
			
		||||
              format: format as Models['OutputFormat_type'],
 | 
			
		||||
            }),
 | 
			
		||||
            {
 | 
			
		||||
              loading: 'Exporting...',
 | 
			
		||||
              success: 'Exported successfully',
 | 
			
		||||
              error: 'Error while exporting',
 | 
			
		||||
            }
 | 
			
		||||
          )
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
      guards: {
 | 
			
		||||
 | 
			
		||||
@ -994,6 +994,10 @@ export class EngineCommandManager {
 | 
			
		||||
  engineConnection?: EngineConnection
 | 
			
		||||
  defaultPlanes: DefaultPlanes | null = null
 | 
			
		||||
  commandLogs: CommandLog[] = []
 | 
			
		||||
  pendingExport?: {
 | 
			
		||||
    resolve: (filename?: string) => void
 | 
			
		||||
    reject: (reason: any) => void
 | 
			
		||||
  }
 | 
			
		||||
  _commandLogCallBack: (command: CommandLog[]) => void = () => {}
 | 
			
		||||
  private resolveReady = () => {}
 | 
			
		||||
  /** Folks should realize that wait for ready does not get called _everytime_
 | 
			
		||||
@ -1150,7 +1154,9 @@ export class EngineCommandManager {
 | 
			
		||||
            // because in all other cases we send JSON strings. But in the case of
 | 
			
		||||
            // export we send a binary blob.
 | 
			
		||||
            // Pass this to our export function.
 | 
			
		||||
            void exportSave(event.data)
 | 
			
		||||
            exportSave(event.data).then(() => {
 | 
			
		||||
              this.pendingExport?.resolve()
 | 
			
		||||
            }, this.pendingExport?.reject)
 | 
			
		||||
          } else {
 | 
			
		||||
            const message: Models['WebSocketResponse_type'] = JSON.parse(
 | 
			
		||||
              event.data
 | 
			
		||||
@ -1548,6 +1554,12 @@ export class EngineCommandManager {
 | 
			
		||||
      this.outSequence++
 | 
			
		||||
      this.engineConnection?.unreliableSend(command)
 | 
			
		||||
      return Promise.resolve()
 | 
			
		||||
    } else if (cmd.type === 'export') {
 | 
			
		||||
      const promise = new Promise((resolve, reject) => {
 | 
			
		||||
        this.pendingExport = { resolve, reject }
 | 
			
		||||
      })
 | 
			
		||||
      this.engineConnection?.send(command)
 | 
			
		||||
      return promise
 | 
			
		||||
    }
 | 
			
		||||
    if (
 | 
			
		||||
      command.cmd.type === 'default_camera_look_at' ||
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user