Compare commits
30 Commits
derive-doc
...
achalmers/
Author | SHA1 | Date | |
---|---|---|---|
a947e23ef9 | |||
76de64780c | |||
2d804dee2b | |||
c094d0ced1 | |||
a90fe3c066 | |||
f3ea7fd0e2 | |||
704ff0df62 | |||
eba17e92b7 | |||
d9d700624e | |||
1e547aeef0 | |||
22899c9e69 | |||
25702f454c | |||
11faf86983 | |||
67d73382b1 | |||
15b9f43f2c | |||
d28555a070 | |||
7bf116629f | |||
fe45b5b54d | |||
bcbd3f5bfd | |||
959433e357 | |||
b3695c060d | |||
09374081ea | |||
3d40650cab | |||
d18e35b7ea | |||
596c9a0ee6 | |||
9106a81c77 | |||
8b5ebe67b2 | |||
a7f539eca6 | |||
f4c87c994c | |||
3d4ae05145 |
12
.github/dependabot.yml
vendored
@ -9,15 +9,27 @@ updates:
|
||||
directory: '/' # Location of package manifests
|
||||
schedule:
|
||||
interval: 'daily'
|
||||
reviewers:
|
||||
- franknoirot
|
||||
- irev-dev
|
||||
- package-ecosystem: 'github-actions' # See documentation for possible values
|
||||
directory: '/' # Location of package manifests
|
||||
schedule:
|
||||
interval: 'daily'
|
||||
reviewers:
|
||||
- adamchalmers
|
||||
- jessfraz
|
||||
- package-ecosystem: 'cargo' # See documentation for possible values
|
||||
directory: '/src/wasm-lib/' # Location of package manifests
|
||||
schedule:
|
||||
interval: 'daily'
|
||||
reviewers:
|
||||
- adamchalmers
|
||||
- jessfraz
|
||||
- package-ecosystem: 'cargo' # See documentation for possible values
|
||||
directory: '/src-tauri/' # Location of package manifests
|
||||
schedule:
|
||||
interval: 'daily'
|
||||
reviewers:
|
||||
- adamchalmers
|
||||
- jessfraz
|
||||
|
5
.github/workflows/generate-website-docs.yml
vendored
@ -15,7 +15,7 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
jobs:
|
||||
generate-website-docs:
|
||||
name: generate-website-docs
|
||||
name: generate-website-docs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@ -56,6 +56,9 @@ jobs:
|
||||
gh pr create --title "Update KCL docs" \
|
||||
--body "Updating the generated kcl docs cc @jessfraz @franknoirot merge this" \
|
||||
--head "$NEW_BRANCH" \
|
||||
--reviewer jessfraz \
|
||||
--reviewer irev-dev \
|
||||
--reviewer franknoirot \
|
||||
--base main || true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
|
@ -34,7 +34,7 @@ const part = startSketchOn('XY')
|
||||
{
|
||||
// The arc angle (in degrees) to place the repetitions. Must be greater than 0.
|
||||
arcDegrees: number,
|
||||
// The center about which to make th pattern. This is a 2D vector.
|
||||
// The center about which to make the pattern. This is a 2D vector.
|
||||
center: [number, number],
|
||||
// The number of repetitions. Must be greater than 0. This excludes the original entity. For example, if `repetitions` is 1, the original entity will be copied once.
|
||||
repetitions: number,
|
||||
|
@ -42,7 +42,7 @@ const part = startSketchOn('XY')
|
||||
arcDegrees: number,
|
||||
// The axis around which to make the pattern. This is a 3D vector.
|
||||
axis: [number, number, number],
|
||||
// The center about which to make th pattern. This is a 3D vector.
|
||||
// The center about which to make the pattern. This is a 3D vector.
|
||||
center: [number, number, number],
|
||||
// The number of repetitions. Must be greater than 0. This excludes the original entity. For example, if `repetitions` is 1, the original entity will be copied once.
|
||||
repetitions: number,
|
||||
|
@ -42165,7 +42165,7 @@
|
||||
"format": "double"
|
||||
},
|
||||
"center": {
|
||||
"description": "The center about which to make th pattern. This is a 2D vector.",
|
||||
"description": "The center about which to make the pattern. This is a 2D vector.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "number",
|
||||
@ -44168,7 +44168,7 @@
|
||||
"minItems": 3
|
||||
},
|
||||
"center": {
|
||||
"description": "The center about which to make th pattern. This is a 3D vector.",
|
||||
"description": "The center about which to make the pattern. This is a 3D vector.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "number",
|
||||
|
@ -2,10 +2,15 @@ import { test, expect } from '@playwright/test'
|
||||
import { getUtils } from './test-utils'
|
||||
import waitOn from 'wait-on'
|
||||
import { roundOff } from 'lib/utils'
|
||||
import { basicStorageState } from './storageStates'
|
||||
import * as TOML from '@iarna/toml'
|
||||
import { SaveSettingsPayload } from 'lib/settings/settingsTypes'
|
||||
import { Themes } from 'lib/theme'
|
||||
import { secrets } from './secrets'
|
||||
import {
|
||||
TEST_SETTINGS,
|
||||
TEST_SETTINGS_KEY,
|
||||
TEST_SETTINGS_CORRUPTED,
|
||||
TEST_SETTINGS_ONBOARDING,
|
||||
} from './storageStates'
|
||||
|
||||
/*
|
||||
debug helper: unfortunately we do rely on exact coord mouse clicks in a few places
|
||||
@ -32,13 +37,25 @@ test.beforeEach(async ({ context, page }) => {
|
||||
timeout: 5000,
|
||||
})
|
||||
|
||||
await context.addInitScript(
|
||||
async ({ token, settingsKey, settings }) => {
|
||||
localStorage.setItem('TOKEN_PERSIST_KEY', token)
|
||||
localStorage.setItem('persistCode', ``)
|
||||
localStorage.setItem(settingsKey, settings)
|
||||
},
|
||||
{
|
||||
token: secrets.token,
|
||||
settingsKey: TEST_SETTINGS_KEY,
|
||||
settings: TOML.stringify({ settings: TEST_SETTINGS }),
|
||||
}
|
||||
)
|
||||
// kill animations, speeds up tests and reduced flakiness
|
||||
await page.emulateMedia({ reducedMotion: 'reduce' })
|
||||
})
|
||||
|
||||
test.setTimeout(60000)
|
||||
|
||||
test('Basic sketch', async ({ page, context }) => {
|
||||
test('Basic sketch', async ({ page }) => {
|
||||
const u = getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
@ -308,9 +325,9 @@ test('if you write invalid kcl you get inlined errors', async ({ page }) => {
|
||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||
})
|
||||
|
||||
test('executes on load', async ({ page, context }) => {
|
||||
test('executes on load', async ({ page }) => {
|
||||
const u = getUtils(page)
|
||||
await context.addInitScript(async () => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const part001 = startSketchOn('-XZ')
|
||||
@ -340,9 +357,9 @@ test('executes on load', async ({ page, context }) => {
|
||||
).toBeVisible()
|
||||
})
|
||||
|
||||
test('re-executes', async ({ page, context }) => {
|
||||
test('re-executes', async ({ page }) => {
|
||||
const u = getUtils(page)
|
||||
await context.addInitScript(async (token) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem('persistCode', `const myVar = 5`)
|
||||
})
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
@ -512,134 +529,131 @@ test('Auto complete works', async ({ page }) => {
|
||||
|> xLine(5, %) // lin`)
|
||||
})
|
||||
|
||||
// Stored settings validation test
|
||||
test.describe('Settings persistence and validation tests', () => {
|
||||
// Override test setup
|
||||
test('Stored settings are validated and fall back to defaults', async ({
|
||||
page,
|
||||
}) => {
|
||||
// Override beforeEach test setup
|
||||
// with corrupted settings
|
||||
const storageState = structuredClone(basicStorageState)
|
||||
const s = TOML.parse(storageState.origins[0].localStorage[2].value) as {
|
||||
settings: SaveSettingsPayload
|
||||
}
|
||||
s.settings.app.theme = Themes.Dark
|
||||
s.settings.app.projectDirectory = 123 as any
|
||||
s.settings.modeling.defaultUnit = 'invalid' as any
|
||||
s.settings.modeling.mouseControls = `() => alert('hack the planet')` as any
|
||||
s.settings.projects.defaultProjectName = false as any
|
||||
storageState.origins[0].localStorage[2].value = TOML.stringify(s)
|
||||
await page.addInitScript(
|
||||
async ({ settingsKey, settings }) => {
|
||||
localStorage.setItem(settingsKey, settings)
|
||||
},
|
||||
{
|
||||
settingsKey: TEST_SETTINGS_KEY,
|
||||
settings: TOML.stringify({ settings: TEST_SETTINGS_CORRUPTED }),
|
||||
}
|
||||
)
|
||||
|
||||
test.use({ storageState })
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.goto('/')
|
||||
|
||||
test('Stored settings are validated and fall back to defaults', async ({
|
||||
page,
|
||||
}) => {
|
||||
const u = getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.goto('/')
|
||||
await u.waitForAuthSkipAppStart()
|
||||
// Check the settings were reset
|
||||
const storedSettings = TOML.parse(
|
||||
await page.evaluate(
|
||||
({ settingsKey }) => localStorage.getItem(settingsKey) || '{}',
|
||||
{ settingsKey: TEST_SETTINGS_KEY }
|
||||
)
|
||||
) as { settings: SaveSettingsPayload }
|
||||
|
||||
// Check the settings were reset
|
||||
const storedSettings = TOML.parse(
|
||||
await page.evaluate(() => localStorage.getItem('/user.toml') || '{}')
|
||||
) as { settings: SaveSettingsPayload }
|
||||
expect(storedSettings.settings.app?.theme).toBe('dark')
|
||||
|
||||
expect(storedSettings.settings.app?.theme).toBe('dark')
|
||||
|
||||
// Check that the invalid settings were removed
|
||||
expect(storedSettings.settings.modeling?.defaultUnit).toBe(undefined)
|
||||
expect(storedSettings.settings.modeling?.mouseControls).toBe(undefined)
|
||||
expect(storedSettings.settings.app?.projectDirectory).toBe(undefined)
|
||||
expect(storedSettings.settings.projects?.defaultProjectName).toBe(undefined)
|
||||
})
|
||||
|
||||
test('Project settings can be set and override user settings', async ({
|
||||
page,
|
||||
}) => {
|
||||
const u = getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.goto('/')
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// Open the settings modal with the browser keyboard shortcut
|
||||
await page.keyboard.press('Meta+Shift+,')
|
||||
|
||||
await expect(
|
||||
page.getByRole('heading', { name: 'Settings', exact: true })
|
||||
).toBeVisible()
|
||||
await page
|
||||
.locator('select[name="app-theme"]')
|
||||
.selectOption({ value: 'light' })
|
||||
|
||||
// Verify the toast appeared
|
||||
await expect(
|
||||
page.getByText(`Set theme to "light" for this project`)
|
||||
).toBeVisible()
|
||||
// Check that the theme changed
|
||||
await expect(page.locator('body')).not.toHaveClass(`body-bg dark`)
|
||||
|
||||
// Check that the user setting was not changed
|
||||
await page.getByRole('radio', { name: 'User' }).click()
|
||||
await expect(page.locator('select[name="app-theme"]')).toHaveValue('dark')
|
||||
|
||||
// Roll back to default "system" theme
|
||||
await page
|
||||
.getByText(
|
||||
'themeRoll back themeRoll back to match defaultThe overall appearance of the appl'
|
||||
)
|
||||
.hover()
|
||||
await page
|
||||
.getByRole('button', {
|
||||
name: 'Roll back theme ; Has tooltip: Roll back to match default',
|
||||
})
|
||||
.click()
|
||||
await expect(page.locator('select[name="app-theme"]')).toHaveValue('system')
|
||||
|
||||
// Check that the project setting did not change
|
||||
await page.getByRole('radio', { name: 'Project' }).click()
|
||||
await expect(page.locator('select[name="app-theme"]')).toHaveValue('light')
|
||||
})
|
||||
// Check that the invalid settings were removed
|
||||
expect(storedSettings.settings.modeling?.defaultUnit).toBe(undefined)
|
||||
expect(storedSettings.settings.modeling?.mouseControls).toBe(undefined)
|
||||
expect(storedSettings.settings.app?.projectDirectory).toBe(undefined)
|
||||
expect(storedSettings.settings.projects?.defaultProjectName).toBe(undefined)
|
||||
})
|
||||
|
||||
// Onboarding tests
|
||||
test.describe('Onboarding tests', () => {
|
||||
// Override test setup
|
||||
const storageState = structuredClone(basicStorageState)
|
||||
const s = TOML.parse(storageState.origins[0].localStorage[2].value) as {
|
||||
settings: SaveSettingsPayload
|
||||
}
|
||||
s.settings.app.onboardingStatus = '/export'
|
||||
storageState.origins[0].localStorage[2].value = TOML.stringify(s)
|
||||
test.use({ storageState })
|
||||
test('Project settings can be set and override user settings', async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.goto('/', { waitUntil: 'domcontentloaded' })
|
||||
await page
|
||||
.getByRole('button', { name: 'Start Sketch' })
|
||||
.waitFor({ state: 'visible' })
|
||||
|
||||
test('Onboarding redirects and code updating', async ({ page, context }) => {
|
||||
const u = getUtils(page)
|
||||
// Open the settings modal with the browser keyboard shortcut
|
||||
await page.keyboard.press('Meta+Shift+,')
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.goto('/')
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await expect(
|
||||
page.getByRole('heading', { name: 'Settings', exact: true })
|
||||
).toBeVisible()
|
||||
await page
|
||||
.locator('select[name="app-theme"]')
|
||||
.selectOption({ value: 'light' })
|
||||
|
||||
// Test that the redirect happened
|
||||
await expect(page.url().split(':3000').slice(-1)[0]).toBe(
|
||||
`/file/%2Fbrowser%2Fmain.kcl/onboarding/export`
|
||||
// Verify the toast appeared
|
||||
await expect(
|
||||
page.getByText(`Set theme to "light" for this project`)
|
||||
).toBeVisible()
|
||||
// Check that the theme changed
|
||||
await expect(page.locator('body')).not.toHaveClass(`body-bg dark`)
|
||||
|
||||
// Check that the user setting was not changed
|
||||
await page.getByRole('radio', { name: 'User' }).click()
|
||||
await expect(page.locator('select[name="app-theme"]')).toHaveValue('dark')
|
||||
|
||||
// Roll back to default "system" theme
|
||||
await page
|
||||
.getByText(
|
||||
'themeRoll back themeRoll back to match defaultThe overall appearance of the appl'
|
||||
)
|
||||
.hover()
|
||||
await page
|
||||
.getByRole('button', {
|
||||
name: 'Roll back theme ; Has tooltip: Roll back to match default',
|
||||
})
|
||||
.click()
|
||||
await expect(page.locator('select[name="app-theme"]')).toHaveValue('system')
|
||||
|
||||
// Test that you come back to this page when you refresh
|
||||
await page.reload()
|
||||
await expect(page.url().split(':3000').slice(-1)[0]).toBe(
|
||||
`/file/%2Fbrowser%2Fmain.kcl/onboarding/export`
|
||||
)
|
||||
// Check that the project setting did not change
|
||||
await page.getByRole('radio', { name: 'Project' }).click()
|
||||
await expect(page.locator('select[name="app-theme"]')).toHaveValue('light')
|
||||
})
|
||||
|
||||
// Test that the onboarding pane loaded
|
||||
const title = page.locator('[data-testid="onboarding-content"]')
|
||||
await expect(title).toBeAttached()
|
||||
test('Onboarding redirects and code updating', async ({ page }) => {
|
||||
const u = getUtils(page)
|
||||
|
||||
// Test that the code changes when you advance to the next step
|
||||
await page.locator('[data-testid="onboarding-next"]').click()
|
||||
await expect(page.locator('.cm-content')).toHaveText('')
|
||||
// Override beforeEach test setup
|
||||
await page.addInitScript(
|
||||
async ({ settingsKey, settings }) => {
|
||||
// Give some initial code, so we can test that it's cleared
|
||||
localStorage.setItem('persistCode', 'const sigmaAllow = 15000')
|
||||
localStorage.setItem(settingsKey, settings)
|
||||
},
|
||||
{
|
||||
settingsKey: TEST_SETTINGS_KEY,
|
||||
settings: TOML.stringify({ settings: TEST_SETTINGS_ONBOARDING }),
|
||||
}
|
||||
)
|
||||
|
||||
// Test that the code is not empty when you click on the next step
|
||||
await page.locator('[data-testid="onboarding-next"]').click()
|
||||
await expect(page.locator('.cm-content')).toHaveText(/.+/)
|
||||
})
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.goto('/')
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// Test that the redirect happened
|
||||
await expect(page.url().split(':3000').slice(-1)[0]).toBe(
|
||||
`/file/%2Fbrowser%2Fmain.kcl/onboarding/export`
|
||||
)
|
||||
|
||||
// Test that you come back to this page when you refresh
|
||||
await page.reload()
|
||||
await expect(page.url().split(':3000').slice(-1)[0]).toBe(
|
||||
`/file/%2Fbrowser%2Fmain.kcl/onboarding/export`
|
||||
)
|
||||
|
||||
// Test that the onboarding pane loaded
|
||||
const title = page.locator('[data-testid="onboarding-content"]')
|
||||
await expect(title).toBeAttached()
|
||||
|
||||
// Test that the code changes when you advance to the next step
|
||||
await page.locator('[data-testid="onboarding-next"]').click()
|
||||
await expect(page.locator('.cm-content')).toHaveText('')
|
||||
|
||||
// Test that the code is not empty when you click on the next step
|
||||
await page.locator('[data-testid="onboarding-next"]').click()
|
||||
await expect(page.locator('.cm-content')).toHaveText(/.+/)
|
||||
})
|
||||
|
||||
test('Selections work on fresh and edited sketch', async ({ page }) => {
|
||||
@ -851,19 +865,22 @@ test.describe('Command bar tests', () => {
|
||||
await expect(page.locator('body')).not.toHaveClass(`body-bg dark`)
|
||||
})
|
||||
|
||||
// Override test setup code
|
||||
const storageState = structuredClone(basicStorageState)
|
||||
storageState.origins[0].localStorage[1].value = `const distance = sqrt(20)
|
||||
const part001 = startSketchOn('-XZ')
|
||||
|> startProfileAt([-6.95, 4.98], %)
|
||||
|> line([25.1, 0.41], %)
|
||||
|> line([0.73, -14.93], %)
|
||||
|> line([-23.44, 0.52], %)
|
||||
|> close(%)
|
||||
`
|
||||
test.use({ storageState })
|
||||
test('Can extrude from the command bar', async ({ page }) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`
|
||||
const distance = sqrt(20)
|
||||
const part001 = startSketchOn('-XZ')
|
||||
|> startProfileAt([-6.95, 4.98], %)
|
||||
|> line([25.1, 0.41], %)
|
||||
|> line([0.73, -14.93], %)
|
||||
|> line([-23.44, 0.52], %)
|
||||
|> close(%)
|
||||
`
|
||||
)
|
||||
})
|
||||
|
||||
test('Can extrude from the command bar', async ({ page, context }) => {
|
||||
const u = getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.goto('/')
|
||||
@ -1055,9 +1072,9 @@ const part002 = startSketchOn('XY')
|
||||
)
|
||||
})
|
||||
|
||||
test('ProgramMemory can be serialised', async ({ page, context }) => {
|
||||
test('ProgramMemory can be serialised', async ({ page }) => {
|
||||
const u = getUtils(page)
|
||||
await context.addInitScript(async () => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const part = startSketchOn('XY')
|
||||
@ -1096,7 +1113,6 @@ test('ProgramMemory can be serialised', async ({ page, context }) => {
|
||||
|
||||
test("Various pipe expressions should and shouldn't allow edit and or extrude", async ({
|
||||
page,
|
||||
context,
|
||||
}) => {
|
||||
const u = getUtils(page)
|
||||
const selectionsSnippets = {
|
||||
@ -1105,7 +1121,7 @@ test("Various pipe expressions should and shouldn't allow edit and or extrude",
|
||||
extrudeAndEditAllowed: '|> startProfileAt([15.72, 4.7], %)',
|
||||
editOnly: '|> startProfileAt([15.79, -14.6], %)',
|
||||
}
|
||||
await context.addInitScript(
|
||||
await page.addInitScript(
|
||||
async ({
|
||||
extrudeAndEditBlocked,
|
||||
extrudeAndEditBlockedInFunction,
|
||||
@ -1265,12 +1281,9 @@ test('Deselecting line tool should mean nothing happens on click', async ({
|
||||
previousCodeContent = await page.locator('.cm-content').innerText()
|
||||
})
|
||||
|
||||
test('Can edit segments by dragging their handles', async ({
|
||||
page,
|
||||
context,
|
||||
}) => {
|
||||
test('Can edit segments by dragging their handles', async ({ page }) => {
|
||||
const u = getUtils(page)
|
||||
await context.addInitScript(async () => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const part001 = startSketchOn('-XZ')
|
||||
@ -1422,9 +1435,9 @@ test('Snap to close works (at any scale)', async ({ page }) => {
|
||||
await doSnapAtDifferentScales([0, 10000, 10000], codeTemplate())
|
||||
})
|
||||
|
||||
test('Sketch on face', async ({ page, context }) => {
|
||||
test('Sketch on face', async ({ page }) => {
|
||||
const u = getUtils(page)
|
||||
await context.addInitScript(async () => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const part001 = startSketchOn('-XZ')
|
||||
|
@ -7,16 +7,26 @@ import { spawn } from 'child_process'
|
||||
import { APP_NAME } from 'lib/constants'
|
||||
import JSZip from 'jszip'
|
||||
import path from 'path'
|
||||
import { basicSettings, basicStorageState } from './storageStates'
|
||||
import { TEST_SETTINGS, TEST_SETTINGS_KEY } from './storageStates'
|
||||
import * as TOML from '@iarna/toml'
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// reducedMotion kills animations, which speeds up tests and reduces flakiness
|
||||
await page.emulateMedia({ reducedMotion: 'reduce' })
|
||||
})
|
||||
|
||||
test.use({
|
||||
storageState: structuredClone(basicStorageState),
|
||||
// set the default settings
|
||||
await page.addInitScript(
|
||||
async ({ token, settingsKey, settings }) => {
|
||||
localStorage.setItem('TOKEN_PERSIST_KEY', token)
|
||||
localStorage.setItem('persistCode', ``)
|
||||
localStorage.setItem(settingsKey, settings)
|
||||
},
|
||||
{
|
||||
token: secrets.token,
|
||||
settingsKey: TEST_SETTINGS_KEY,
|
||||
settings: TOML.stringify({ settings: TEST_SETTINGS }),
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test.setTimeout(60_000)
|
||||
@ -364,13 +374,15 @@ test('extrude on each default plane should be stable', async ({
|
||||
await u.removeCurrentCode()
|
||||
// add makeCode('XZ')
|
||||
await u.openAndClearDebugPanel()
|
||||
await page.locator('.cm-content').fill(makeCode(plane))
|
||||
await u.doAndWaitForImageDiff(
|
||||
() => page.locator('.cm-content').fill(makeCode(plane)),
|
||||
200
|
||||
)
|
||||
// wait for execution done
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.clearAndCloseDebugPanel()
|
||||
|
||||
await page.getByText('Code').click()
|
||||
await page.waitForTimeout(150)
|
||||
await expect(page).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
})
|
||||
@ -445,105 +457,108 @@ test('Draft segments should look right', async ({ page, context }) => {
|
||||
})
|
||||
})
|
||||
|
||||
test('Client side scene scale should match engine scale - Inch', async ({
|
||||
page,
|
||||
}) => {
|
||||
const u = getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.goto('/')
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await u.openDebugPanel()
|
||||
test.describe('Client side scene scale should match engine scale', () => {
|
||||
test('Inch', async ({ page }) => {
|
||||
const u = getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.goto('/')
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await u.openDebugPanel()
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled()
|
||||
await expect(page.getByRole('button', { name: 'Start Sketch' })).toBeVisible()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).toBeVisible()
|
||||
|
||||
// click on "Start Sketch" button
|
||||
await u.clearCommandLogs()
|
||||
await u.doAndWaitForImageDiff(
|
||||
() => page.getByRole('button', { name: 'Start Sketch' }).click(),
|
||||
200
|
||||
)
|
||||
// click on "Start Sketch" button
|
||||
await u.clearCommandLogs()
|
||||
await u.doAndWaitForImageDiff(
|
||||
() => page.getByRole('button', { name: 'Start Sketch' }).click(),
|
||||
200
|
||||
)
|
||||
|
||||
// select a plane
|
||||
await page.mouse.click(700, 200)
|
||||
// select a plane
|
||||
await page.mouse.click(700, 200)
|
||||
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`const part001 = startSketchOn('-XZ')`
|
||||
)
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`const part001 = startSketchOn('-XZ')`
|
||||
)
|
||||
|
||||
await page.waitForTimeout(300) // TODO detect animation ending, or disable animation
|
||||
await page.waitForTimeout(300) // TODO detect animation ending, or disable animation
|
||||
|
||||
const startXPx = 600
|
||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||
|> startProfileAt([9.06, -12.22], %)`)
|
||||
await page.waitForTimeout(100)
|
||||
const startXPx = 600
|
||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||
|> startProfileAt([9.06, -12.22], %)`)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await u.closeDebugPanel()
|
||||
await u.closeDebugPanel()
|
||||
|
||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
||||
await page.waitForTimeout(100)
|
||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||
|> startProfileAt([9.06, -12.22], %)
|
||||
|> line([9.14, 0], %)`)
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||
|> startProfileAt([9.06, -12.22], %)
|
||||
|> line([9.14, 0], %)`)
|
||||
|
||||
await page.getByRole('button', { name: 'Tangential Arc' }).click()
|
||||
await page.waitForTimeout(100)
|
||||
await page.getByRole('button', { name: 'Tangential Arc' }).click()
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await page.mouse.click(startXPx + PUR * 30, 500 - PUR * 20)
|
||||
await page.mouse.click(startXPx + PUR * 30, 500 - PUR * 20)
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||
|> startProfileAt([9.06, -12.22], %)
|
||||
|> line([9.14, 0], %)
|
||||
|> tangentialArcTo([27.34, -3.08], %)`)
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||
|> startProfileAt([9.06, -12.22], %)
|
||||
|> line([9.14, 0], %)
|
||||
|> tangentialArcTo([27.34, -3.08], %)`)
|
||||
|
||||
// click tangential arc tool again to unequip it
|
||||
await page.getByRole('button', { name: 'Tangential Arc' }).click()
|
||||
await page.waitForTimeout(100)
|
||||
// click tangential arc tool again to unequip it
|
||||
await page.getByRole('button', { name: 'Tangential Arc' }).click()
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
// screen shot should show the sketch
|
||||
await expect(page).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
})
|
||||
// screen shot should show the sketch
|
||||
await expect(page).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
})
|
||||
|
||||
// exit sketch
|
||||
await u.openAndClearDebugPanel()
|
||||
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||
// exit sketch
|
||||
await u.openAndClearDebugPanel()
|
||||
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||
|
||||
// wait for execution done
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.clearAndCloseDebugPanel()
|
||||
await page.waitForTimeout(200)
|
||||
// wait for execution done
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.clearAndCloseDebugPanel()
|
||||
await page.waitForTimeout(200)
|
||||
|
||||
// second screen shot should look almost identical, i.e. scale should be the same.
|
||||
await expect(page).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Client side scene scale should match engine scale - Millimeters', () => {
|
||||
const storageState = structuredClone(basicStorageState)
|
||||
storageState.origins[0].localStorage[2].value = TOML.stringify({
|
||||
settings: {
|
||||
...basicSettings,
|
||||
modeling: {
|
||||
...basicSettings.modeling,
|
||||
defaultUnit: 'mm',
|
||||
},
|
||||
},
|
||||
})
|
||||
test.use({
|
||||
storageState,
|
||||
// second screen shot should look almost identical, i.e. scale should be the same.
|
||||
await expect(page).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
})
|
||||
})
|
||||
|
||||
test('Millimeters', async ({ page }) => {
|
||||
await page.addInitScript(
|
||||
async ({ settingsKey, settings }) => {
|
||||
localStorage.setItem(settingsKey, settings)
|
||||
},
|
||||
{
|
||||
settingsKey: TEST_SETTINGS_KEY,
|
||||
settings: TOML.stringify({
|
||||
settings: {
|
||||
...TEST_SETTINGS,
|
||||
modeling: {
|
||||
...TEST_SETTINGS.modeling,
|
||||
defaultUnit: 'mm',
|
||||
},
|
||||
},
|
||||
}),
|
||||
}
|
||||
)
|
||||
const u = getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
|
After Width: | Height: | Size: 43 KiB |
After Width: | Height: | Size: 47 KiB |
After Width: | Height: | Size: 47 KiB |
After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
@ -1,9 +1,8 @@
|
||||
import { SaveSettingsPayload } from 'lib/settings/settingsTypes'
|
||||
import { secrets } from './secrets'
|
||||
import * as TOML from '@iarna/toml'
|
||||
import { Themes } from 'lib/theme'
|
||||
|
||||
export const basicSettings = {
|
||||
export const TEST_SETTINGS_KEY = '/user.toml'
|
||||
export const TEST_SETTINGS = {
|
||||
app: {
|
||||
theme: Themes.Dark,
|
||||
onboardingStatus: 'dismissed',
|
||||
@ -22,19 +21,26 @@ export const basicSettings = {
|
||||
},
|
||||
} satisfies Partial<SaveSettingsPayload>
|
||||
|
||||
export const basicStorageState = {
|
||||
cookies: [],
|
||||
origins: [
|
||||
{
|
||||
origin: 'http://localhost:3000',
|
||||
localStorage: [
|
||||
{ name: 'TOKEN_PERSIST_KEY', value: secrets.token },
|
||||
{ name: 'persistCode', value: '' },
|
||||
{
|
||||
name: '/user.toml',
|
||||
value: TOML.stringify({ settings: basicSettings }),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
export const TEST_SETTINGS_ONBOARDING = {
|
||||
...TEST_SETTINGS,
|
||||
app: { ...TEST_SETTINGS.app, onboardingStatus: '/export ' },
|
||||
} satisfies Partial<SaveSettingsPayload>
|
||||
|
||||
export const TEST_SETTINGS_CORRUPTED = {
|
||||
app: {
|
||||
theme: Themes.Dark,
|
||||
onboardingStatus: 'dismissed',
|
||||
projectDirectory: 123 as any,
|
||||
},
|
||||
modeling: {
|
||||
defaultUnit: 'invalid' as any,
|
||||
mouseControls: `() => alert('hack the planet')` as any,
|
||||
showDebugPanel: true,
|
||||
},
|
||||
projects: {
|
||||
defaultProjectName: false as any,
|
||||
},
|
||||
textEditor: {
|
||||
textWrapping: true,
|
||||
},
|
||||
} satisfies Partial<SaveSettingsPayload>
|
||||
|
29
package.json
@ -4,8 +4,8 @@
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.15.0",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.4.2",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.4.2",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.5.2",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.5.2",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.4.2",
|
||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||
"@headlessui/react": "^1.7.18",
|
||||
@ -16,7 +16,7 @@
|
||||
"@open-rpc/client-js": "^1.8.1",
|
||||
"@react-hook/resize-observer": "^1.2.6",
|
||||
"@replit/codemirror-interact": "^6.3.0",
|
||||
"@tauri-apps/api": "^2.0.0-beta.7",
|
||||
"@tauri-apps/api": "2.0.0-beta.7",
|
||||
"@tauri-apps/plugin-dialog": "^2.0.0-beta.2",
|
||||
"@tauri-apps/plugin-fs": "^2.0.0-beta.2",
|
||||
"@tauri-apps/plugin-http": "^2.0.0-beta.2",
|
||||
@ -27,8 +27,8 @@
|
||||
"@testing-library/user-event": "^14.5.2",
|
||||
"@ts-stack/markdown": "^1.5.0",
|
||||
"@tweenjs/tween.js": "^23.1.1",
|
||||
"@types/node": "^18.19.26",
|
||||
"@types/react": "^18.2.73",
|
||||
"@types/node": "^18.19.31",
|
||||
"@types/react": "^18.2.75",
|
||||
"@types/react-dom": "^18.2.22",
|
||||
"@uiw/react-codemirror": "^4.21.25",
|
||||
"@xstate/inspect": "^0.8.0",
|
||||
@ -36,8 +36,9 @@
|
||||
"crypto-js": "^4.2.0",
|
||||
"debounce-promise": "^3.1.2",
|
||||
"decamelize": "^6.0.0",
|
||||
"formik": "^2.4.3",
|
||||
"formik": "^2.4.5",
|
||||
"fuse.js": "^7.0.0",
|
||||
"html2canvas-pro": "^1.4.3",
|
||||
"http-server": "^14.1.1",
|
||||
"json-rpc-2.0": "^1.6.0",
|
||||
"jszip": "^3.10.1",
|
||||
@ -52,18 +53,19 @@
|
||||
"react-modal-promise": "^1.0.2",
|
||||
"react-router-dom": "^6.22.3",
|
||||
"sketch-helpers": "^0.0.4",
|
||||
"swr": "^2.2.2",
|
||||
"three": "^0.160.0",
|
||||
"swr": "^2.2.5",
|
||||
"three": "^0.163.0",
|
||||
"toml": "^3.0.0",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.4.3",
|
||||
"ua-parser-js": "^1.0.37",
|
||||
"uuid": "^9.0.1",
|
||||
"vitest": "^1.4.0",
|
||||
"vscode-jsonrpc": "^8.1.0",
|
||||
"vscode-languageserver-protocol": "^3.17.5",
|
||||
"wasm-pack": "^0.12.1",
|
||||
"web-vitals": "^3.5.2",
|
||||
"ws": "^8.13.0",
|
||||
"ws": "^8.16.0",
|
||||
"xstate": "^4.38.2",
|
||||
"zustand": "^4.5.2"
|
||||
},
|
||||
@ -115,18 +117,19 @@
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||
"@babel/preset-env": "^7.24.3",
|
||||
"@playwright/test": "^1.39.0",
|
||||
"@playwright/test": "^1.43.0",
|
||||
"@tauri-apps/cli": "^2.0.0-beta.12",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/debounce-promise": "^3.1.9",
|
||||
"@types/pixelmatch": "^5.2.6",
|
||||
"@types/pngjs": "^6.0.4",
|
||||
"@types/react-modal": "^3.16.3",
|
||||
"@types/three": "^0.160.0",
|
||||
"@types/three": "^0.163.0",
|
||||
"@types/ua-parser-js": "^0.7.39",
|
||||
"@types/uuid": "^9.0.8",
|
||||
"@types/wait-on": "^5.3.4",
|
||||
"@types/wicg-file-system-access": "^2023.10.5",
|
||||
"@types/ws": "^8.5.5",
|
||||
"@types/ws": "^8.5.10",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"@wdio/cli": "^8.24.3",
|
||||
"@wdio/globals": "^8.24.3",
|
||||
@ -153,6 +156,6 @@
|
||||
"vite-tsconfig-paths": "^4.3.2",
|
||||
"vitest-webgl-canvas-mock": "^1.1.0",
|
||||
"wait-on": "^7.2.0",
|
||||
"yarn": "^1.22.19"
|
||||
"yarn": "^1.22.22"
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { defineConfig, devices } from '@playwright/test'
|
||||
import { basicStorageState } from './e2e/playwright/storageStates'
|
||||
|
||||
/**
|
||||
* Read environment variables from file.
|
||||
@ -29,9 +28,6 @@ export default defineConfig({
|
||||
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: 'on-first-retry',
|
||||
|
||||
/* Use a common shared localStorage */
|
||||
storageState: basicStorageState,
|
||||
},
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
|
10
src-tauri/Cargo.lock
generated
@ -69,9 +69,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.81"
|
||||
version = "1.0.82"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247"
|
||||
checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519"
|
||||
|
||||
[[package]]
|
||||
name = "app"
|
||||
@ -2231,9 +2231,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kittycad"
|
||||
version = "0.2.63"
|
||||
version = "0.2.66"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93a332250e08fd715ad3d5826e04d36da1c5bb42d0c1b1ff1f0598278b9ebf3c"
|
||||
checksum = "9e2897244f4600f863115561a0fd1cd7c87fca20253ffecfebc53ef642d0aceb"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@ -2333,7 +2333,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-targets 0.48.0",
|
||||
"windows-targets 0.52.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -16,7 +16,7 @@ tauri-build = { version = "2.0.0-beta", features = [] }
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
kittycad = "0.2.63"
|
||||
kittycad = "0.2.66"
|
||||
oauth2 = "4.4.2"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
|
12
src/App.tsx
@ -1,4 +1,4 @@
|
||||
import { useCallback, MouseEventHandler, useEffect } from 'react'
|
||||
import { useCallback, MouseEventHandler, useEffect, useRef } from 'react'
|
||||
import { DebugPanel } from './components/DebugPanel'
|
||||
import { uuidv4 } from 'lib/utils'
|
||||
import { PaneType, useStore } from './useStore'
|
||||
@ -41,6 +41,9 @@ export function App() {
|
||||
const navigate = useNavigate()
|
||||
const filePath = useAbsoluteFilePath()
|
||||
const { onProjectOpen } = useLspContext()
|
||||
// We need the ref for the outermost div so we can screenshot the app for
|
||||
// the coredump.
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
|
||||
const projectName = project?.name || null
|
||||
const projectPath = project?.path || null
|
||||
@ -55,14 +58,20 @@ export function App() {
|
||||
setOpenPanes,
|
||||
didDragInStream,
|
||||
streamDimensions,
|
||||
setHtmlRef,
|
||||
} = useStore((s) => ({
|
||||
buttonDownInStream: s.buttonDownInStream,
|
||||
openPanes: s.openPanes,
|
||||
setOpenPanes: s.setOpenPanes,
|
||||
didDragInStream: s.didDragInStream,
|
||||
streamDimensions: s.streamDimensions,
|
||||
setHtmlRef: s.setHtmlRef,
|
||||
}))
|
||||
|
||||
useEffect(() => {
|
||||
setHtmlRef(ref)
|
||||
}, [ref])
|
||||
|
||||
const { settings } = useSettingsAuthContext()
|
||||
const {
|
||||
modeling: { showDebugPanel },
|
||||
@ -140,6 +149,7 @@ export function App() {
|
||||
<div
|
||||
className="relative h-full flex flex-col"
|
||||
onMouseMove={handleMouseMove}
|
||||
ref={ref}
|
||||
>
|
||||
<AppHeader
|
||||
className={
|
||||
|
@ -38,7 +38,7 @@ import {
|
||||
getSketchQuaternion,
|
||||
} from 'clientSideScene/sceneEntities'
|
||||
import { sketchOnExtrudedFace, startSketchOnDefault } from 'lang/modifyAst'
|
||||
import { Program, parse } from 'lang/wasm'
|
||||
import { Program, coreDump, parse } from 'lang/wasm'
|
||||
import { getNodePathFromSourceRange, isSingleCursorInPipe } from 'lang/queryAst'
|
||||
import { TEST } from 'env'
|
||||
import { exportFromEngine } from 'lib/exportFromEngine'
|
||||
@ -47,6 +47,8 @@ import toast from 'react-hot-toast'
|
||||
import { EditorSelection } from '@uiw/react-codemirror'
|
||||
import { Vector3 } from 'three'
|
||||
import { quaternionFromUpNForward } from 'clientSideScene/helpers'
|
||||
import { CoreDumpManager } from 'lib/coredump'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
|
||||
type MachineContext<T extends AnyStateMachine> = {
|
||||
state: StateFrom<T>
|
||||
@ -76,6 +78,15 @@ export const ModelingMachineProvider = ({
|
||||
const token = auth?.context?.token
|
||||
const streamRef = useRef<HTMLDivElement>(null)
|
||||
useSetupEngineManager(streamRef, token, theme.current)
|
||||
const { htmlRef } = useStore((s) => ({
|
||||
htmlRef: s.htmlRef,
|
||||
}))
|
||||
const coreDumpManager = new CoreDumpManager(
|
||||
engineCommandManager,
|
||||
htmlRef,
|
||||
token
|
||||
)
|
||||
useHotkeys('meta + shift + .', () => coreDump(coreDumpManager, true))
|
||||
|
||||
const {
|
||||
isShiftDown,
|
||||
|
@ -241,7 +241,6 @@ export function extrudeSketch(
|
||||
pathToExtrudeArg: PathToNode
|
||||
} {
|
||||
const _node = { ...node }
|
||||
const dumbyStartend = { start: 0, end: 0 }
|
||||
const { node: sketchExpression } = getNodeFromPath(
|
||||
_node,
|
||||
pathToNode,
|
||||
@ -256,18 +255,14 @@ export function extrudeSketch(
|
||||
)
|
||||
const isInPipeExpression = pipeExpression.type === 'PipeExpression'
|
||||
|
||||
const { node: variableDeclorator, shallowPath: pathToDecleration } =
|
||||
const { node: variableDeclarator, shallowPath: pathToDecleration } =
|
||||
getNodeFromPath<VariableDeclarator>(_node, pathToNode, 'VariableDeclarator')
|
||||
|
||||
const extrudeCall = createCallExpressionStdLib('extrude', [
|
||||
distance,
|
||||
shouldPipe
|
||||
? createPipeSubstitution()
|
||||
: {
|
||||
type: 'Identifier',
|
||||
...dumbyStartend,
|
||||
name: variableDeclorator.id.name,
|
||||
},
|
||||
: createIdentifier(variableDeclarator.id.name),
|
||||
])
|
||||
|
||||
if (shouldPipe) {
|
||||
@ -277,7 +272,7 @@ export function extrudeSketch(
|
||||
: [sketchExpression as any, extrudeCall]
|
||||
)
|
||||
|
||||
variableDeclorator.init = pipeChain
|
||||
variableDeclarator.init = pipeChain
|
||||
const pathToExtrudeArg: PathToNode = [
|
||||
...pathToDecleration,
|
||||
['init', 'VariableDeclarator'],
|
||||
|
@ -25,7 +25,8 @@ interface CommandInfo {
|
||||
}
|
||||
}
|
||||
|
||||
type WebSocketResponse = Models['OkWebSocketResponseData_type']
|
||||
type WebSocketResponse = Models['WebSocketResponse_type']
|
||||
type OkWebSocketResponseData = Models['OkWebSocketResponseData_type']
|
||||
|
||||
interface ResultCommand extends CommandInfo {
|
||||
type: 'result'
|
||||
@ -37,10 +38,19 @@ interface FailedCommand extends CommandInfo {
|
||||
type: 'failed'
|
||||
errors: Models['FailureWebSocketResponse_type']['errors']
|
||||
}
|
||||
interface ResolveCommand {
|
||||
id: string
|
||||
commandType: CommandTypes
|
||||
range: SourceRange
|
||||
// We ALWAYS need the raw response because we pass it back to the rust side.
|
||||
raw: WebSocketResponse
|
||||
data?: Models['OkModelingCmdResponse_type']
|
||||
errors?: Models['FailureWebSocketResponse_type']['errors']
|
||||
}
|
||||
interface PendingCommand extends CommandInfo {
|
||||
type: 'pending'
|
||||
promise: Promise<any>
|
||||
resolve: (val: any) => void
|
||||
resolve: (val: ResolveCommand) => void
|
||||
}
|
||||
|
||||
export interface ArtifactMap {
|
||||
@ -59,6 +69,15 @@ type Timeout = ReturnType<typeof setTimeout>
|
||||
|
||||
type ClientMetrics = Models['ClientMetrics_type']
|
||||
|
||||
interface WebRTCClientMetrics extends ClientMetrics {
|
||||
rtc_frame_height: number
|
||||
rtc_frame_width: number
|
||||
rtc_packets_lost: number
|
||||
rtc_pli_count: number
|
||||
rtc_pause_count: number
|
||||
rtc_total_pauses_duration_sec: number
|
||||
}
|
||||
|
||||
type Value<T, U> = U extends undefined
|
||||
? { type: T; value: U }
|
||||
: U extends void
|
||||
@ -224,7 +243,7 @@ class EngineConnection {
|
||||
private onNewTrack: (track: NewTrackArgs) => void
|
||||
|
||||
// TODO: actual type is ClientMetrics
|
||||
private webrtcStatsCollector?: () => Promise<ClientMetrics>
|
||||
public webrtcStatsCollector?: () => Promise<WebRTCClientMetrics>
|
||||
private engineCommandManager: EngineCommandManager
|
||||
|
||||
constructor({
|
||||
@ -396,7 +415,7 @@ class EngineConnection {
|
||||
},
|
||||
}
|
||||
|
||||
this.webrtcStatsCollector = (): Promise<ClientMetrics> => {
|
||||
this.webrtcStatsCollector = (): Promise<WebRTCClientMetrics> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (mediaStream.getVideoTracks().length !== 1) {
|
||||
reject(new Error('too many video tracks to report'))
|
||||
@ -405,7 +424,7 @@ class EngineConnection {
|
||||
|
||||
let videoTrack = mediaStream.getVideoTracks()[0]
|
||||
void this.pc?.getStats(videoTrack).then((videoTrackStats) => {
|
||||
let client_metrics: ClientMetrics = {
|
||||
let client_metrics: WebRTCClientMetrics = {
|
||||
rtc_frames_decoded: 0,
|
||||
rtc_frames_dropped: 0,
|
||||
rtc_frames_received: 0,
|
||||
@ -414,6 +433,12 @@ class EngineConnection {
|
||||
rtc_jitter_sec: 0.0,
|
||||
rtc_keyframes_decoded: 0,
|
||||
rtc_total_freezes_duration_sec: 0.0,
|
||||
rtc_frame_height: 0,
|
||||
rtc_frame_width: 0,
|
||||
rtc_packets_lost: 0,
|
||||
rtc_pli_count: 0,
|
||||
rtc_pause_count: 0,
|
||||
rtc_total_pauses_duration_sec: 0.0,
|
||||
}
|
||||
|
||||
// TODO(paultag): Since we can technically have multiple WebRTC
|
||||
@ -439,6 +464,13 @@ class EngineConnection {
|
||||
videoTrackReport.keyFramesDecoded || 0
|
||||
client_metrics.rtc_total_freezes_duration_sec =
|
||||
videoTrackReport.totalFreezesDuration || 0
|
||||
client_metrics.rtc_frame_height =
|
||||
videoTrackReport.frameHeight || 0
|
||||
client_metrics.rtc_frame_width =
|
||||
videoTrackReport.frameWidth || 0
|
||||
client_metrics.rtc_packets_lost =
|
||||
videoTrackReport.packetsLost || 0
|
||||
client_metrics.rtc_pli_count = videoTrackReport.pliCount || 0
|
||||
} else if (videoTrackReport.type === 'transport') {
|
||||
// videoTrackReport.bytesReceived,
|
||||
// videoTrackReport.bytesSent,
|
||||
@ -827,7 +859,7 @@ export type CommandLog =
|
||||
}
|
||||
| {
|
||||
type: 'receive-reliable'
|
||||
data: WebSocketResponse
|
||||
data: OkWebSocketResponseData
|
||||
id: string
|
||||
cmd_type?: string
|
||||
}
|
||||
@ -1020,7 +1052,11 @@ export class EngineCommandManager {
|
||||
message.resp.type === 'modeling' &&
|
||||
message.request_id
|
||||
) {
|
||||
this.handleModelingCommand(message.resp, message.request_id)
|
||||
this.handleModelingCommand(
|
||||
message.resp,
|
||||
message.request_id,
|
||||
message
|
||||
)
|
||||
} else if (
|
||||
!message.success &&
|
||||
message.request_id &&
|
||||
@ -1069,7 +1105,11 @@ export class EngineCommandManager {
|
||||
}
|
||||
this.engineConnection?.send(resizeCmd)
|
||||
}
|
||||
handleModelingCommand(message: WebSocketResponse, id: string) {
|
||||
handleModelingCommand(
|
||||
message: OkWebSocketResponseData,
|
||||
id: string,
|
||||
raw: WebSocketResponse
|
||||
) {
|
||||
if (message.type !== 'modeling') {
|
||||
return
|
||||
}
|
||||
@ -1081,7 +1121,7 @@ export class EngineCommandManager {
|
||||
command?.additionalData?.type === 'batch-ids'
|
||||
) {
|
||||
command.additionalData.ids.forEach((id) => {
|
||||
this.handleModelingCommand(message, id)
|
||||
this.handleModelingCommand(message, id, raw)
|
||||
})
|
||||
// batch artifact is just a container, we don't need to keep it
|
||||
// once we process all the commands inside it
|
||||
@ -1092,7 +1132,7 @@ export class EngineCommandManager {
|
||||
commandType: command.commandType,
|
||||
range: command.range,
|
||||
data: modelingResponse,
|
||||
raw: message,
|
||||
raw,
|
||||
})
|
||||
return
|
||||
}
|
||||
@ -1116,7 +1156,7 @@ export class EngineCommandManager {
|
||||
commandType: command.commandType,
|
||||
parentId: command.parentId ? command.parentId : undefined,
|
||||
data: modelingResponse,
|
||||
raw: message,
|
||||
raw,
|
||||
} as const
|
||||
this.artifactMap[id] = artifact
|
||||
if (
|
||||
@ -1161,7 +1201,7 @@ export class EngineCommandManager {
|
||||
commandType: command.commandType,
|
||||
range: command.range,
|
||||
data: modelingResponse,
|
||||
raw: message,
|
||||
raw,
|
||||
})
|
||||
} else if (sceneCommand && sceneCommand.type === 'pending') {
|
||||
const resolve = sceneCommand.resolve
|
||||
@ -1172,7 +1212,7 @@ export class EngineCommandManager {
|
||||
commandType: sceneCommand.commandType,
|
||||
parentId: sceneCommand.parentId ? sceneCommand.parentId : undefined,
|
||||
data: modelingResponse,
|
||||
raw: message,
|
||||
raw,
|
||||
} as const
|
||||
this.sceneCommandArtifacts[id] = artifact
|
||||
resolve({
|
||||
@ -1180,6 +1220,7 @@ export class EngineCommandManager {
|
||||
commandType: sceneCommand.commandType,
|
||||
range: sceneCommand.range,
|
||||
data: modelingResponse,
|
||||
raw,
|
||||
})
|
||||
} else if (command) {
|
||||
this.artifactMap[id] = {
|
||||
@ -1188,7 +1229,7 @@ export class EngineCommandManager {
|
||||
range: command?.range,
|
||||
pathToNode: command?.pathToNode,
|
||||
data: modelingResponse,
|
||||
raw: message,
|
||||
raw,
|
||||
}
|
||||
} else {
|
||||
this.sceneCommandArtifacts[id] = {
|
||||
@ -1197,15 +1238,14 @@ export class EngineCommandManager {
|
||||
range: sceneCommand?.range,
|
||||
pathToNode: sceneCommand?.pathToNode,
|
||||
data: modelingResponse,
|
||||
raw: message,
|
||||
raw,
|
||||
}
|
||||
}
|
||||
}
|
||||
handleFailedModelingCommand({
|
||||
request_id,
|
||||
errors,
|
||||
}: Models['FailureWebSocketResponse_type']) {
|
||||
const id = request_id
|
||||
handleFailedModelingCommand(raw: WebSocketResponse) {
|
||||
const id = raw.request_id
|
||||
const failed = raw as Models['FailureWebSocketResponse_type']
|
||||
const errors = failed.errors
|
||||
if (!id) return
|
||||
const command = this.artifactMap[id]
|
||||
if (command && command.type === 'pending') {
|
||||
@ -1223,6 +1263,7 @@ export class EngineCommandManager {
|
||||
commandType: command.commandType,
|
||||
range: command.range,
|
||||
errors,
|
||||
raw,
|
||||
})
|
||||
} else {
|
||||
this.artifactMap[id] = {
|
||||
@ -1573,7 +1614,14 @@ export class EngineCommandManager {
|
||||
command: commandStr,
|
||||
ast: this.getAst(),
|
||||
idToRangeMap,
|
||||
}).then(({ raw }) => JSON.stringify(raw))
|
||||
}).then(({ raw }: { raw: WebSocketResponse | undefined | null }) => {
|
||||
if (raw === undefined || raw === null) {
|
||||
throw new Error(
|
||||
'returning modeling cmd response to the rust side is undefined or null'
|
||||
)
|
||||
}
|
||||
return JSON.stringify(raw)
|
||||
})
|
||||
}
|
||||
commandResult(id: string): Promise<any> {
|
||||
const command = this.artifactMap[id]
|
||||
|
@ -10,6 +10,7 @@ import init, {
|
||||
ServerConfig,
|
||||
copilot_lsp_run,
|
||||
kcl_lsp_run,
|
||||
coredump,
|
||||
} from '../wasm-lib/pkg/wasm_lib'
|
||||
import { KCLError } from './errors'
|
||||
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError'
|
||||
@ -21,6 +22,9 @@ import type { Token } from '../wasm-lib/kcl/bindings/Token'
|
||||
import { Coords2d } from './std/sketch'
|
||||
import { fileSystemManager } from 'lang/std/fileSystemManager'
|
||||
import { DEV } from 'env'
|
||||
import { AppInfo } from 'wasm-lib/kcl/bindings/AppInfo'
|
||||
import { CoreDumpManager } from 'lib/coredump'
|
||||
import openWindow from 'lib/openWindow'
|
||||
|
||||
export type { Program } from '../wasm-lib/kcl/bindings/Program'
|
||||
export type { Value } from '../wasm-lib/kcl/bindings/Value'
|
||||
@ -311,3 +315,18 @@ export async function kclLspRun(config: ServerConfig, token: string) {
|
||||
// We can't restart here because a moved value, we should do this another way.
|
||||
}
|
||||
}
|
||||
|
||||
export async function coreDump(
|
||||
coreDumpManager: CoreDumpManager,
|
||||
openGithubIssue: boolean = false
|
||||
): Promise<AppInfo> {
|
||||
try {
|
||||
const dump: AppInfo = await coredump(coreDumpManager)
|
||||
if (openGithubIssue && dump.github_issue_url) {
|
||||
openWindow(dump.github_issue_url)
|
||||
}
|
||||
return dump
|
||||
} catch (e: any) {
|
||||
throw new Error(`Error getting core dump: ${e}`)
|
||||
}
|
||||
}
|
||||
|
150
src/lib/coredump.ts
Normal file
@ -0,0 +1,150 @@
|
||||
import { EngineCommandManager } from 'lang/std/engineConnection'
|
||||
import { WebrtcStats } from 'wasm-lib/kcl/bindings/WebrtcStats'
|
||||
import { OsInfo } from 'wasm-lib/kcl/bindings/OsInfo'
|
||||
import { isTauri } from 'lib/isTauri'
|
||||
import {
|
||||
platform as tauriPlatform,
|
||||
arch as tauriArch,
|
||||
version as tauriKernelVersion,
|
||||
} from '@tauri-apps/plugin-os'
|
||||
import { APP_VERSION } from 'routes/Settings'
|
||||
import { UAParser } from 'ua-parser-js'
|
||||
import screenshot from 'lib/screenshot'
|
||||
import React from 'react'
|
||||
import { VITE_KC_API_BASE_URL } from 'env'
|
||||
|
||||
// This is a class for getting all the values from the JS world to pass to the Rust world
|
||||
// for a core dump.
|
||||
export class CoreDumpManager {
|
||||
engineCommandManager: EngineCommandManager
|
||||
htmlRef: React.RefObject<HTMLDivElement> | null
|
||||
token: string | undefined
|
||||
baseUrl: string = VITE_KC_API_BASE_URL
|
||||
|
||||
constructor(
|
||||
engineCommandManager: EngineCommandManager,
|
||||
htmlRef: React.RefObject<HTMLDivElement> | null,
|
||||
token: string | undefined
|
||||
) {
|
||||
this.engineCommandManager = engineCommandManager
|
||||
this.htmlRef = htmlRef
|
||||
this.token = token
|
||||
}
|
||||
|
||||
// Get the token.
|
||||
authToken(): string {
|
||||
if (!this.token) {
|
||||
throw new Error('Token not set')
|
||||
}
|
||||
return this.token
|
||||
}
|
||||
|
||||
// Get the base url.
|
||||
baseApiUrl(): string {
|
||||
return this.baseUrl
|
||||
}
|
||||
|
||||
// Get the version of the app from the package.json.
|
||||
version(): string {
|
||||
return APP_VERSION
|
||||
}
|
||||
|
||||
// Get the os information.
|
||||
getOsInfo(): Promise<string> {
|
||||
if (this.isTauri()) {
|
||||
return tauriArch()
|
||||
.catch((error: any) => {
|
||||
throw new Error(`Error getting arch: ${error}`)
|
||||
})
|
||||
.then((arch: string) => {
|
||||
return tauriPlatform()
|
||||
.catch((error: any) => {
|
||||
throw new Error(`Error getting platform: ${error}`)
|
||||
})
|
||||
.then((platform: string) => {
|
||||
return tauriKernelVersion()
|
||||
.catch((error: any) => {
|
||||
throw new Error(`Error getting kernel version: ${error}`)
|
||||
})
|
||||
.then((kernelVersion: string) => {
|
||||
const osinfo: OsInfo = {
|
||||
platform,
|
||||
arch,
|
||||
version: kernelVersion,
|
||||
}
|
||||
return JSON.stringify(osinfo)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const userAgent = window.navigator.userAgent || 'unknown browser'
|
||||
if (userAgent === 'unknown browser') {
|
||||
const osinfo: OsInfo = {
|
||||
platform: userAgent,
|
||||
arch: userAgent,
|
||||
version: userAgent,
|
||||
}
|
||||
return new Promise((resolve) => resolve(JSON.stringify(osinfo)))
|
||||
}
|
||||
|
||||
const parser = new UAParser(userAgent)
|
||||
const parserResults = parser.getResult()
|
||||
const osinfo: OsInfo = {
|
||||
platform: parserResults.os.name,
|
||||
arch: parserResults.cpu.architecture,
|
||||
version: parserResults.os.version,
|
||||
browser: userAgent,
|
||||
}
|
||||
return new Promise((resolve) => resolve(JSON.stringify(osinfo)))
|
||||
}
|
||||
|
||||
isTauri(): boolean {
|
||||
return isTauri()
|
||||
}
|
||||
|
||||
getWebrtcStats(): Promise<string> {
|
||||
if (!this.engineCommandManager.engineConnection) {
|
||||
throw new Error('Engine connection not initialized')
|
||||
}
|
||||
|
||||
if (!this.engineCommandManager.engineConnection.webrtcStatsCollector) {
|
||||
throw new Error('Engine webrtcStatsCollector not initialized')
|
||||
}
|
||||
|
||||
return this.engineCommandManager.engineConnection
|
||||
.webrtcStatsCollector()
|
||||
.catch((error: any) => {
|
||||
throw new Error(`Error getting webrtc stats: ${error}`)
|
||||
})
|
||||
.then((stats: any) => {
|
||||
const webrtcStats: WebrtcStats = {
|
||||
packets_lost: stats.rtc_packets_lost,
|
||||
frames_received: stats.rtc_frames_received,
|
||||
frame_width: stats.rtc_frame_width,
|
||||
frame_height: stats.rtc_frame_height,
|
||||
frame_rate: stats.rtc_frames_per_second,
|
||||
key_frames_decoded: stats.rtc_keyframes_decoded,
|
||||
frames_dropped: stats.rtc_frames_dropped,
|
||||
pause_count: stats.rtc_pause_count,
|
||||
total_pauses_duration: stats.rtc_total_pauses_duration_sec,
|
||||
freeze_count: stats.rtc_freeze_count,
|
||||
total_freezes_duration: stats.rtc_total_freezes_duration_sec,
|
||||
pli_count: stats.rtc_pli_count,
|
||||
jitter: stats.rtc_jitter_sec,
|
||||
}
|
||||
return JSON.stringify(webrtcStats)
|
||||
})
|
||||
}
|
||||
|
||||
// Return a data URL (png format) of the screenshot of the current page.
|
||||
screenshot(): Promise<string> {
|
||||
return screenshot(this.htmlRef)
|
||||
.then((screenshot: string) => {
|
||||
return screenshot
|
||||
})
|
||||
.catch((error: any) => {
|
||||
throw new Error(`Error getting screenshot: ${error}`)
|
||||
})
|
||||
}
|
||||
}
|
@ -29,11 +29,11 @@ const bracket = startSketchOn('XY')
|
||||
|> extrude(width, %)
|
||||
|> fillet({
|
||||
radius: filletR,
|
||||
tags: [getNextAdjacentEdge('innerEdge', %)]
|
||||
tags: [getPreviousAdjacentEdge('innerEdge', %)]
|
||||
}, %)
|
||||
|> fillet({
|
||||
radius: filletR + thickness,
|
||||
tags: [getNextAdjacentEdge('outerEdge', %)]
|
||||
tags: [getPreviousAdjacentEdge('outerEdge', %)]
|
||||
}, %)`
|
||||
|
||||
function findLineInExampleCode({
|
||||
|
11
src/lib/openWindow.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { isTauri } from 'lib/isTauri'
|
||||
import { open as tauriOpen } from '@tauri-apps/plugin-shell'
|
||||
|
||||
// Open a new browser window tauri style or browser style.
|
||||
export default async function openWindow(url: string) {
|
||||
if (isTauri()) {
|
||||
await tauriOpen(url)
|
||||
} else {
|
||||
window.open(url, '_blank')
|
||||
}
|
||||
}
|
21
src/lib/screenshot.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import React from 'react'
|
||||
import html2canvas from 'html2canvas-pro'
|
||||
|
||||
// Return a data URL (png format) of the screenshot of the current page.
|
||||
export default async function screenshot(
|
||||
htmlRef: React.RefObject<HTMLDivElement> | null
|
||||
): Promise<string> {
|
||||
if (htmlRef === null) {
|
||||
throw new Error('htmlRef is null')
|
||||
}
|
||||
if (htmlRef.current === null) {
|
||||
throw new Error('htmlRef is null')
|
||||
}
|
||||
return html2canvas(htmlRef.current)
|
||||
.then((canvas) => {
|
||||
return canvas.toDataURL()
|
||||
})
|
||||
.catch((error) => {
|
||||
throw error
|
||||
})
|
||||
}
|
@ -6,7 +6,7 @@ import {
|
||||
import { Models } from '@kittycad/lib'
|
||||
import { Themes } from './theme'
|
||||
|
||||
type WebSocketResponse = Models['OkWebSocketResponseData_type']
|
||||
type WebSocketResponse = Models['WebSocketResponse_type']
|
||||
|
||||
class MockEngineCommandManager {
|
||||
// eslint-disable-next-line @typescript-eslint/no-useless-constructor
|
||||
@ -27,9 +27,12 @@ class MockEngineCommandManager {
|
||||
command: EngineCommand
|
||||
}): Promise<any> {
|
||||
const response: WebSocketResponse = {
|
||||
type: 'modeling',
|
||||
data: {
|
||||
modeling_response: { type: 'empty' },
|
||||
success: true,
|
||||
resp: {
|
||||
type: 'modeling',
|
||||
data: {
|
||||
modeling_response: { type: 'empty' },
|
||||
},
|
||||
},
|
||||
}
|
||||
return Promise.resolve(JSON.stringify(response))
|
||||
|
@ -37,8 +37,9 @@ import {
|
||||
shouldShowSettingInput,
|
||||
} from 'lib/settings/settingsUtils'
|
||||
|
||||
export const APP_VERSION = import.meta.env.PACKAGE_VERSION || 'unknown'
|
||||
|
||||
export const Settings = () => {
|
||||
const APP_VERSION = import.meta.env.PACKAGE_VERSION || 'unknown'
|
||||
const navigate = useNavigate()
|
||||
const close = () => navigate(location.pathname.replace(paths.SETTINGS, ''))
|
||||
const location = useLocation()
|
||||
|
@ -81,6 +81,8 @@ export interface StoreState {
|
||||
streamWidth: number
|
||||
streamHeight: number
|
||||
}) => void
|
||||
setHtmlRef: (ref: React.RefObject<HTMLDivElement>) => void
|
||||
htmlRef: React.RefObject<HTMLDivElement> | null
|
||||
|
||||
showHomeMenu: boolean
|
||||
setHomeShowMenu: (showMenu: boolean) => void
|
||||
@ -132,6 +134,10 @@ export const useStore = create<StoreState>()(
|
||||
setButtonDownInStream: (buttonDownInStream) => {
|
||||
set({ buttonDownInStream })
|
||||
},
|
||||
setHtmlRef: (htmlRef) => {
|
||||
set({ htmlRef })
|
||||
},
|
||||
htmlRef: null,
|
||||
didDragInStream: false,
|
||||
setDidDragInStream: (didDragInStream) => {
|
||||
set({ didDragInStream })
|
||||
|
32
src/wasm-lib/Cargo.lock
generated
@ -155,9 +155,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.81"
|
||||
version = "1.0.82"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247"
|
||||
checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
]
|
||||
@ -1373,6 +1373,12 @@ version = "0.28.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
|
||||
|
||||
[[package]]
|
||||
name = "git_rev"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60884563ea313b5037683cd5d44f1e14e9cb07b08543756242a65887f9cff48e"
|
||||
|
||||
[[package]]
|
||||
name = "gloo-utils"
|
||||
version = "0.2.0"
|
||||
@ -1864,7 +1870,9 @@ dependencies = [
|
||||
"databake",
|
||||
"derive-docs",
|
||||
"expectorate",
|
||||
"form_urlencoded",
|
||||
"futures",
|
||||
"git_rev",
|
||||
"gltf-json",
|
||||
"iai",
|
||||
"image",
|
||||
@ -1912,9 +1920,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kittycad"
|
||||
version = "0.2.63"
|
||||
version = "0.2.66"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93a332250e08fd715ad3d5826e04d36da1c5bb42d0c1b1ff1f0598278b9ebf3c"
|
||||
checksum = "9e2897244f4600f863115561a0fd1cd7c87fca20253ffecfebc53ef642d0aceb"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@ -1950,9 +1958,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kittycad-execution-plan"
|
||||
version = "0.1.3"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e913f8e5f3ef7928cddca2e7b53c6582d7be6a8f900d18ce6c31c04083056270"
|
||||
checksum = "acf8ffb148bd09de8889a8a2b3075a23ee86446c3a6e1c6dcf66b40fdc778158"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"gltf-json",
|
||||
@ -1995,9 +2003,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kittycad-modeling-cmds"
|
||||
version = "0.2.10"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b41beb7d9b776df93fd449604de5c447e33c7bd3326fd590002dc18cf5f08166"
|
||||
checksum = "e94b75afb2ab9fe824bb483d2475d419bb15fbe850466c45962999b54456173a"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
@ -2035,9 +2043,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kittycad-modeling-session"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ee3a24232a086ec12ae4cfee443485c22e6c6959936d861006fa13bebef0904"
|
||||
checksum = "bae9bc47fcc3cc30727b35e738c35666b97e1e5f48f3f4c60ddaeccb69b66559"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"kittycad",
|
||||
@ -2815,9 +2823,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.35"
|
||||
version = "1.0.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
|
||||
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
@ -59,12 +59,12 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.dependencies]
|
||||
kittycad = { version = "0.2.63", default-features = false, features = ["js", "requests"] }
|
||||
kittycad-execution-plan = "0.1.3"
|
||||
kittycad = { version = "0.2.66", default-features = false, features = ["js", "requests"] }
|
||||
kittycad-execution-plan = "0.1.4"
|
||||
kittycad-execution-plan-macros = "0.1.9"
|
||||
kittycad-execution-plan-traits = "0.1.14"
|
||||
kittycad-modeling-cmds = "0.2.10"
|
||||
kittycad-modeling-session = "0.1.2"
|
||||
kittycad-modeling-cmds = "0.2.17"
|
||||
kittycad-modeling-session = "0.1.3"
|
||||
|
||||
[[test]]
|
||||
name = "executor"
|
||||
|
@ -23,7 +23,7 @@ serde_tokenstream = "0.2"
|
||||
syn = { version = "2.0.58", features = ["full"] }
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = "1.0.81"
|
||||
anyhow = "1.0.82"
|
||||
expectorate = "1.1.0"
|
||||
pretty_assertions = "1.4.0"
|
||||
rustfmt-wrapper = "0.2.1"
|
||||
|
@ -777,7 +777,7 @@ fn generate_code_block_test(
|
||||
}
|
||||
let ws = client
|
||||
.modeling()
|
||||
.commands_ws(None, None, None, None, None, Some(false))
|
||||
.commands_ws(None, None, None, None, None,None, Some(false))
|
||||
.await.unwrap();
|
||||
|
||||
let tokens = crate::token::lexer(#code_block);
|
||||
|
@ -11,15 +11,18 @@ keywords = ["kcl", "KittyCAD", "CAD"]
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = { version = "1.0.81", features = ["backtrace"] }
|
||||
anyhow = { version = "1.0.82", features = ["backtrace"] }
|
||||
async-recursion = "1.1.0"
|
||||
async-trait = "0.1.79"
|
||||
base64 = "0.22.0"
|
||||
chrono = "0.4.37"
|
||||
clap = { version = "4.5.4", features = ["cargo", "derive", "env", "unicode"], optional = true }
|
||||
dashmap = "5.5.3"
|
||||
databake = { version = "0.1.7", features = ["derive"] }
|
||||
derive-docs = { version = "0.1.12", path = "../derive-docs" }
|
||||
derive-docs = { version = "0.1.13", path = "../derive-docs" }
|
||||
form_urlencoded = "1.2.1"
|
||||
futures = { version = "0.3.30" }
|
||||
git_rev = "0.1.0"
|
||||
gltf-json = "1.4.0"
|
||||
kittycad = { workspace = true }
|
||||
kittycad-execution-plan-macros = { workspace = true }
|
||||
|
58
src/wasm-lib/kcl/src/coredump/local.rs
Normal file
@ -0,0 +1,58 @@
|
||||
//! Functions for getting core dump information via local rust.
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::coredump::CoreDump;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CoreDumper {}
|
||||
|
||||
impl CoreDumper {
|
||||
pub fn new() -> Self {
|
||||
CoreDumper {}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for CoreDumper {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl CoreDump for CoreDumper {
|
||||
fn token(&self) -> Result<String> {
|
||||
Ok(std::env::var("KITTYCAD_API_TOKEN").unwrap_or_default())
|
||||
}
|
||||
|
||||
fn base_api_url(&self) -> Result<String> {
|
||||
Ok("https://api.zoo.dev".to_string())
|
||||
}
|
||||
|
||||
fn version(&self) -> Result<String> {
|
||||
Ok(env!("CARGO_PKG_VERSION").to_string())
|
||||
}
|
||||
|
||||
async fn os(&self) -> Result<crate::coredump::OsInfo> {
|
||||
Ok(crate::coredump::OsInfo {
|
||||
platform: Some(std::env::consts::OS.to_string()),
|
||||
arch: Some(std::env::consts::ARCH.to_string()),
|
||||
version: None,
|
||||
browser: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn is_tauri(&self) -> Result<bool> {
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
async fn get_webrtc_stats(&self) -> Result<crate::coredump::WebrtcStats> {
|
||||
// TODO: we could actually implement this.
|
||||
Ok(crate::coredump::WebrtcStats::default())
|
||||
}
|
||||
|
||||
async fn screenshot(&self) -> Result<String> {
|
||||
// Take a screenshot of the engine.
|
||||
todo!()
|
||||
}
|
||||
}
|
193
src/wasm-lib/kcl/src/coredump/mod.rs
Normal file
@ -0,0 +1,193 @@
|
||||
//! Core dump related structures and functions.
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub mod local;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod wasm;
|
||||
|
||||
use anyhow::Result;
|
||||
use base64::Engine;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
pub trait CoreDump: Clone {
|
||||
/// Return the authentication token.
|
||||
fn token(&self) -> Result<String>;
|
||||
|
||||
fn base_api_url(&self) -> Result<String>;
|
||||
|
||||
fn version(&self) -> Result<String>;
|
||||
|
||||
async fn os(&self) -> Result<OsInfo>;
|
||||
|
||||
fn is_tauri(&self) -> Result<bool>;
|
||||
|
||||
async fn get_webrtc_stats(&self) -> Result<WebrtcStats>;
|
||||
|
||||
/// Return a screenshot of the app.
|
||||
async fn screenshot(&self) -> Result<String>;
|
||||
|
||||
/// Get a screenshot of the app and upload it to public cloud storage.
|
||||
async fn upload_screenshot(&self) -> Result<String> {
|
||||
let screenshot = self.screenshot().await?;
|
||||
let cleaned = screenshot.trim_start_matches("data:image/png;base64,");
|
||||
// Create the zoo client.
|
||||
let mut zoo = kittycad::Client::new(self.token()?);
|
||||
zoo.set_base_url(&self.base_api_url()?);
|
||||
|
||||
// Base64 decode the screenshot.
|
||||
let data = base64::engine::general_purpose::STANDARD.decode(cleaned)?;
|
||||
// Upload the screenshot.
|
||||
let links = zoo
|
||||
.meta()
|
||||
.create_debug_uploads(vec![kittycad::types::multipart::Attachment {
|
||||
name: "".to_string(),
|
||||
filename: Some("modeling-app/core-dump-screenshot.png".to_string()),
|
||||
content_type: Some("image/png".to_string()),
|
||||
data,
|
||||
}])
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!(e.to_string()))?;
|
||||
|
||||
if links.is_empty() {
|
||||
anyhow::bail!("Failed to upload screenshot");
|
||||
}
|
||||
|
||||
Ok(links[0].clone())
|
||||
}
|
||||
|
||||
/// Dump the app info.
|
||||
async fn dump(&self) -> Result<AppInfo> {
|
||||
let webrtc_stats = self.get_webrtc_stats().await?;
|
||||
let os = self.os().await?;
|
||||
let screenshot_url = self.upload_screenshot().await?;
|
||||
|
||||
let mut app_info = AppInfo {
|
||||
version: self.version()?,
|
||||
git_rev: git_rev::try_revision_string!().map_or_else(|| "unknown".to_string(), |s| s.to_string()),
|
||||
timestamp: chrono::Utc::now(),
|
||||
tauri: self.is_tauri()?,
|
||||
os,
|
||||
webrtc_stats,
|
||||
github_issue_url: None,
|
||||
};
|
||||
app_info.set_github_issue_url(&screenshot_url)?;
|
||||
|
||||
Ok(app_info)
|
||||
}
|
||||
}
|
||||
|
||||
/// The app info structure.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct AppInfo {
|
||||
/// The version of the app.
|
||||
pub version: String,
|
||||
/// The git revision of the app.
|
||||
pub git_rev: String,
|
||||
/// A timestamp of the core dump.
|
||||
#[ts(type = "string")]
|
||||
pub timestamp: chrono::DateTime<chrono::Utc>,
|
||||
/// If the app is running in tauri or the browser.
|
||||
pub tauri: bool,
|
||||
|
||||
/// The os info.
|
||||
pub os: OsInfo,
|
||||
|
||||
/// The webrtc stats.
|
||||
pub webrtc_stats: WebrtcStats,
|
||||
|
||||
/// A GitHub issue url to report the core dump.
|
||||
/// This gets prepoulated with all the core dump info.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub github_issue_url: Option<String>,
|
||||
}
|
||||
|
||||
impl AppInfo {
|
||||
/// Set the github issue url.
|
||||
pub fn set_github_issue_url(&mut self, screenshot_url: &str) -> Result<()> {
|
||||
let tauri_or_browser_label = if self.tauri { "tauri" } else { "browser" };
|
||||
let labels = ["coredump", "bug", tauri_or_browser_label];
|
||||
let body = format!(
|
||||
r#"[Insert a description of the issue here]
|
||||
|
||||

|
||||
|
||||
<details>
|
||||
<summary><b>Core Dump</b></summary>
|
||||
|
||||
```json
|
||||
{}
|
||||
```
|
||||
</details>
|
||||
"#,
|
||||
screenshot_url,
|
||||
serde_json::to_string_pretty(&self)?
|
||||
);
|
||||
let urlencoded: String = form_urlencoded::byte_serialize(body.as_bytes()).collect();
|
||||
|
||||
self.github_issue_url = Some(format!(
|
||||
r#"https://github.com/{}/{}/issues/new?body={}&labels={}"#,
|
||||
"KittyCAD",
|
||||
"modeling-app",
|
||||
urlencoded,
|
||||
labels.join(",")
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// The os info structure.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct OsInfo {
|
||||
/// The platform the app is running on.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub platform: Option<String>,
|
||||
/// The architecture the app is running on.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub arch: Option<String>,
|
||||
/// The kernel version.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub version: Option<String>,
|
||||
/// Information about the browser.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub browser: Option<String>,
|
||||
}
|
||||
|
||||
/// The webrtc stats structure.
|
||||
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct WebrtcStats {
|
||||
/// The packets lost.
|
||||
pub packets_lost: u32,
|
||||
/// The frames received.
|
||||
pub frames_received: u32,
|
||||
/// The frame width.
|
||||
pub frame_width: f32,
|
||||
/// The frame height.
|
||||
pub frame_height: f32,
|
||||
/// The frame rate.
|
||||
pub frame_rate: f32,
|
||||
/// The number of key frames decoded.
|
||||
pub key_frames_decoded: u32,
|
||||
/// The number of frames dropped.
|
||||
pub frames_dropped: u32,
|
||||
/// The pause count.
|
||||
pub pause_count: u32,
|
||||
/// The total pauses duration.
|
||||
pub total_pauses_duration: f32,
|
||||
/// The freeze count.
|
||||
pub freeze_count: u32,
|
||||
/// The total freezes duration.
|
||||
pub total_freezes_duration: f32,
|
||||
/// The pli count.
|
||||
pub pli_count: u32,
|
||||
/// Packet jitter for this synchronizing source, measured in seconds.
|
||||
pub jitter: f32,
|
||||
}
|
134
src/wasm-lib/kcl/src/coredump/wasm.rs
Normal file
@ -0,0 +1,134 @@
|
||||
//! Functions for getting core dump information via wasm.
|
||||
|
||||
use anyhow::Result;
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
|
||||
use crate::{coredump::CoreDump, wasm::JsFuture};
|
||||
|
||||
#[wasm_bindgen(module = "/../../lib/coredump.ts")]
|
||||
extern "C" {
|
||||
#[derive(Debug, Clone)]
|
||||
pub type CoreDumpManager;
|
||||
|
||||
#[wasm_bindgen(method, js_name = authToken, catch)]
|
||||
fn auth_token(this: &CoreDumpManager) -> Result<String, js_sys::Error>;
|
||||
|
||||
#[wasm_bindgen(method, js_name = baseApiUrl, catch)]
|
||||
fn baseApiUrl(this: &CoreDumpManager) -> Result<String, js_sys::Error>;
|
||||
|
||||
#[wasm_bindgen(method, js_name = version, catch)]
|
||||
fn version(this: &CoreDumpManager) -> Result<String, js_sys::Error>;
|
||||
|
||||
#[wasm_bindgen(method, js_name = getOsInfo, catch)]
|
||||
fn get_os_info(this: &CoreDumpManager) -> Result<js_sys::Promise, js_sys::Error>;
|
||||
|
||||
#[wasm_bindgen(method, js_name = isTauri, catch)]
|
||||
fn is_tauri(this: &CoreDumpManager) -> Result<bool, js_sys::Error>;
|
||||
|
||||
#[wasm_bindgen(method, js_name = getWebrtcStats, catch)]
|
||||
fn get_webrtc_stats(this: &CoreDumpManager) -> Result<js_sys::Promise, js_sys::Error>;
|
||||
|
||||
#[wasm_bindgen(method, js_name = screenshot, catch)]
|
||||
fn screenshot(this: &CoreDumpManager) -> Result<js_sys::Promise, js_sys::Error>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CoreDumper {
|
||||
manager: CoreDumpManager,
|
||||
}
|
||||
|
||||
impl CoreDumper {
|
||||
pub fn new(manager: CoreDumpManager) -> Self {
|
||||
CoreDumper { manager }
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for CoreDumper {}
|
||||
unsafe impl Sync for CoreDumper {}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl CoreDump for CoreDumper {
|
||||
fn token(&self) -> Result<String> {
|
||||
self.manager
|
||||
.auth_token()
|
||||
.map_err(|e| anyhow::anyhow!("Failed to get response from token: {:?}", e))
|
||||
}
|
||||
|
||||
fn base_api_url(&self) -> Result<String> {
|
||||
self.manager
|
||||
.baseApiUrl()
|
||||
.map_err(|e| anyhow::anyhow!("Failed to get response from base api url: {:?}", e))
|
||||
}
|
||||
|
||||
fn version(&self) -> Result<String> {
|
||||
self.manager
|
||||
.version()
|
||||
.map_err(|e| anyhow::anyhow!("Failed to get response from version: {:?}", e))
|
||||
}
|
||||
|
||||
async fn os(&self) -> Result<crate::coredump::OsInfo> {
|
||||
let promise = self
|
||||
.manager
|
||||
.get_os_info()
|
||||
.map_err(|e| anyhow::anyhow!("Failed to get promise from get os info: {:?}", e))?;
|
||||
|
||||
let value = JsFuture::from(promise)
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!("Failed to get response from os info: {:?}", e))?;
|
||||
|
||||
// Parse the value as a string.
|
||||
let s = value
|
||||
.as_string()
|
||||
.ok_or_else(|| anyhow::anyhow!("Failed to get string from response from os info: `{:?}`", value))?;
|
||||
|
||||
let os: crate::coredump::OsInfo =
|
||||
serde_json::from_str(&s).map_err(|e| anyhow::anyhow!("Failed to parse os info: {:?}", e))?;
|
||||
|
||||
Ok(os)
|
||||
}
|
||||
|
||||
fn is_tauri(&self) -> Result<bool> {
|
||||
self.manager
|
||||
.is_tauri()
|
||||
.map_err(|e| anyhow::anyhow!("Failed to get response from is tauri: {:?}", e))
|
||||
}
|
||||
|
||||
async fn get_webrtc_stats(&self) -> Result<crate::coredump::WebrtcStats> {
|
||||
let promise = self
|
||||
.manager
|
||||
.get_webrtc_stats()
|
||||
.map_err(|e| anyhow::anyhow!("Failed to get promise from get webrtc stats: {:?}", e))?;
|
||||
|
||||
let value = JsFuture::from(promise)
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!("Failed to get response from webrtc stats: {:?}", e))?;
|
||||
|
||||
// Parse the value as a string.
|
||||
let s = value
|
||||
.as_string()
|
||||
.ok_or_else(|| anyhow::anyhow!("Failed to get string from response from webrtc stats: `{:?}`", value))?;
|
||||
|
||||
let stats: crate::coredump::WebrtcStats =
|
||||
serde_json::from_str(&s).map_err(|e| anyhow::anyhow!("Failed to parse webrtc stats: {:?}", e))?;
|
||||
|
||||
Ok(stats)
|
||||
}
|
||||
|
||||
async fn screenshot(&self) -> Result<String> {
|
||||
let promise = self
|
||||
.manager
|
||||
.screenshot()
|
||||
.map_err(|e| anyhow::anyhow!("Failed to get promise from get screenshot: {:?}", e))?;
|
||||
|
||||
let value = JsFuture::from(promise)
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!("Failed to get response from screenshot: {:?}", e))?;
|
||||
|
||||
// Parse the value as a string.
|
||||
let s = value
|
||||
.as_string()
|
||||
.ok_or_else(|| anyhow::anyhow!("Failed to get string from response from screenshot: `{:?}`", value))?;
|
||||
|
||||
Ok(s)
|
||||
}
|
||||
}
|
@ -99,13 +99,25 @@ impl crate::engine::EngineManager for EngineConnection {
|
||||
})
|
||||
})?;
|
||||
|
||||
let modeling_result: kittycad::types::OkWebSocketResponseData = serde_json::from_str(&s).map_err(|e| {
|
||||
let ws_result: kittycad::types::WebSocketResponse = serde_json::from_str(&s).map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: format!("Failed to deserialize response from engine: {:?}", e),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?;
|
||||
|
||||
Ok(modeling_result)
|
||||
if let Some(data) = &ws_result.resp {
|
||||
Ok(data.clone())
|
||||
} else if let Some(errors) = &ws_result.errors {
|
||||
Err(KclError::Engine(KclErrorDetails {
|
||||
message: format!("Modeling command failed: {:?}", errors),
|
||||
source_ranges: vec![source_range],
|
||||
}))
|
||||
} else {
|
||||
Err(KclError::Engine(KclErrorDetails {
|
||||
message: format!("Modeling command failed: {:?}", ws_result),
|
||||
source_ranges: vec![source_range],
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +28,16 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
id_to_source_range: std::collections::HashMap<uuid::Uuid, crate::executor::SourceRange>,
|
||||
) -> Result<kittycad::types::OkWebSocketResponseData, crate::errors::KclError>;
|
||||
|
||||
fn push_to_batch(
|
||||
&self,
|
||||
cmd_id: uuid::Uuid,
|
||||
source_range: crate::executor::SourceRange,
|
||||
cmd: kittycad::types::ModelingCmd,
|
||||
) {
|
||||
let req = WebSocketRequest::ModelingCmdReq { cmd, cmd_id };
|
||||
self.batch().lock().unwrap().push((req, source_range))
|
||||
}
|
||||
|
||||
async fn send_modeling_cmd(
|
||||
&self,
|
||||
id: uuid::Uuid,
|
||||
@ -85,6 +95,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
let batched_requests = WebSocketRequest::ModelingCmdBatchReq {
|
||||
requests,
|
||||
batch_id: uuid::Uuid::new_v4(),
|
||||
responses: Some(false),
|
||||
};
|
||||
|
||||
let final_req = if self.batch().lock().unwrap().len() == 1 {
|
||||
@ -93,7 +104,8 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
} else {
|
||||
batched_requests
|
||||
};
|
||||
// println!("Running batch: {final_req:#?}");
|
||||
// TODO: Uncomment this.
|
||||
debug_batch(&final_req);
|
||||
|
||||
// Create the map of original command IDs to source range.
|
||||
// This is for the wasm side, kurt needs it for selections.
|
||||
@ -117,7 +129,11 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
|
||||
// We pop off the responses to cleanup our mappings.
|
||||
let id_final = match final_req {
|
||||
WebSocketRequest::ModelingCmdBatchReq { requests: _, batch_id } => batch_id,
|
||||
WebSocketRequest::ModelingCmdBatchReq {
|
||||
requests: _,
|
||||
batch_id,
|
||||
responses: _,
|
||||
} => batch_id,
|
||||
WebSocketRequest::ModelingCmdReq { cmd: _, cmd_id } => cmd_id,
|
||||
_ => {
|
||||
return Err(KclError::Engine(KclErrorDetails {
|
||||
@ -186,3 +202,18 @@ pub fn is_cmd_with_return_values(cmd: &kittycad::types::ModelingCmd) -> bool {
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
#[allow(dead_code)] // Only used in debugging.
|
||||
fn debug_batch(msg: &WebSocketRequest) {
|
||||
match msg {
|
||||
WebSocketRequest::ModelingCmdReq { cmd, .. } => {
|
||||
println!("[ {:?} ]", cmd);
|
||||
}
|
||||
|
||||
WebSocketRequest::ModelingCmdBatchReq { requests, .. } => {
|
||||
let names: Vec<_> = requests.iter().map(|req| format!("{:?}", req.cmd)).collect();
|
||||
println!("[ {} ]", names.join(", "))
|
||||
}
|
||||
other => panic!("this isn't a modeling command or batch: {other:?}"),
|
||||
}
|
||||
}
|
||||
|
@ -1226,7 +1226,7 @@ pub(crate) async fn execute(
|
||||
}
|
||||
|
||||
// Flush the batch queue.
|
||||
ctx.engine.flush_batch(SourceRange::default()).await?;
|
||||
ctx.engine.flush_batch(SourceRange([program.end, program.end])).await?;
|
||||
|
||||
Ok(memory.clone())
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ pub use local::FileManager;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[cfg(not(test))]
|
||||
pub mod wasm;
|
||||
|
||||
use anyhow::Result;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[cfg(not(test))]
|
||||
|
@ -5,6 +5,7 @@
|
||||
#![recursion_limit = "1024"]
|
||||
|
||||
pub mod ast;
|
||||
pub mod coredump;
|
||||
pub mod docs;
|
||||
pub mod engine;
|
||||
pub mod errors;
|
||||
|
@ -38,15 +38,14 @@ async fn inner_extrude(length: f64, sketch_group: Box<SketchGroup>, args: Args)
|
||||
let id = uuid::Uuid::new_v4();
|
||||
|
||||
// Extrude the element.
|
||||
args.send_modeling_cmd(
|
||||
args.push_to_batch(
|
||||
id,
|
||||
kittycad::types::ModelingCmd::Extrude {
|
||||
target: sketch_group.id,
|
||||
distance: length,
|
||||
cap: true,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
);
|
||||
|
||||
do_post_extrude(sketch_group, length, id, args).await
|
||||
}
|
||||
@ -66,13 +65,12 @@ pub(crate) async fn do_post_extrude(
|
||||
|
||||
// Bring the object to the front of the scene.
|
||||
// See: https://github.com/KittyCAD/modeling-app/issues/806
|
||||
args.send_modeling_cmd(
|
||||
args.push_to_batch(
|
||||
uuid::Uuid::new_v4(),
|
||||
kittycad::types::ModelingCmd::ObjectBringToFront {
|
||||
object_id: sketch_group.id,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
);
|
||||
|
||||
if sketch_group.value.is_empty() {
|
||||
return Err(KclError::Type(KclErrorDetails {
|
||||
|
@ -214,6 +214,10 @@ impl Args {
|
||||
self.ctx.engine.send_modeling_cmd(id, self.source_range, cmd).await
|
||||
}
|
||||
|
||||
pub fn push_to_batch(&self, id: uuid::Uuid, cmd: kittycad::types::ModelingCmd) {
|
||||
self.ctx.engine.push_to_batch(id, self.source_range, cmd)
|
||||
}
|
||||
|
||||
fn make_user_val_from_json(&self, j: serde_json::Value) -> Result<MemoryItem, KclError> {
|
||||
Ok(MemoryItem::UserVal(crate::executor::UserVal {
|
||||
value: j,
|
||||
|
@ -235,7 +235,7 @@ pub struct CircularPattern2dData {
|
||||
/// This excludes the original entity. For example, if `repetitions` is 1,
|
||||
/// the original entity will be copied once.
|
||||
pub repetitions: u32,
|
||||
/// The center about which to make th pattern. This is a 2D vector.
|
||||
/// The center about which to make the pattern. This is a 2D vector.
|
||||
pub center: [f64; 2],
|
||||
/// The arc angle (in degrees) to place the repetitions. Must be greater than 0.
|
||||
pub arc_degrees: f64,
|
||||
@ -254,7 +254,7 @@ pub struct CircularPattern3dData {
|
||||
pub repetitions: u32,
|
||||
/// The axis around which to make the pattern. This is a 3D vector.
|
||||
pub axis: [f64; 3],
|
||||
/// The center about which to make th pattern. This is a 3D vector.
|
||||
/// The center about which to make the pattern. This is a 3D vector.
|
||||
pub center: [f64; 3],
|
||||
/// The arc angle (in degrees) to place the repetitions. Must be greater than 0.
|
||||
pub arc_degrees: f64,
|
||||
|
@ -224,7 +224,7 @@ async fn inner_revolve(
|
||||
match data.axis {
|
||||
RevolveAxis::Axis(axis) => {
|
||||
let (axis, origin) = axis.axis_and_origin()?;
|
||||
args.send_modeling_cmd(
|
||||
args.push_to_batch(
|
||||
id,
|
||||
ModelingCmd::Revolve {
|
||||
angle,
|
||||
@ -234,8 +234,7 @@ async fn inner_revolve(
|
||||
tolerance: DEFAULT_TOLERANCE,
|
||||
axis_is_2d: true,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
);
|
||||
}
|
||||
RevolveAxis::Edge(edge) => {
|
||||
let edge_id = match edge {
|
||||
@ -256,7 +255,7 @@ async fn inner_revolve(
|
||||
.id
|
||||
}
|
||||
};
|
||||
args.send_modeling_cmd(
|
||||
args.push_to_batch(
|
||||
id,
|
||||
ModelingCmd::RevolveAboutEdge {
|
||||
angle,
|
||||
@ -264,8 +263,7 @@ async fn inner_revolve(
|
||||
edge_id,
|
||||
tolerance: DEFAULT_TOLERANCE,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@ use std::{
|
||||
|
||||
use futures::stream::TryStreamExt;
|
||||
use gloo_utils::format::JsValueSerdeExt;
|
||||
use kcl_lib::engine::EngineManager;
|
||||
use kcl_lib::{coredump::CoreDump, engine::EngineManager};
|
||||
use tower_lsp::{LspService, Server};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
@ -379,3 +379,16 @@ pub fn program_memory_init() -> Result<JsValue, String> {
|
||||
// gloo-serialize crate instead.
|
||||
JsValue::from_serde(&memory).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
/// Get a coredump.
|
||||
#[wasm_bindgen]
|
||||
pub async fn coredump(core_dump_manager: kcl_lib::coredump::wasm::CoreDumpManager) -> Result<JsValue, String> {
|
||||
console_error_panic_hook::set_once();
|
||||
|
||||
let core_dumper = kcl_lib::coredump::wasm::CoreDumper::new(core_dump_manager);
|
||||
let dump = core_dumper.dump().await.map_err(|e| e.to_string())?;
|
||||
|
||||
// The serde-wasm-bindgen does not work here because of weird HashMap issues so we use the
|
||||
// gloo-serialize crate instead.
|
||||
JsValue::from_serde(&dump).map_err(|e| e.to_string())
|
||||
}
|
||||
|
18
src/wasm-lib/tests/executor/inputs/three_cubes.kcl
Normal file
@ -0,0 +1,18 @@
|
||||
const width = 20
|
||||
|
||||
fn cube = (cx, cy) => {
|
||||
let d = width/2
|
||||
startSketchAt([cx-d, cy-d])
|
||||
|> lineTo([cx+d, cy-d], %)
|
||||
|> lineTo([cx+d, cy+d], %)
|
||||
|> lineTo([cx-d, cy+d], %)
|
||||
|> lineTo([cx-d, cy-d], %)
|
||||
|> close(%)
|
||||
|> extrude(width, %)
|
||||
}
|
||||
|
||||
let interval = 30
|
||||
cube(interval,interval)
|
||||
cube(0,0)
|
||||
cube(-interval,interval)
|
||||
|
@ -29,7 +29,7 @@ async fn execute_and_snapshot(code: &str, units: kittycad::types::UnitLength) ->
|
||||
|
||||
let ws = client
|
||||
.modeling()
|
||||
.commands_ws(None, None, None, None, None, Some(false))
|
||||
.commands_ws(None, None, None, None, None, None, Some(false))
|
||||
.await?;
|
||||
|
||||
// Create a temporary file to write the output to.
|
||||
@ -110,6 +110,15 @@ const part002 = startSketchOn(part001, "here")
|
||||
twenty_twenty::assert_image("tests/executor/outputs/sketch_on_face.png", &result, 0.999);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn serial_test_three_cubes() {
|
||||
let code = include_str!("inputs/three_cubes.kcl");
|
||||
let result = execute_and_snapshot(code, kittycad::types::UnitLength::Mm)
|
||||
.await
|
||||
.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/three_cubes.png", &result, 0.999);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn serial_test_riddle_small() {
|
||||
let code = include_str!("inputs/riddle_small.kcl");
|
||||
@ -1959,3 +1968,52 @@ capScrew([0, 0.5, 0], 50, 37.5, 50, 25)
|
||||
.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/member_expression_in_params.png", &result, 1.0);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn serial_test_bracket_with_fillets_ensure_fail_on_flush_source_ranges() {
|
||||
let code = r#"// Shelf Bracket
|
||||
// This is a shelf bracket made out of 6061-T6 aluminum sheet metal. The required thickness is calculated based on a point load of 300 lbs applied to the end of the shelf. There are two brackets holding up the shelf, so the moment experienced is divided by 2. The shelf is 1 foot long from the wall.
|
||||
|
||||
const sigmaAllow = 35000 // psi
|
||||
const width = 6 // inch
|
||||
const p = 300 // Force on shelf - lbs
|
||||
const distance = 12 // inches
|
||||
const M = 12 * 300 / 2 // Moment experienced at fixed end of bracket
|
||||
const FOS = 2 // Factor of safety of 2
|
||||
const shelfMountL = 8 // The length of the bracket holding up the shelf is 6 inches
|
||||
const wallMountL = 8 // the length of the bracket
|
||||
|
||||
|
||||
// Calculate the thickness off the allowable bending stress and factor of safety
|
||||
const thickness = sqrt(6 * M * FOS / (width * sigmaAllow))
|
||||
|
||||
// 0.25 inch fillet radius
|
||||
const filletR = 0.25
|
||||
|
||||
// Sketch the bracket and extrude with fillets
|
||||
const bracket = startSketchOn('XY')
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> line([0, wallMountL], %, 'outerEdge')
|
||||
|> line([-shelfMountL, 0], %)
|
||||
|> line([0, -thickness], %)
|
||||
|> line([shelfMountL - thickness, 0], %, 'innerEdge')
|
||||
|> line([0, -wallMountL + thickness], %)
|
||||
|> close(%)
|
||||
|> extrude(width, %)
|
||||
|> fillet({
|
||||
radius: filletR,
|
||||
tags: [getNextAdjacentEdge('innerEdge', %)]
|
||||
}, %)
|
||||
|> fillet({
|
||||
radius: filletR + thickness,
|
||||
tags: [getNextAdjacentEdge('outerEdge', %)]
|
||||
}, %)
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, kittycad::types::UnitLength::Mm).await;
|
||||
assert!(result.is_err());
|
||||
assert_eq!(
|
||||
result.err().unwrap().to_string(),
|
||||
r#"engine: KclErrorDetails { source_ranges: [SourceRange([1443, 1443])], message: "Modeling command failed: Some([ApiError { error_code: BadRequest, message: \"Fillet failed\" }])" }"#
|
||||
);
|
||||
}
|
||||
|
BIN
src/wasm-lib/tests/executor/outputs/three_cubes.png
Normal file
After Width: | Height: | Size: 116 KiB |
@ -33,7 +33,7 @@ async fn setup(code: &str, name: &str) -> Result<(ExecutorContext, Program, uuid
|
||||
|
||||
let ws = client
|
||||
.modeling()
|
||||
.commands_ws(None, None, None, None, None, Some(false))
|
||||
.commands_ws(None, None, None, None, None, None, Some(false))
|
||||
.await?;
|
||||
|
||||
let tokens = kcl_lib::token::lexer(code);
|
||||
|
211
yarn.lock
@ -1630,19 +1630,24 @@
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.2.tgz#1766039cad33f8ad87f9467b98e0d18fbc8f01c5"
|
||||
integrity sha512-1DgP7f+XQIJbLFCTX1V2QnxVmpLdKdzzo2k8EmvDOePfchaIGQ9eCHj2up3/jNEbZuBqel5OxiaOJf37TWauRA==
|
||||
|
||||
"@fortawesome/fontawesome-svg-core@^6.4.2":
|
||||
version "6.4.2"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.4.2.tgz#37f4507d5ec645c8b50df6db14eced32a6f9be09"
|
||||
integrity sha512-gjYDSKv3TrM2sLTOKBc5rH9ckje8Wrwgx1CxAPbN5N3Fm4prfi7NsJVWd1jklp7i5uSCVwhZS5qlhMXqLrpAIg==
|
||||
dependencies:
|
||||
"@fortawesome/fontawesome-common-types" "6.4.2"
|
||||
"@fortawesome/fontawesome-common-types@6.5.2":
|
||||
version "6.5.2"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.5.2.tgz#eaf2f5699f73cef198454ebc0c414e3688898179"
|
||||
integrity sha512-gBxPg3aVO6J0kpfHNILc+NMhXnqHumFxOmjYCFfOiLZfwhnnfhtsdA2hfJlDnj+8PjAs6kKQPenOTKj3Rf7zHw==
|
||||
|
||||
"@fortawesome/free-brands-svg-icons@^6.4.2":
|
||||
version "6.4.2"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.4.2.tgz#9b8e78066ea6dd563da5dfa686615791d0f7cc71"
|
||||
integrity sha512-LKOwJX0I7+mR/cvvf6qIiqcERbdnY+24zgpUSouySml+5w8B4BJOx8EhDR/FTKAu06W12fmUIcv6lzPSwYKGGg==
|
||||
"@fortawesome/fontawesome-svg-core@^6.5.2":
|
||||
version "6.5.2"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.5.2.tgz#4b42de71e196039b0d5ccf88559b8044e3296c21"
|
||||
integrity sha512-5CdaCBGl8Rh9ohNdxeeTMxIj8oc3KNBgIeLMvJosBMdslK/UnEB8rzyDRrbKdL1kDweqBPo4GT9wvnakHWucZw==
|
||||
dependencies:
|
||||
"@fortawesome/fontawesome-common-types" "6.4.2"
|
||||
"@fortawesome/fontawesome-common-types" "6.5.2"
|
||||
|
||||
"@fortawesome/free-brands-svg-icons@^6.5.2":
|
||||
version "6.5.2"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.5.2.tgz#bfca0cebd2c4713dc93244e1fa8b384f1f023587"
|
||||
integrity sha512-zi5FNYdmKLnEc0jc0uuHH17kz/hfYTg4Uei0wMGzcoCL/4d3WM3u1VMc0iGGa31HuhV5i7ZK8ZlTCQrHqRHSGQ==
|
||||
dependencies:
|
||||
"@fortawesome/fontawesome-common-types" "6.5.2"
|
||||
|
||||
"@fortawesome/free-solid-svg-icons@^6.4.2":
|
||||
version "6.4.2"
|
||||
@ -1913,12 +1918,12 @@
|
||||
resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33"
|
||||
integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==
|
||||
|
||||
"@playwright/test@^1.39.0":
|
||||
version "1.39.0"
|
||||
resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.39.0.tgz#d10ba8e38e44104499e25001945f07faa9fa91cd"
|
||||
integrity sha512-3u1iFqgzl7zr004bGPYiN/5EZpRUSFddQBra8Rqll5N0/vfpqlP9I9EXqAoGacuAbX6c9Ulg/Cjqglp5VkK6UQ==
|
||||
"@playwright/test@^1.43.0":
|
||||
version "1.43.0"
|
||||
resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.43.0.tgz#5d90f247b26d404dd5d81c60f9c7c5e5159eb664"
|
||||
integrity sha512-Ebw0+MCqoYflop7wVKj711ccbNlrwTBCtjY5rlbiY9kHL2bCYxq+qltK6uPsVBGGAOb033H2VO0YobcQVxoW7Q==
|
||||
dependencies:
|
||||
playwright "1.39.0"
|
||||
playwright "1.43.0"
|
||||
|
||||
"@puppeteer/browsers@1.4.6":
|
||||
version "1.4.6"
|
||||
@ -2106,7 +2111,7 @@
|
||||
resolved "https://registry.yarnpkg.com/@tauri-apps/api/-/api-2.0.0-beta.4.tgz#7688950f6e03f38b3bac73585f8f4cdd61be6aa6"
|
||||
integrity sha512-Nxtj28NYUo5iwYkpYslxmOPkdI2WkELU2e3UH9nbJm9Ydki2CQwJVGQxx4EANtdZcMNsEsUzRqaDTvEUYH1l6w==
|
||||
|
||||
"@tauri-apps/api@^2.0.0-beta.7":
|
||||
"@tauri-apps/api@2.0.0-beta.7":
|
||||
version "2.0.0-beta.7"
|
||||
resolved "https://registry.yarnpkg.com/@tauri-apps/api/-/api-2.0.0-beta.7.tgz#a80a3ffc24e6ec8dbdc8131dc3e68d8f4342293d"
|
||||
integrity sha512-cM7SJQP4DBkLLMOdybLFYUURWn/tng2FEdAnXlu42f3NhFxKL4KVeeQTkuwlgC7ePwwwvDSqiXGiF+dKOadY7w==
|
||||
@ -2287,7 +2292,7 @@
|
||||
resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9"
|
||||
integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==
|
||||
|
||||
"@tweenjs/tween.js@^23.1.1":
|
||||
"@tweenjs/tween.js@^23.1.1", "@tweenjs/tween.js@~23.1.1":
|
||||
version "23.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@tweenjs/tween.js/-/tween.js-23.1.1.tgz#0ae28ed9c635805557f78c2626464018d5f1b5e2"
|
||||
integrity sha512-ZpboH7pCPPeyBWKf8c7TJswtCEQObFo3bOBYalm99NzZarATALYCo5OhbCa/n4RQyJyHfhkdx+hNrdL5ByFYDw==
|
||||
@ -2358,6 +2363,14 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4"
|
||||
integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==
|
||||
|
||||
"@types/hoist-non-react-statics@^3.3.1":
|
||||
version "3.3.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz#dab7867ef789d87e2b4b0003c9d65c49cc44a494"
|
||||
integrity sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
hoist-non-react-statics "^3.3.0"
|
||||
|
||||
"@types/http-cache-semantics@^4.0.2":
|
||||
version "4.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz#b979ebad3919799c979b17c72621c0bc0a31c6c4"
|
||||
@ -2412,10 +2425,10 @@
|
||||
dependencies:
|
||||
undici-types "~5.26.4"
|
||||
|
||||
"@types/node@^18.19.26":
|
||||
version "18.19.26"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.26.tgz#18991279d0a0e53675285e8cf4a0823766349729"
|
||||
integrity sha512-+wiMJsIwLOYCvUqSdKTrfkS8mpTp+MPINe6+Np4TAGFWWRWiBQ5kSq9nZGCSPkzx9mvT+uEukzpX4MOSCydcvw==
|
||||
"@types/node@^18.19.31":
|
||||
version "18.19.31"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.31.tgz#b7d4a00f7cb826b60a543cebdbda5d189aaecdcd"
|
||||
integrity sha512-ArgCD39YpyyrtFKIqMDvjz79jto5fcI/SVUs2HwB+f0dAzq68yqOdyaSivLiLugSziTpNXLQrVb7RZFmdZzbhA==
|
||||
dependencies:
|
||||
undici-types "~5.26.4"
|
||||
|
||||
@ -2462,10 +2475,10 @@
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react@*", "@types/react@^18.2.73":
|
||||
version "18.2.73"
|
||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.73.tgz#0579548ad122660d99e00499d22e33b81e73ed94"
|
||||
integrity sha512-XcGdod0Jjv84HOC7N5ziY3x+qL0AfmubvKOZ9hJjJ2yd5EE+KYjWhdOjt387e9HPheHkdggF9atTifMRtyAaRA==
|
||||
"@types/react@*", "@types/react@^18.2.75":
|
||||
version "18.2.75"
|
||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.75.tgz#45d18f384939306d35312def1bf532eb38a68562"
|
||||
integrity sha512-+DNnF7yc5y0bHkBTiLKqXFe+L4B3nvOphiMY3tuA5X10esmjqk7smyBZzbGTy2vsiy/Bnzj8yFIBL8xhRacoOg==
|
||||
dependencies:
|
||||
"@types/prop-types" "*"
|
||||
csstype "^3.0.2"
|
||||
@ -2492,16 +2505,22 @@
|
||||
dependencies:
|
||||
"@types/jest" "*"
|
||||
|
||||
"@types/three@^0.160.0":
|
||||
version "0.160.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/three/-/three-0.160.0.tgz#7915a97e0a14ccaa9ccbb9f190c5730b04a23075"
|
||||
integrity sha512-jWlbUBovicUKaOYxzgkLlhkiEQJkhCVvg4W2IYD2trqD2om3VK4DGLpHH5zQHNr7RweZK/5re/4IVhbhvxbV9w==
|
||||
"@types/three@^0.163.0":
|
||||
version "0.163.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/three/-/three-0.163.0.tgz#96f5440fcd39452d2c84dfe0c9b7a9cf0247b9e6"
|
||||
integrity sha512-uIdDhsXRpQiBUkflBS/i1l3JX14fW6Ot9csed60nfbZNXHDTRsnV2xnTVwXcgbvTiboAR4IW+t+lTL5f1rqIqA==
|
||||
dependencies:
|
||||
"@tweenjs/tween.js" "~23.1.1"
|
||||
"@types/stats.js" "*"
|
||||
"@types/webxr" "*"
|
||||
fflate "~0.6.10"
|
||||
fflate "~0.8.2"
|
||||
meshoptimizer "~0.18.1"
|
||||
|
||||
"@types/ua-parser-js@^0.7.39":
|
||||
version "0.7.39"
|
||||
resolved "https://registry.yarnpkg.com/@types/ua-parser-js/-/ua-parser-js-0.7.39.tgz#832c58e460c9435e4e34bb866e85e9146e12cdbb"
|
||||
integrity sha512-P/oDfpofrdtF5xw433SPALpdSchtJmY7nsJItf8h3KXqOslkbySh8zq4dSWXH2oTjRvJ5PczVEoCZPow6GicLg==
|
||||
|
||||
"@types/uuid@^9.0.8":
|
||||
version "9.0.8"
|
||||
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.8.tgz#7545ba4fc3c003d6c756f651f3bf163d8f0f29ba"
|
||||
@ -2529,20 +2548,13 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/wicg-file-system-access/-/wicg-file-system-access-2023.10.5.tgz#14b3c25eb4d914b5734795bdea71da229f918b9d"
|
||||
integrity sha512-e9kZO9kCdLqT2h9Tw38oGv9UNzBBWaR1MzuAavxPcsV/7FJ3tWbU6RI3uB+yKIDPGLkGVbplS52ub0AcRLvrhA==
|
||||
|
||||
"@types/ws@^8.5.3":
|
||||
"@types/ws@^8.5.10", "@types/ws@^8.5.3":
|
||||
version "8.5.10"
|
||||
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.10.tgz#4acfb517970853fa6574a3a6886791d04a396787"
|
||||
integrity sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/ws@^8.5.5":
|
||||
version "8.5.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.5.tgz#af587964aa06682702ee6dcbc7be41a80e4b28eb"
|
||||
integrity sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/yargs-parser@*":
|
||||
version "21.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15"
|
||||
@ -3505,6 +3517,11 @@ base16@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/base16/-/base16-1.0.0.tgz#e297f60d7ec1014a7a971a39ebc8a98c0b681e70"
|
||||
integrity sha512-pNdYkNPiJUnEhnfXV56+sQy8+AaPcG3POZAUnwr4EeqCUZFz4u2PePbo3e5Gj4ziYPCWGUZT9RHisvJKnwFuBQ==
|
||||
|
||||
base64-arraybuffer@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz#1c37589a7c4b0746e34bd1feb951da2df01c1bdc"
|
||||
integrity sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==
|
||||
|
||||
base64-js@^1.3.1:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
|
||||
@ -4022,6 +4039,13 @@ crypto-js@^4.2.0:
|
||||
resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.2.0.tgz#4d931639ecdfd12ff80e8186dba6af2c2e856631"
|
||||
integrity sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==
|
||||
|
||||
css-line-break@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/css-line-break/-/css-line-break-2.1.0.tgz#bfef660dfa6f5397ea54116bb3cb4873edbc4fa0"
|
||||
integrity sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==
|
||||
dependencies:
|
||||
utrie "^1.0.2"
|
||||
|
||||
css-shorthand-properties@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/css-shorthand-properties/-/css-shorthand-properties-1.1.1.tgz#1c808e63553c283f289f2dd56fcee8f3337bd935"
|
||||
@ -4993,10 +5017,10 @@ fetch-blob@^3.1.2, fetch-blob@^3.1.4:
|
||||
node-domexception "^1.0.0"
|
||||
web-streams-polyfill "^3.0.3"
|
||||
|
||||
fflate@~0.6.10:
|
||||
version "0.6.10"
|
||||
resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.6.10.tgz#5f40f9659205936a2d18abf88b2e7781662b6d43"
|
||||
integrity sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==
|
||||
fflate@~0.8.2:
|
||||
version "0.8.2"
|
||||
resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.8.2.tgz#fc8631f5347812ad6028bbe4a2308b2792aa1dea"
|
||||
integrity sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==
|
||||
|
||||
figures@^5.0.0:
|
||||
version "5.0.0"
|
||||
@ -5110,11 +5134,12 @@ formdata-polyfill@^4.0.10:
|
||||
dependencies:
|
||||
fetch-blob "^3.1.2"
|
||||
|
||||
formik@^2.4.3:
|
||||
version "2.4.3"
|
||||
resolved "https://registry.yarnpkg.com/formik/-/formik-2.4.3.tgz#6020e85eb3e3e8415b3b19d6f4f65793ab754b24"
|
||||
integrity sha512-2Dy79Szw3zlXmZiokUdKsn+n1ow4G8hRrC/n92cOWHNTWXCRpQXlyvz6HcjW7aSQZrldytvDOavYjhfmDnUq8Q==
|
||||
formik@^2.4.5:
|
||||
version "2.4.5"
|
||||
resolved "https://registry.yarnpkg.com/formik/-/formik-2.4.5.tgz#f899b5b7a6f103a8fabb679823e8fafc7e0ee1b4"
|
||||
integrity sha512-Gxlht0TD3vVdzMDHwkiNZqJ7Mvg77xQNfmBRrNtvzcHZs72TJppSTDKHpImCMJZwcWPBJ8jSQQ95GJzXFf1nAQ==
|
||||
dependencies:
|
||||
"@types/hoist-non-react-statics" "^3.3.1"
|
||||
deepmerge "^2.1.1"
|
||||
hoist-non-react-statics "^3.3.0"
|
||||
lodash "^4.17.21"
|
||||
@ -5608,6 +5633,14 @@ html-encoding-sniffer@^3.0.0:
|
||||
dependencies:
|
||||
whatwg-encoding "^2.0.0"
|
||||
|
||||
html2canvas-pro@^1.4.3:
|
||||
version "1.4.3"
|
||||
resolved "https://registry.yarnpkg.com/html2canvas-pro/-/html2canvas-pro-1.4.3.tgz#100124e2d17d4de483700ce03176d7447e90d49f"
|
||||
integrity sha512-RB36SrUGxT9PTjImC7BsGxTinaI3y8cEne76ACdw+E7nRmeJ0jgDntxUP15B9Q9AM2mvEPN6SZo6zmkzwk8HKg==
|
||||
dependencies:
|
||||
css-line-break "^2.1.0"
|
||||
text-segmentation "^1.0.3"
|
||||
|
||||
http-cache-semantics@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a"
|
||||
@ -7231,17 +7264,17 @@ pkg-types@^1.0.3:
|
||||
mlly "^1.2.0"
|
||||
pathe "^1.1.0"
|
||||
|
||||
playwright-core@1.39.0:
|
||||
version "1.39.0"
|
||||
resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.39.0.tgz#efeaea754af4fb170d11845b8da30b2323287c63"
|
||||
integrity sha512-+k4pdZgs1qiM+OUkSjx96YiKsXsmb59evFoqv8SKO067qBA+Z2s/dCzJij/ZhdQcs2zlTAgRKfeiiLm8PQ2qvw==
|
||||
playwright-core@1.43.0:
|
||||
version "1.43.0"
|
||||
resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.43.0.tgz#d8079acb653abebb0b63062e432479647a4e1271"
|
||||
integrity sha512-iWFjyBUH97+pUFiyTqSLd8cDMMOS0r2ZYz2qEsPjH8/bX++sbIJT35MSwKnp1r/OQBAqC5XO99xFbJ9XClhf4w==
|
||||
|
||||
playwright@1.39.0:
|
||||
version "1.39.0"
|
||||
resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.39.0.tgz#184c81cd6478f8da28bcd9e60e94fcebf566e077"
|
||||
integrity sha512-naE5QT11uC/Oiq0BwZ50gDmy8c8WLPRTEWuSSFVG2egBka/1qMoSqYQcROMT9zLwJ86oPofcTH2jBY/5wWOgIw==
|
||||
playwright@1.43.0:
|
||||
version "1.43.0"
|
||||
resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.43.0.tgz#2c2efd4ee2a25defd8c24c98ccb342bdd9d435f5"
|
||||
integrity sha512-SiOKHbVjTSf6wHuGCbqrEyzlm6qvXcv7mENP+OZon1I07brfZLGdfWV0l/efAzVx7TF3Z45ov1gPEkku9q25YQ==
|
||||
dependencies:
|
||||
playwright-core "1.39.0"
|
||||
playwright-core "1.43.0"
|
||||
optionalDependencies:
|
||||
fsevents "2.3.2"
|
||||
|
||||
@ -8327,10 +8360,10 @@ supports-preserve-symlinks-flag@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
|
||||
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
|
||||
|
||||
swr@^2.2.2:
|
||||
version "2.2.2"
|
||||
resolved "https://registry.yarnpkg.com/swr/-/swr-2.2.2.tgz#abcb1f9c97e10527789884169d58b878472d4c98"
|
||||
integrity sha512-CbR41AoMD4TQBQw9ic3GTXspgfM9Y8Mdhb5Ob4uIKXhWqnRLItwA5fpGvB7SmSw3+zEjb0PdhiEumtUvYoQ+bQ==
|
||||
swr@^2.2.5:
|
||||
version "2.2.5"
|
||||
resolved "https://registry.yarnpkg.com/swr/-/swr-2.2.5.tgz#063eea0e9939f947227d5ca760cc53696f46446b"
|
||||
integrity sha512-QtxqyclFeAsxEUeZIYmsaQ0UjimSq1RZ9Un7I68/0ClKK/U3LoyQunwkQfJZr2fc22DfIXLNDc2wFyTEikCUpg==
|
||||
dependencies:
|
||||
client-only "^0.0.1"
|
||||
use-sync-external-store "^1.2.0"
|
||||
@ -8393,9 +8426,9 @@ tar-stream@^3.0.0, tar-stream@^3.1.5:
|
||||
streamx "^2.15.0"
|
||||
|
||||
tar@^6.1.11:
|
||||
version "6.1.15"
|
||||
resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.15.tgz#c9738b0b98845a3b344d334b8fa3041aaba53a69"
|
||||
integrity sha512-/zKt9UyngnxIT/EAGYuxaMYgOIJiP81ab9ZfkILq4oNLPFX50qyYmu7jRj9qeXoxmJHjGlbH0+cm2uy1WCs10A==
|
||||
version "6.2.1"
|
||||
resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.1.tgz#717549c541bc3c2af15751bea94b1dd068d4b03a"
|
||||
integrity sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==
|
||||
dependencies:
|
||||
chownr "^2.0.0"
|
||||
fs-minipass "^2.0.0"
|
||||
@ -8404,6 +8437,13 @@ tar@^6.1.11:
|
||||
mkdirp "^1.0.3"
|
||||
yallist "^4.0.0"
|
||||
|
||||
text-segmentation@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/text-segmentation/-/text-segmentation-1.0.3.tgz#52a388159efffe746b24a63ba311b6ac9f2d7943"
|
||||
integrity sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==
|
||||
dependencies:
|
||||
utrie "^1.0.2"
|
||||
|
||||
text-table@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
|
||||
@ -8423,10 +8463,10 @@ thenify-all@^1.0.0:
|
||||
dependencies:
|
||||
any-promise "^1.0.0"
|
||||
|
||||
three@^0.160.0:
|
||||
version "0.160.0"
|
||||
resolved "https://registry.yarnpkg.com/three/-/three-0.160.0.tgz#cd1e4dbd01aee0719280a9086d75545db52b7a8f"
|
||||
integrity sha512-DLU8lc0zNIPkM7rH5/e1Ks1Z8tWCGRq6g8mPowdDJpw1CFBJMU7UoJjC6PefXW7z//SSl0b2+GCw14LB+uDhng==
|
||||
three@^0.163.0:
|
||||
version "0.163.0"
|
||||
resolved "https://registry.yarnpkg.com/three/-/three-0.163.0.tgz#cbfefbfd64a1353ab7cc8bf0fc396ddca1875a49"
|
||||
integrity sha512-HlMgCb2TF/dTLRtknBnjUTsR8FsDqBY43itYop2+Zg822I+Kd0Ua2vs8CvfBVefXkBdNDrLMoRTGCIIpfCuDew==
|
||||
|
||||
through@^2.3.8:
|
||||
version "2.3.8"
|
||||
@ -8531,12 +8571,7 @@ tslib@^1.8.1:
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
|
||||
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
||||
|
||||
tslib@^2.0.0:
|
||||
version "2.6.1"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.1.tgz#fd8c9a0ff42590b25703c0acb3de3d3f4ede0410"
|
||||
integrity sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==
|
||||
|
||||
tslib@^2.0.1, tslib@^2.1.0, tslib@^2.3.0:
|
||||
tslib@^2.0.0, tslib@^2.0.1, tslib@^2.1.0, tslib@^2.3.0:
|
||||
version "2.6.2"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae"
|
||||
integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==
|
||||
@ -8644,6 +8679,11 @@ ua-parser-js@^1.0.35:
|
||||
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.35.tgz#c4ef44343bc3db0a3cbefdf21822f1b1fc1ab011"
|
||||
integrity sha512-fKnGuqmTBnIE+/KXSzCn4db8RTigUzw1AN0DmdU6hJovUTbYJKyqj+8Mt1c4VfRDnOVJnENmfYkIPZ946UrSAA==
|
||||
|
||||
ua-parser-js@^1.0.37:
|
||||
version "1.0.37"
|
||||
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.37.tgz#b5dc7b163a5c1f0c510b08446aed4da92c46373f"
|
||||
integrity sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==
|
||||
|
||||
ufo@^1.1.2:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.2.0.tgz#28d127a087a46729133fdc89cb1358508b3f80ba"
|
||||
@ -8791,6 +8831,13 @@ util@^0.12.5:
|
||||
is-typed-array "^1.1.3"
|
||||
which-typed-array "^1.1.2"
|
||||
|
||||
utrie@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/utrie/-/utrie-1.0.2.tgz#d42fe44de9bc0119c25de7f564a6ed1b2c87a645"
|
||||
integrity sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==
|
||||
dependencies:
|
||||
base64-arraybuffer "^1.0.2"
|
||||
|
||||
uuid@^9.0.1:
|
||||
version "9.0.1"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30"
|
||||
@ -9193,7 +9240,7 @@ wrappy@1:
|
||||
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
||||
integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
|
||||
|
||||
ws@8.13.0, ws@^8.13.0:
|
||||
ws@8.13.0:
|
||||
version "8.13.0"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0"
|
||||
integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==
|
||||
@ -9203,10 +9250,10 @@ ws@^7.0.0:
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591"
|
||||
integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==
|
||||
|
||||
ws@^8.8.0:
|
||||
version "8.14.2"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-8.14.2.tgz#6c249a806eb2db7a20d26d51e7709eab7b2e6c7f"
|
||||
integrity sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==
|
||||
ws@^8.16.0, ws@^8.8.0:
|
||||
version "8.16.0"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-8.16.0.tgz#d1cd774f36fbc07165066a60e40323eab6446fd4"
|
||||
integrity sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==
|
||||
|
||||
"xstate-beta@npm:xstate@beta":
|
||||
version "5.0.0-beta.54"
|
||||
@ -9307,10 +9354,10 @@ yargs@17.7.2, yargs@^17.7.2:
|
||||
y18n "^5.0.5"
|
||||
yargs-parser "^21.1.1"
|
||||
|
||||
yarn@^1.22.19:
|
||||
version "1.22.19"
|
||||
resolved "https://registry.yarnpkg.com/yarn/-/yarn-1.22.19.tgz#4ba7fc5c6e704fce2066ecbfb0b0d8976fe62447"
|
||||
integrity sha512-/0V5q0WbslqnwP91tirOvldvYISzaqhClxzyUKXYxs07yUILIs5jx/k6CFe8bvKSkds5w+eiOqta39Wk3WxdcQ==
|
||||
yarn@^1.22.22:
|
||||
version "1.22.22"
|
||||
resolved "https://registry.yarnpkg.com/yarn/-/yarn-1.22.22.tgz#ac34549e6aa8e7ead463a7407e1c7390f61a6610"
|
||||
integrity sha512-prL3kGtyG7o9Z9Sv8IPfBNrWTDmXB4Qbes8A9rEzt6wkJV8mUvoirjU0Mp3GGAU06Y0XQyA3/2/RQFVuK7MTfg==
|
||||
|
||||
yauzl@^2.10.0:
|
||||
version "2.10.0"
|
||||
|