Compare commits
18 Commits
jtran/fix-
...
achalmers/
Author | SHA1 | Date | |
---|---|---|---|
0604da9c37 | |||
29bf77bb82 | |||
e81b614523 | |||
5a5fe3bb95 | |||
0710f6e5f2 | |||
c9d5633647 | |||
f9419a98b5 | |||
999f72bccf | |||
9dbe74e008 | |||
88d9cdc52b | |||
2dd1f0f213 | |||
b971f3ecf4 | |||
2198bd7580 | |||
5fa1497b75 | |||
ff86e41283 | |||
08e4c03ca7 | |||
c654582137 | |||
6c2fa95a32 |
33
.github/workflows/ci.yml
vendored
@ -13,6 +13,7 @@ on:
|
||||
# Will checkout the last commit from the default branch (main as of 2023-10-04)
|
||||
|
||||
env:
|
||||
CUT_RELEASE_PR: ${{ github.event_name == 'pull_request' && (contains(github.event.pull_request.title, 'Cut release v')) }}
|
||||
BUILD_RELEASE: ${{ github.event_name == 'release' || github.event_name == 'schedule' || github.event_name == 'pull_request' && (contains(github.event.pull_request.title, 'Cut release v')) }}
|
||||
|
||||
concurrency:
|
||||
@ -110,8 +111,14 @@ jobs:
|
||||
echo "$(jq --arg name 'Zoo Modeling App (Nightly)' \
|
||||
'.productName=$name' src-tauri/tauri.release.conf.json --indent 2)" > src-tauri/tauri.release.conf.json
|
||||
|
||||
- name: Set updater test version
|
||||
if: ${{ env.CUT_RELEASE_PR == 'true' }}
|
||||
run: |
|
||||
echo "$(jq --arg url 'https://dl.zoo.dev/releases/modeling-app/test/last_update.json' \
|
||||
'.plugins.updater.endpoints[]=$url' src-tauri/tauri.release.conf.json --indent 2)" > src-tauri/tauri.release.conf.json
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: github.event_name == 'schedule'
|
||||
if: ${{ github.event_name == 'schedule' || env.CUT_RELEASE_PR == 'true' }}
|
||||
with:
|
||||
path: |
|
||||
package.json
|
||||
@ -377,6 +384,30 @@ jobs:
|
||||
E2E_TAURI_ENABLED: true
|
||||
TS_NODE_COMPILER_OPTIONS: '{"module": "commonjs"}'
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
if: ${{ env.CUT_RELEASE_PR == 'true' }}
|
||||
|
||||
- name: Copy updated .json file for updater test
|
||||
if: ${{ env.CUT_RELEASE_PR == 'true' }}
|
||||
run: |
|
||||
ls -l artifact
|
||||
cp artifact/src-tauri/tauri.release.conf.json src-tauri/tauri.release.conf.json
|
||||
cat src-tauri/tauri.release.conf.json
|
||||
|
||||
- name: Build the app (release, updater test)
|
||||
if: ${{ env.CUT_RELEASE_PR == 'true' && matrix.os != 'ubuntu-latest' }}
|
||||
env:
|
||||
TAURI_CONF_ARGS: "-c ${{ matrix.os == 'windows-latest' && 'src-tauri\\tauri.release.conf.json' || 'src-tauri/tauri.release.conf.json' }}"
|
||||
TAURI_BUNDLE_ARGS: "-b ${{ matrix.os == 'windows-latest' && 'msi' || 'dmg' }}"
|
||||
run: "yarn tauri build ${{ env.TAURI_CONF_ARGS }} ${{ env.TAURI_BUNDLE_ARGS }} ${{ env.TAURI_ARGS_MACOS }}"
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: ${{ env.CUT_RELEASE_PR == 'true' && matrix.os != 'ubuntu-latest' }}
|
||||
with:
|
||||
path: "${{ matrix.os == 'macos-14' && 'src-tauri/target/universal-apple-darwin/release/bundle/dmg/*.dmg' || 'src-tauri/target/release/bundle/msi/*.msi' }}"
|
||||
name: updater-test
|
||||
|
||||
|
||||
publish-apps-release:
|
||||
permissions:
|
||||
contents: write
|
||||
|
151
.github/workflows/playwright.yml
vendored
@ -83,6 +83,20 @@ jobs:
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: './src/wasm-lib'
|
||||
- name: Install vector
|
||||
run: |
|
||||
curl --proto '=https' --tlsv1.2 -sSfL https://sh.vector.dev > /tmp/vector.sh
|
||||
chmod +x /tmp/vector.sh
|
||||
/tmp/vector.sh -y -no-modify-path
|
||||
mkdir -p /tmp/vector
|
||||
cp .github/workflows/vector.toml /tmp/vector.toml
|
||||
sed -i "s#GITHUB_WORKFLOW#${GITHUB_WORKFLOW}#g" /tmp/vector.toml
|
||||
sed -i "s#GITHUB_REPOSITORY#${GITHUB_REPOSITORY}#g" /tmp/vector.toml
|
||||
sed -i "s#GITHUB_SHA#${GITHUB_SHA}#g" /tmp/vector.toml
|
||||
sed -i "s#GITHUB_REF_NAME#${GITHUB_REF_NAME}#g" /tmp/vector.toml
|
||||
sed -i "s#GH_ACTIONS_AXIOM_TOKEN#${{secrets.GH_ACTIONS_AXIOM_TOKEN}}#g" /tmp/vector.toml
|
||||
cat /tmp/vector.toml
|
||||
${HOME}/.vector/bin/vector --config /tmp/vector.toml &
|
||||
- name: Build Wasm (because rust diff)
|
||||
if: needs.check-rust-changes.outputs.rust-changed == 'true'
|
||||
run: yarn build:wasm
|
||||
@ -139,27 +153,60 @@ jobs:
|
||||
with:
|
||||
name: test-results-ubuntu-${{ github.sha }}
|
||||
path: test-results/
|
||||
- name: Run ubuntu/chrome flow retry failures
|
||||
- name: Run ubuntu/chrome flow (with retries)
|
||||
id: retry
|
||||
if: always()
|
||||
run: |
|
||||
if [[ -d "test-results" ]];
|
||||
then if [[ $(ls -1 "test-results" | wc -l) != "0" ]];
|
||||
then echo "retried=true" >> $GITHUB_OUTPUT;
|
||||
else echo "retried=false" >> $GITHUB_OUTPUT; exit 0;
|
||||
fi;
|
||||
else echo "retried=false" >> $GITHUB_OUTPUT; exit 0;
|
||||
fi;
|
||||
yarn playwright test --project="Google Chrome" --last-failed e2e/playwright/flow-tests.spec.ts
|
||||
env:
|
||||
CI: true
|
||||
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||
- name: Run ubuntu/chrome flow
|
||||
if: steps.retry.outputs.retried == 'false'
|
||||
run: yarn playwright test --project="Google Chrome" e2e/playwright/flow-tests.spec.ts
|
||||
if [[ ! -f "test-results/.last-run.json" ]]; then
|
||||
# if no last run artifact, than run plawright normally
|
||||
echo "run playwright normally"
|
||||
yarn playwright test --project="Google Chrome" e2e/playwright/flow-tests.spec.ts || true
|
||||
# # send to axiom
|
||||
node playwrightProcess.mjs | tee /tmp/github-actions.log > /dev/null 2>&1
|
||||
fi
|
||||
|
||||
retry=1
|
||||
max_retrys=4
|
||||
|
||||
# retry failed tests, doing our own retries because using inbuilt playwright retries causes connection issues
|
||||
while [[ $retry -le $max_retrys ]]; do
|
||||
if [[ -f "test-results/.last-run.json" ]]; then
|
||||
failed_tests=$(jq '.failedTests | length' test-results/.last-run.json)
|
||||
if [[ $failed_tests -gt 0 ]]; then
|
||||
echo "retried=true" >>$GITHUB_OUTPUT
|
||||
echo "run playwright with last failed tests and retry $retry"
|
||||
yarn playwright test --project="Google Chrome" --last-failed e2e/playwright/flow-tests.spec.ts || true
|
||||
# send to axiom
|
||||
node playwrightProcess.mjs | tee /tmp/github-actions.log > /dev/null 2>&1
|
||||
retry=$((retry + 1))
|
||||
else
|
||||
echo "retried=false" >>$GITHUB_OUTPUT
|
||||
exit 0
|
||||
fi
|
||||
else
|
||||
echo "retried=false" >>$GITHUB_OUTPUT
|
||||
exit 0
|
||||
fi
|
||||
done
|
||||
|
||||
echo "retried=false" >>$GITHUB_OUTPUT
|
||||
|
||||
if [[ -f "test-results/.last-run.json" ]]; then
|
||||
failed_tests=$(jq '.failedTests | length' test-results/.last-run.json)
|
||||
if [[ $failed_tests -gt 0 ]]; then
|
||||
# if it still fails after 3 retrys, then fail the job
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
exit 0
|
||||
env:
|
||||
CI: true
|
||||
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||
- name: send to axiom
|
||||
if: always()
|
||||
shell: bash
|
||||
run: |
|
||||
node playwrightProcess.mjs | tee /tmp/github-actions.log
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
@ -226,6 +273,20 @@ jobs:
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: './src/wasm-lib'
|
||||
- name: Install vector
|
||||
run: |
|
||||
curl --proto '=https' --tlsv1.2 -sSfL https://sh.vector.dev > /tmp/vector.sh
|
||||
chmod +x /tmp/vector.sh
|
||||
/tmp/vector.sh -y -no-modify-path
|
||||
mkdir -p /tmp/vector
|
||||
cp .github/workflows/vector.toml /tmp/vector.toml
|
||||
sed -i "" "s#GITHUB_WORKFLOW#${GITHUB_WORKFLOW}#g" /tmp/vector.toml
|
||||
sed -i "" "s#GITHUB_REPOSITORY#${GITHUB_REPOSITORY}#g" /tmp/vector.toml
|
||||
sed -i "" "s#GITHUB_SHA#${GITHUB_SHA}#g" /tmp/vector.toml
|
||||
sed -i "" "s#GITHUB_REF_NAME#${GITHUB_REF_NAME}#g" /tmp/vector.toml
|
||||
sed -i "" "s#GH_ACTIONS_AXIOM_TOKEN#${{secrets.GH_ACTIONS_AXIOM_TOKEN}}#g" /tmp/vector.toml
|
||||
cat /tmp/vector.toml
|
||||
${HOME}/.vector/bin/vector --config /tmp/vector.toml &
|
||||
- name: Build Wasm (because rust diff)
|
||||
if: needs.check-rust-changes.outputs.rust-changed == 'true'
|
||||
run: yarn build:wasm
|
||||
@ -241,26 +302,52 @@ jobs:
|
||||
with:
|
||||
name: test-results-macos-${{ github.sha }}
|
||||
path: test-results/
|
||||
- name: Run macos/safari flow retry failures
|
||||
- name: Run macos/safari flow (with retries)
|
||||
id: retry
|
||||
if: always()
|
||||
run: |
|
||||
if [[ -d "test-results" ]];
|
||||
then if [[ $(ls -1 "test-results" | wc -l) != "0" ]];
|
||||
then echo "retried=true" >> $GITHUB_OUTPUT;
|
||||
else echo "retried=false" >> $GITHUB_OUTPUT; exit 0;
|
||||
fi;
|
||||
else echo "retried=false" >> $GITHUB_OUTPUT; exit 0;
|
||||
fi;
|
||||
yarn playwright test --project="webkit" --last-failed e2e/playwright/flow-tests.spec.ts
|
||||
env:
|
||||
CI: true
|
||||
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||
- name: Run macos/safari flow
|
||||
if: steps.retry.outputs.retried == 'false'
|
||||
# webkit doesn't work on Ubuntu because of the same reason tauri doesn't (webRTC issues)
|
||||
# TODO remove this and the matrix and run all tests on ubuntu when this is fixed
|
||||
run: yarn playwright test --project="webkit" e2e/playwright/flow-tests.spec.ts
|
||||
if [[ ! -f "test-results/.last-run.json" ]]; then
|
||||
# if no last run artifact, than run plawright normally
|
||||
echo "run playwright normally"
|
||||
yarn playwright test --project="webkit" e2e/playwright/flow-tests.spec.ts || true
|
||||
# # send to axiom
|
||||
node playwrightProcess.mjs | tee /tmp/github-actions.log > /dev/null 2>&1
|
||||
fi
|
||||
|
||||
retry=1
|
||||
max_retrys=4
|
||||
|
||||
# retry failed tests, doing our own retries because using inbuilt playwright retries causes connection issues
|
||||
while [[ $retry -le $max_retrys ]]; do
|
||||
if [[ -f "test-results/.last-run.json" ]]; then
|
||||
failed_tests=$(jq '.failedTests | length' test-results/.last-run.json)
|
||||
if [[ $failed_tests -gt 0 ]]; then
|
||||
echo "retried=true" >>$GITHUB_OUTPUT
|
||||
echo "run playwright with last failed tests and retry $retry"
|
||||
yarn playwright test --project="webkit" --last-failed e2e/playwright/flow-tests.spec.ts || true
|
||||
# send to axiom
|
||||
node playwrightProcess.mjs | tee /tmp/github-actions.log > /dev/null 2>&1
|
||||
retry=$((retry + 1))
|
||||
else
|
||||
echo "retried=false" >>$GITHUB_OUTPUT
|
||||
exit 0
|
||||
fi
|
||||
else
|
||||
echo "retried=false" >>$GITHUB_OUTPUT
|
||||
exit 0
|
||||
fi
|
||||
done
|
||||
|
||||
echo "retried=false" >>$GITHUB_OUTPUT
|
||||
|
||||
if [[ -f "test-results/.last-run.json" ]]; then
|
||||
failed_tests=$(jq '.failedTests | length' test-results/.last-run.json)
|
||||
if [[ $failed_tests -gt 0 ]]; then
|
||||
# if it still fails after 3 retrys, then fail the job
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
exit 0
|
||||
env:
|
||||
CI: true
|
||||
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||
|
Before Width: | Height: | Size: 249 KiB After Width: | Height: | Size: 249 KiB |
Before Width: | Height: | Size: 249 KiB After Width: | Height: | Size: 249 KiB |
Before Width: | Height: | Size: 249 KiB After Width: | Height: | Size: 249 KiB |
Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 171 KiB |
Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 171 KiB |
Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 171 KiB |
Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 171 KiB |
Before Width: | Height: | Size: 249 KiB After Width: | Height: | Size: 249 KiB |
Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 171 KiB |
Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 171 KiB |
@ -3691,6 +3691,46 @@ const extrude001 = extrude(distance001, sketch001)`.replace(
|
||||
) // remove newlines
|
||||
)
|
||||
})
|
||||
|
||||
test('Can switch between sketch tools via command bar', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
const sketchButton = page.getByRole('button', { name: 'Start Sketch' })
|
||||
const cmdBarButton = page.getByRole('button', { name: 'Commands' })
|
||||
const rectangleToolCommand = page.getByRole('option', {
|
||||
name: 'Rectangle',
|
||||
})
|
||||
const rectangleToolButton = page.getByRole('button', { name: 'Rectangle' })
|
||||
const lineToolCommand = page.getByRole('option', { name: 'Line' })
|
||||
const lineToolButton = page.getByRole('button', { name: 'Line' })
|
||||
const arcToolCommand = page.getByRole('option', { name: 'Tangential Arc' })
|
||||
const arcToolButton = page.getByRole('button', { name: 'Tangential Arc' })
|
||||
|
||||
// Start a sketch
|
||||
await sketchButton.click()
|
||||
await page.mouse.click(700, 200)
|
||||
|
||||
// Switch between sketch tools via the command bar
|
||||
await expect(lineToolButton).toHaveAttribute('aria-pressed', 'true')
|
||||
await cmdBarButton.click()
|
||||
await rectangleToolCommand.click()
|
||||
await expect(rectangleToolButton).toHaveAttribute('aria-pressed', 'true')
|
||||
await cmdBarButton.click()
|
||||
await lineToolCommand.click()
|
||||
await expect(lineToolButton).toHaveAttribute('aria-pressed', 'true')
|
||||
|
||||
// Click in the scene a couple times to draw a line
|
||||
// so tangential arc is valid
|
||||
await page.mouse.click(700, 200)
|
||||
await page.mouse.click(700, 300)
|
||||
|
||||
// switch to tangential arc via command bar
|
||||
await cmdBarButton.click()
|
||||
await arcToolCommand.click()
|
||||
await expect(arcToolButton).toHaveAttribute('aria-pressed', 'true')
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Regression tests', () => {
|
||||
@ -7726,6 +7766,31 @@ test('Basic default modeling and sketch hotkeys work', async ({ page }) => {
|
||||
await expect(page.locator('.cm-content')).toContainText('extrude(')
|
||||
})
|
||||
|
||||
test('Delete key does not navigate back', async ({ page }) => {
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.goto('/')
|
||||
await page.waitForURL('**/file/**', { waitUntil: 'domcontentloaded' })
|
||||
|
||||
const settingsButton = page.getByRole('link', {
|
||||
name: 'Settings',
|
||||
exact: false,
|
||||
})
|
||||
const settingsCloseButton = page.getByTestId('settings-close-button')
|
||||
|
||||
await settingsButton.click()
|
||||
await expect(page.url()).toContain('/settings')
|
||||
|
||||
// Make sure that delete doesn't go back from settings
|
||||
await page.keyboard.press('Delete')
|
||||
await expect(page.url()).toContain('/settings')
|
||||
|
||||
// Now close the settings and try delete again,
|
||||
// make sure it doesn't go back to settings
|
||||
await settingsCloseButton.click()
|
||||
await page.keyboard.press('Delete')
|
||||
await expect(page.url()).not.toContain('/settings')
|
||||
})
|
||||
|
||||
test('Sketch on face', async ({ page }) => {
|
||||
test.setTimeout(90_000)
|
||||
const u = await getUtils(page)
|
||||
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 71 KiB |
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 44 KiB |
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "untitled-app",
|
||||
"version": "0.24.0",
|
||||
"version": "0.24.1",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.17.0",
|
||||
|
@ -93,23 +93,10 @@ export class LanguageServerPlugin implements PluginValue {
|
||||
private doSemanticTokens: boolean = false
|
||||
private doFoldingRanges: boolean = false
|
||||
|
||||
private _defferer = deferExecution((code: string) => {
|
||||
try {
|
||||
// Update the state (not the editor) with the new code.
|
||||
this.client.textDocumentDidChange({
|
||||
textDocument: {
|
||||
uri: this.getDocUri(),
|
||||
version: this.documentVersion++,
|
||||
},
|
||||
contentChanges: [{ text: code }],
|
||||
})
|
||||
|
||||
this.requestSemanticTokens()
|
||||
this.updateFoldingRanges()
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}, this.changesDelay)
|
||||
// When a doc update needs to be sent to the server, this holds the
|
||||
// timeout handle for it. When null, the server has the up-to-date
|
||||
// document.
|
||||
private sendScheduled: number | null = null
|
||||
|
||||
constructor(options: LanguageServerOptions, private view: EditorView) {
|
||||
this.client = options.client
|
||||
@ -152,14 +139,9 @@ export class LanguageServerPlugin implements PluginValue {
|
||||
}
|
||||
|
||||
update(viewUpdate: ViewUpdate) {
|
||||
// If the doc didn't change we can return early.
|
||||
if (!viewUpdate.docChanged) {
|
||||
return
|
||||
if (viewUpdate.docChanged) {
|
||||
this.scheduleSendDoc()
|
||||
}
|
||||
|
||||
this.sendChange({
|
||||
documentText: viewUpdate.state.doc.toString(),
|
||||
})
|
||||
}
|
||||
|
||||
destroy() {
|
||||
@ -184,16 +166,6 @@ export class LanguageServerPlugin implements PluginValue {
|
||||
this.updateFoldingRanges()
|
||||
}
|
||||
|
||||
async sendChange({ documentText }: { documentText: string }) {
|
||||
if (!this.client.ready) return
|
||||
|
||||
this._defferer(documentText)
|
||||
}
|
||||
|
||||
requestDiagnostics() {
|
||||
this.sendChange({ documentText: this.getDocText() })
|
||||
}
|
||||
|
||||
async requestHoverTooltip(
|
||||
view: EditorView,
|
||||
{ line, character }: { line: number; character: number }
|
||||
@ -204,7 +176,7 @@ export class LanguageServerPlugin implements PluginValue {
|
||||
)
|
||||
return null
|
||||
|
||||
this.sendChange({ documentText: this.getDocText() })
|
||||
this.ensureDocSent()
|
||||
const result = await this.client.textDocumentHover({
|
||||
textDocument: { uri: this.getDocUri() },
|
||||
position: { line, character },
|
||||
@ -227,6 +199,42 @@ export class LanguageServerPlugin implements PluginValue {
|
||||
return { pos, end, create: (view) => ({ dom }), above: true }
|
||||
}
|
||||
|
||||
scheduleSendDoc() {
|
||||
if (this.sendScheduled != null) window.clearTimeout(this.sendScheduled)
|
||||
this.sendScheduled = window.setTimeout(
|
||||
() => this.sendDoc(),
|
||||
this.changesDelay
|
||||
)
|
||||
}
|
||||
|
||||
sendDoc() {
|
||||
if (this.sendScheduled != null) {
|
||||
window.clearTimeout(this.sendScheduled)
|
||||
this.sendScheduled = null
|
||||
}
|
||||
|
||||
if (!this.client.ready) return
|
||||
try {
|
||||
// Update the state (not the editor) with the new code.
|
||||
this.client.textDocumentDidChange({
|
||||
textDocument: {
|
||||
uri: this.getDocUri(),
|
||||
version: this.documentVersion++,
|
||||
},
|
||||
contentChanges: [{ text: this.view.state.doc.toString() }],
|
||||
})
|
||||
|
||||
this.requestSemanticTokens()
|
||||
this.updateFoldingRanges()
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
ensureDocSent() {
|
||||
if (this.sendScheduled != null) this.sendDoc()
|
||||
}
|
||||
|
||||
async getFoldingRanges(): Promise<LSP.FoldingRange[] | null> {
|
||||
if (
|
||||
!this.doFoldingRanges ||
|
||||
@ -284,13 +292,7 @@ export class LanguageServerPlugin implements PluginValue {
|
||||
)
|
||||
return null
|
||||
|
||||
this.client.textDocumentDidChange({
|
||||
textDocument: {
|
||||
uri: this.getDocUri(),
|
||||
version: this.documentVersion++,
|
||||
},
|
||||
contentChanges: [{ text: this.getDocText() }],
|
||||
})
|
||||
this.ensureDocSent()
|
||||
|
||||
const result = await this.client.textDocumentFormatting({
|
||||
textDocument: { uri: this.getDocUri() },
|
||||
@ -330,9 +332,7 @@ export class LanguageServerPlugin implements PluginValue {
|
||||
)
|
||||
return null
|
||||
|
||||
this.sendChange({
|
||||
documentText: context.state.doc.toString(),
|
||||
})
|
||||
this.ensureDocSent()
|
||||
|
||||
const result = await this.client.textDocumentCompletion({
|
||||
textDocument: { uri: this.getDocUri() },
|
||||
|
@ -20,7 +20,10 @@ export default defineConfig({
|
||||
/* Different amount of parallelism on CI and local. */
|
||||
workers: process.env.CI ? 4 : 4,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: 'html',
|
||||
reporter: [
|
||||
[process.env.CI ? 'dot' : 'list'],
|
||||
['json', { outputFile: './test-results/report.json' }],
|
||||
],
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||
|
65
playwrightProcess.mjs
Normal file
@ -0,0 +1,65 @@
|
||||
import { readFileSync } from 'fs'
|
||||
|
||||
const data = readFileSync('./test-results/report.json', 'utf8')
|
||||
|
||||
// types, but was easier to store and run as normal js
|
||||
// interface FailedTest {
|
||||
// name: string;
|
||||
// projectName: string;
|
||||
// error: string;
|
||||
// }
|
||||
|
||||
// interface Spec {
|
||||
// title: string;
|
||||
// tests: Test[];
|
||||
// }
|
||||
|
||||
// interface Test {
|
||||
// expectedStatus: 'passed' | 'failed' | 'pending';
|
||||
// projectName: string;
|
||||
// title: string;
|
||||
// results: {
|
||||
// status: 'passed' | 'failed' | 'pending';
|
||||
// error: {stack: string}
|
||||
// }[]
|
||||
// }
|
||||
|
||||
// interface Suite {
|
||||
// title: string
|
||||
// suites: Suite[];
|
||||
// specs: Spec[];
|
||||
// }
|
||||
|
||||
// const processReport = (suites: Suite[]): FailedTest[] => {
|
||||
// const failedTests: FailedTest[] = []
|
||||
// const loopSuites = (suites: Suite[], previousName = '') => {
|
||||
const processReport = (suites) => {
|
||||
const failedTests = []
|
||||
const loopSuites = (suites, previousName = '') => {
|
||||
if (!suites) return
|
||||
for (const suite of suites) {
|
||||
const name = (previousName ? `${previousName} -- ` : '') + suite.title
|
||||
for (const spec of suite.specs) {
|
||||
for (const test of spec.tests) {
|
||||
for (const result of test.results) {
|
||||
if ((result.status !== 'passed') && test.expectedStatus === 'passed') {
|
||||
failedTests.push({
|
||||
name: (name + ' -- ' + spec.title) + (test.title ? ` -- ${test.title}` : ''),
|
||||
status: result.status,
|
||||
projectName: test.projectName,
|
||||
error: result.error?.stack,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
loopSuites(suite.suites, name)
|
||||
}
|
||||
}
|
||||
loopSuites(suites)
|
||||
return failedTests.map(line => JSON.stringify(line)).join('\n')
|
||||
}
|
||||
const failedTests = processReport(JSON.parse(data).suites)
|
||||
// log to stdout to be piped to axiom
|
||||
console.log(failedTests)
|
||||
|
130
src-tauri/Cargo.lock
generated
@ -332,7 +332,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.70",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -361,13 +361,13 @@ checksum = "fbb36e985947064623dbd357f727af08ffd077f93d696782f3c56365fa2e2799"
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.80"
|
||||
version = "0.1.81"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca"
|
||||
checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.70",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -407,7 +407,7 @@ checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.70",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -550,7 +550,7 @@ dependencies = [
|
||||
"proc-macro-crate 3.1.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.70",
|
||||
"syn_derive",
|
||||
]
|
||||
|
||||
@ -792,9 +792,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.7"
|
||||
version = "4.5.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f"
|
||||
checksum = "64acc1846d54c1fe936a78dc189c34e28d3f5afc348403f28ecf53660b9b8462"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@ -802,9 +802,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.7"
|
||||
version = "4.5.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f"
|
||||
checksum = "6fb8393d67ba2e7bfaf28a23458e4e2b543cc73a99595511eb207fdb8aede942"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@ -816,14 +816,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.5"
|
||||
version = "4.5.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6"
|
||||
checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085"
|
||||
dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.70",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1073,7 +1073,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.70",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1083,7 +1083,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.70",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1107,7 +1107,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim 0.10.0",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.70",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1118,7 +1118,7 @@ checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.70",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1179,7 +1179,7 @@ checksum = "4078275de501a61ceb9e759d37bdd3d7210e654dbc167ac1a3678ef4435ed57b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.70",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
@ -1216,7 +1216,7 @@ dependencies = [
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_tokenstream",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.70",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1227,7 +1227,7 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.70",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1288,7 +1288,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.70",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1320,7 +1320,7 @@ checksum = "f2b99bf03862d7f545ebc28ddd33a665b50865f4dfd84031a393823879bd4c54"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.70",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1427,7 +1427,7 @@ checksum = "5c785274071b1b420972453b306eeca06acf4633829db4223b58a2a8c5953bc4"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.70",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1588,7 +1588,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.70",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1704,7 +1704,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.70",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1980,7 +1980,7 @@ dependencies = [
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.70",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2008,7 +2008,7 @@ dependencies = [
|
||||
"inflections",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.70",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2083,7 +2083,7 @@ dependencies = [
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.70",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2571,7 +2571,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kcl-lib"
|
||||
version = "0.1.70"
|
||||
version = "0.1.72"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"approx",
|
||||
@ -3377,7 +3377,7 @@ dependencies = [
|
||||
"regex",
|
||||
"regex-syntax 0.8.3",
|
||||
"structmeta",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.70",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3496,7 +3496,7 @@ dependencies = [
|
||||
"phf_shared 0.11.2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.70",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3564,7 +3564,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.70",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4438,7 +4438,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde_derive_internals",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.70",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4523,9 +4523,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.203"
|
||||
version = "1.0.204"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094"
|
||||
checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
@ -4552,13 +4552,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.203"
|
||||
version = "1.0.204"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba"
|
||||
checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.70",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4569,7 +4569,7 @@ checksum = "330f01ce65a3a5fe59a60c82f3c9a024b573b8a6e875bd233fe5f934e71d54e3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.70",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4602,7 +4602,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.70",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4623,7 +4623,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.70",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4665,7 +4665,7 @@ dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.70",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4933,7 +4933,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"structmeta-derive",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.70",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4944,7 +4944,7 @@ checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.70",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4966,7 +4966,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.70",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4999,9 +4999,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.68"
|
||||
version = "2.0.70"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9"
|
||||
checksum = "2f0209b68b3613b093e0ec905354eccaedcfe83b8cb37cbdeae64026c3064c16"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -5017,7 +5017,7 @@ dependencies = [
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.70",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -5034,7 +5034,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.70",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -5251,7 +5251,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.70",
|
||||
"tauri-utils",
|
||||
"thiserror",
|
||||
"time",
|
||||
@ -5269,7 +5269,7 @@ dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.70",
|
||||
"tauri-codegen",
|
||||
"tauri-utils",
|
||||
]
|
||||
@ -5642,7 +5642,7 @@ checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.70",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -5740,7 +5740,7 @@ checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.70",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -5940,7 +5940,7 @@ checksum = "84fd902d4e0b9a4b27f2f440108dc034e1758628a9b702f8ec61ad66355422fa"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.70",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -5969,7 +5969,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.70",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -6099,7 +6099,7 @@ checksum = "c88cc88fd23b5a04528f3a8436024f20010a16ec18eb23c164b1242f65860130"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.70",
|
||||
"termcolor",
|
||||
]
|
||||
|
||||
@ -6280,9 +6280,9 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.9.1"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5de17fd2f7da591098415cff336e12965a28061ddace43b59cb3c430179c9439"
|
||||
checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314"
|
||||
dependencies = [
|
||||
"getrandom 0.2.14",
|
||||
"serde",
|
||||
@ -6316,7 +6316,7 @@ dependencies = [
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.70",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -6415,7 +6415,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.70",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
@ -6449,7 +6449,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.70",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
@ -6590,7 +6590,7 @@ checksum = "ac1345798ecd8122468840bcdf1b95e5dc6d2206c5e4b0eafa078d061f59c9bc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.70",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -6696,7 +6696,7 @@ checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.70",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -6707,7 +6707,7 @@ checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.70",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -7159,7 +7159,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.70",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -80,5 +80,5 @@
|
||||
}
|
||||
},
|
||||
"productName": "Zoo Modeling App",
|
||||
"version": "0.24.0"
|
||||
"version": "0.24.1"
|
||||
}
|
||||
|
@ -39,3 +39,32 @@ export const AppStateProvider = ({ children }: { children: ReactNode }) => {
|
||||
</AppStateContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
interface AppStream {
|
||||
mediaStream: MediaStream
|
||||
setMediaStream: (mediaStream: MediaStream) => void
|
||||
}
|
||||
|
||||
const AppStreamContext = createContext<AppStream>({
|
||||
mediaStream: undefined as unknown as MediaStream,
|
||||
setMediaStream: () => {},
|
||||
})
|
||||
|
||||
export const useAppStream = () => useContext(AppStreamContext)
|
||||
|
||||
export const AppStreamProvider = ({ children }: { children: ReactNode }) => {
|
||||
const [mediaStream, setMediaStream] = useState<MediaStream>(
|
||||
undefined as unknown as MediaStream
|
||||
)
|
||||
|
||||
return (
|
||||
<AppStreamContext.Provider
|
||||
value={{
|
||||
mediaStream,
|
||||
setMediaStream,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</AppStreamContext.Provider>
|
||||
)
|
||||
}
|
||||
|
@ -60,7 +60,7 @@ export function Toolbar({
|
||||
? send('CancelSketch')
|
||||
: send({
|
||||
type: 'change tool',
|
||||
data: 'line',
|
||||
data: { tool: 'line' },
|
||||
}),
|
||||
{ enabled: !disableLineButton, scopes: ['sketch'] }
|
||||
)
|
||||
@ -75,7 +75,7 @@ export function Toolbar({
|
||||
? send('CancelSketch')
|
||||
: send({
|
||||
type: 'change tool',
|
||||
data: 'tangentialArc',
|
||||
data: { tool: 'tangentialArc' },
|
||||
}),
|
||||
{ enabled: !disableTangentialArc, scopes: ['sketch'] }
|
||||
)
|
||||
@ -89,7 +89,7 @@ export function Toolbar({
|
||||
? send('CancelSketch')
|
||||
: send({
|
||||
type: 'change tool',
|
||||
data: 'rectangle',
|
||||
data: { tool: 'rectangle' },
|
||||
}),
|
||||
{ enabled: !disableRectangle, scopes: ['sketch'] }
|
||||
)
|
||||
@ -114,7 +114,7 @@ export function Toolbar({
|
||||
() =>
|
||||
commandBarSend({
|
||||
type: 'Find and select command',
|
||||
data: { name: 'Extrude', ownerMachine: 'modeling' },
|
||||
data: { name: 'Extrude', groupId: 'modeling' },
|
||||
}),
|
||||
{ enabled: !disableAllButtons, scopes: ['modeling'] }
|
||||
)
|
||||
@ -263,7 +263,7 @@ export function Toolbar({
|
||||
? send('CancelSketch')
|
||||
: send({
|
||||
type: 'change tool',
|
||||
data: 'line',
|
||||
data: { tool: 'line' },
|
||||
})
|
||||
}
|
||||
aria-pressed={state?.matches('Sketch.Line tool')}
|
||||
@ -293,7 +293,7 @@ export function Toolbar({
|
||||
? send('CancelSketch')
|
||||
: send({
|
||||
type: 'change tool',
|
||||
data: 'tangentialArc',
|
||||
data: { tool: 'tangentialArc' },
|
||||
})
|
||||
}
|
||||
aria-pressed={state.matches('Sketch.Tangential arc to')}
|
||||
@ -323,7 +323,7 @@ export function Toolbar({
|
||||
? send('CancelSketch')
|
||||
: send({
|
||||
type: 'change tool',
|
||||
data: 'rectangle',
|
||||
data: { tool: 'rectangle' },
|
||||
})
|
||||
}
|
||||
aria-pressed={state.matches('Sketch.Rectangle tool')}
|
||||
@ -378,7 +378,7 @@ export function Toolbar({
|
||||
onClick={() =>
|
||||
commandBarSend({
|
||||
type: 'Find and select command',
|
||||
data: { name: 'Extrude', ownerMachine: 'modeling' },
|
||||
data: { name: 'Extrude', groupId: 'modeling' },
|
||||
})
|
||||
}
|
||||
disabled={!state.can('Extrude') || disableAllButtons}
|
||||
|
@ -518,9 +518,9 @@ export class CameraControls {
|
||||
direction.normalize()
|
||||
this.camera.position.copy(this.target).addScaledVector(direction, distance)
|
||||
}
|
||||
usePerspectiveCamera = async () => {
|
||||
usePerspectiveCamera = async (forceSend = false) => {
|
||||
this._usePerspectiveCamera()
|
||||
if (this.syncDirection === 'clientToEngine') {
|
||||
if (forceSend || this.syncDirection === 'clientToEngine') {
|
||||
await this.engineCommandManager.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
|
@ -717,7 +717,7 @@ export const CamDebugSettings = () => {
|
||||
if (camSettings.type === 'perspective') {
|
||||
sceneInfra.camControls.useOrthographicCamera()
|
||||
} else {
|
||||
sceneInfra.camControls.usePerspectiveCamera()
|
||||
sceneInfra.camControls.usePerspectiveCamera(true)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
@ -28,6 +28,11 @@ export const CommandBarProvider = ({
|
||||
Object.keys(context.selectedCommand?.args).length === 0
|
||||
)
|
||||
},
|
||||
'All arguments are skippable': (context, _event) => {
|
||||
return Object.values(context.selectedCommand!.args!).every(
|
||||
(argConfig) => argConfig.skip
|
||||
)
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
|
@ -70,19 +70,23 @@ function CommandComboBox({
|
||||
>
|
||||
{filteredOptions?.map((option) => (
|
||||
<Combobox.Option
|
||||
key={option.name}
|
||||
key={option.groupId + option.name + (option.displayName || '')}
|
||||
value={option}
|
||||
className="flex items-center gap-2 px-4 py-1 first:mt-2 last:mb-2 ui-active:bg-primary/10 dark:ui-active:bg-chalkboard-90"
|
||||
className="flex items-center gap-4 px-4 py-1.5 first:mt-2 last:mb-2 ui-active:bg-primary/10 dark:ui-active:bg-chalkboard-90"
|
||||
>
|
||||
{'icon' in option && option.icon && (
|
||||
<CustomIcon name={option.icon} className="w-5 h-5" />
|
||||
)}
|
||||
<p className="flex-grow">{option.displayName || option.name} </p>
|
||||
{option.description && (
|
||||
<p className="text-xs text-chalkboard-60 dark:text-chalkboard-40">
|
||||
{option.description}
|
||||
<div className="flex-grow flex flex-col">
|
||||
<p className="my-0 leading-tight">
|
||||
{option.displayName || option.name}{' '}
|
||||
</p>
|
||||
)}
|
||||
{option.description && (
|
||||
<p className="my-0 text-xs text-chalkboard-60 dark:text-chalkboard-50">
|
||||
{option.description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</Combobox.Option>
|
||||
))}
|
||||
</Combobox.Options>
|
||||
|
@ -131,6 +131,16 @@ const CustomIconMap = {
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
code: (
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M10.7071 5L7.77734 14.7794L8.73527 15.0663L11.665 5.28698L10.7071 5ZM2.35356 9.64644L5.85362 6.14644L6.56072 6.85355L3.41423 10L6.56072 13.1464L5.85362 13.8536L2.35356 10.3536L2 10L2.35356 9.64644ZM17.0607 9.64644L13.5607 6.14644L12.8536 6.85355L16 10L12.8536 13.1464L13.5607 13.8535L17.0607 10.3536L17.4142 10L17.0607 9.64644Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
dimension: (
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
|
@ -42,7 +42,7 @@ import {
|
||||
import { applyConstraintIntersect } from './Toolbar/Intersect'
|
||||
import { applyConstraintAbsDistance } from './Toolbar/SetAbsDistance'
|
||||
import useStateMachineCommands from 'hooks/useStateMachineCommands'
|
||||
import { modelingMachineConfig } from 'lib/commandBarConfigs/modelingCommandConfig'
|
||||
import { modelingMachineCommandConfig } from 'lib/commandBarConfigs/modelingCommandConfig'
|
||||
import {
|
||||
STRAIGHT_SEGMENT,
|
||||
TANGENTIAL_ARC_TO_SEGMENT,
|
||||
@ -920,7 +920,7 @@ export const ModelingMachineProvider = ({
|
||||
state: modelingState,
|
||||
send: modelingSend,
|
||||
actor: modelingActor,
|
||||
commandBarConfig: modelingMachineConfig,
|
||||
commandBarConfig: modelingMachineCommandConfig,
|
||||
allCommandsRequireNetwork: true,
|
||||
// TODO for when sketch tools are in the toolbar: This was added when we used one "Cancel" event,
|
||||
// but we need to support "SketchCancel" and basically
|
||||
|
@ -82,11 +82,11 @@ function ProjectMenuPopover({
|
||||
}) {
|
||||
const { commandBarState, commandBarSend } = useCommandsContext()
|
||||
const { onProjectClose } = useLspContext()
|
||||
const exportCommandInfo = { name: 'Export', ownerMachine: 'modeling' }
|
||||
const findCommand = (obj: { name: string; ownerMachine: string }) =>
|
||||
const exportCommandInfo = { name: 'Export', groupId: 'modeling' }
|
||||
const findCommand = (obj: { name: string; groupId: string }) =>
|
||||
Boolean(
|
||||
commandBarState.context.commands.find(
|
||||
(c) => c.name === obj.name && c.ownerMachine === obj.ownerMachine
|
||||
(c) => c.name === obj.name && c.groupId === obj.groupId
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { DEV } from 'env'
|
||||
import { MouseEventHandler, useEffect, useRef, useState } from 'react'
|
||||
import { getNormalisedCoordinates } from '../lib/utils'
|
||||
import Loading from './Loading'
|
||||
@ -6,9 +7,10 @@ import { useModelingContext } from 'hooks/useModelingContext'
|
||||
import { useNetworkContext } from 'hooks/useNetworkContext'
|
||||
import { NetworkHealthState } from 'hooks/useNetworkStatus'
|
||||
import { ClientSideScene } from 'clientSideScene/ClientSideSceneComp'
|
||||
import { butName } from 'lib/cameraControls'
|
||||
import { btnName } from 'lib/cameraControls'
|
||||
import { sendSelectEventToEngine } from 'lib/selections'
|
||||
import { kclManager, engineCommandManager, sceneInfra } from 'lib/singletons'
|
||||
import { useAppStream } from 'AppState'
|
||||
|
||||
export const Stream = () => {
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
@ -17,9 +19,12 @@ export const Stream = () => {
|
||||
const videoRef = useRef<HTMLVideoElement>(null)
|
||||
const { settings } = useSettingsAuthContext()
|
||||
const { state, send, context } = useModelingContext()
|
||||
const { mediaStream } = useAppStream()
|
||||
const { overallState } = useNetworkContext()
|
||||
const [isFreezeFrame, setIsFreezeFrame] = useState(false)
|
||||
|
||||
const IDLE = true
|
||||
|
||||
const isNetworkOkay =
|
||||
overallState === NetworkHealthState.Ok ||
|
||||
overallState === NetworkHealthState.Weak
|
||||
@ -51,7 +56,7 @@ export const Stream = () => {
|
||||
capture: true,
|
||||
})
|
||||
|
||||
const IDLE_TIME_MS = 1000 * 20
|
||||
const IDLE_TIME_MS = 1000 * 60 * 2
|
||||
let timeoutIdIdleA: ReturnType<typeof setTimeout> | undefined = undefined
|
||||
|
||||
const teardown = () => {
|
||||
@ -60,19 +65,21 @@ export const Stream = () => {
|
||||
sceneInfra.modelingSend({ type: 'Cancel' })
|
||||
// Give video time to pause
|
||||
window.requestAnimationFrame(() => {
|
||||
engineCommandManager.engineConnection?.tearDown({ freeze: true })
|
||||
engineCommandManager.tearDown()
|
||||
})
|
||||
}
|
||||
|
||||
// Teardown everything if we go hidden or reconnect
|
||||
if (globalThis?.window?.document) {
|
||||
globalThis.window.document.onvisibilitychange = () => {
|
||||
if (globalThis.window.document.visibilityState === 'hidden') {
|
||||
clearTimeout(timeoutIdIdleA)
|
||||
timeoutIdIdleA = setTimeout(teardown, IDLE_TIME_MS)
|
||||
} else if (!engineCommandManager.engineConnection?.isReady()) {
|
||||
clearTimeout(timeoutIdIdleA)
|
||||
engineCommandManager.engineConnection?.connect(true)
|
||||
if (IDLE && DEV) {
|
||||
if (globalThis?.window?.document) {
|
||||
globalThis.window.document.onvisibilitychange = () => {
|
||||
if (globalThis.window.document.visibilityState === 'hidden') {
|
||||
clearTimeout(timeoutIdIdleA)
|
||||
timeoutIdIdleA = setTimeout(teardown, IDLE_TIME_MS)
|
||||
} else if (!engineCommandManager.engineConnection?.isReady()) {
|
||||
clearTimeout(timeoutIdIdleA)
|
||||
engineCommandManager.engineConnection?.connect(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -80,35 +87,44 @@ export const Stream = () => {
|
||||
let timeoutIdIdleB: ReturnType<typeof setTimeout> | undefined = undefined
|
||||
|
||||
const onAnyInput = () => {
|
||||
if (!engineCommandManager.engineConnection?.isReady()) {
|
||||
engineCommandManager.engineConnection?.connect(true)
|
||||
}
|
||||
// Clear both timers
|
||||
clearTimeout(timeoutIdIdleA)
|
||||
clearTimeout(timeoutIdIdleB)
|
||||
timeoutIdIdleB = setTimeout(teardown, IDLE_TIME_MS)
|
||||
}
|
||||
|
||||
globalThis?.window?.document?.addEventListener('keydown', onAnyInput)
|
||||
globalThis?.window?.document?.addEventListener('mousemove', onAnyInput)
|
||||
globalThis?.window?.document?.addEventListener('mousedown', onAnyInput)
|
||||
globalThis?.window?.document?.addEventListener('scroll', onAnyInput)
|
||||
globalThis?.window?.document?.addEventListener('touchstart', onAnyInput)
|
||||
if (IDLE && DEV) {
|
||||
globalThis?.window?.document?.addEventListener('keydown', onAnyInput)
|
||||
globalThis?.window?.document?.addEventListener('mousemove', onAnyInput)
|
||||
globalThis?.window?.document?.addEventListener('mousedown', onAnyInput)
|
||||
globalThis?.window?.document?.addEventListener('scroll', onAnyInput)
|
||||
globalThis?.window?.document?.addEventListener('touchstart', onAnyInput)
|
||||
}
|
||||
|
||||
timeoutIdIdleB = setTimeout(teardown, IDLE_TIME_MS)
|
||||
if (IDLE && DEV) {
|
||||
timeoutIdIdleB = setTimeout(teardown, IDLE_TIME_MS)
|
||||
}
|
||||
|
||||
return () => {
|
||||
globalThis?.window?.document?.removeEventListener('paste', handlePaste, {
|
||||
capture: true,
|
||||
})
|
||||
globalThis?.window?.document?.removeEventListener('keydown', onAnyInput)
|
||||
globalThis?.window?.document?.removeEventListener('mousemove', onAnyInput)
|
||||
globalThis?.window?.document?.removeEventListener('mousedown', onAnyInput)
|
||||
globalThis?.window?.document?.removeEventListener('scroll', onAnyInput)
|
||||
globalThis?.window?.document?.removeEventListener(
|
||||
'touchstart',
|
||||
onAnyInput
|
||||
)
|
||||
if (IDLE && DEV) {
|
||||
globalThis?.window?.document?.removeEventListener('keydown', onAnyInput)
|
||||
globalThis?.window?.document?.removeEventListener(
|
||||
'mousemove',
|
||||
onAnyInput
|
||||
)
|
||||
globalThis?.window?.document?.removeEventListener(
|
||||
'mousedown',
|
||||
onAnyInput
|
||||
)
|
||||
globalThis?.window?.document?.removeEventListener('scroll', onAnyInput)
|
||||
globalThis?.window?.document?.removeEventListener(
|
||||
'touchstart',
|
||||
onAnyInput
|
||||
)
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
@ -124,10 +140,10 @@ export const Stream = () => {
|
||||
)
|
||||
return
|
||||
if (!videoRef.current) return
|
||||
if (!context.store?.mediaStream) return
|
||||
if (!mediaStream) return
|
||||
|
||||
// Do not immediately play the stream!
|
||||
videoRef.current.srcObject = context.store.mediaStream
|
||||
videoRef.current.srcObject = mediaStream
|
||||
videoRef.current.pause()
|
||||
|
||||
send({
|
||||
@ -136,7 +152,7 @@ export const Stream = () => {
|
||||
videoElement: videoRef.current,
|
||||
},
|
||||
})
|
||||
}, [context.store?.mediaStream])
|
||||
}, [mediaStream])
|
||||
|
||||
const handleMouseDown: MouseEventHandler<HTMLDivElement> = (e) => {
|
||||
if (!isNetworkOkay) return
|
||||
@ -172,7 +188,7 @@ export const Stream = () => {
|
||||
if (state.matches('Sketch')) return
|
||||
if (state.matches('Sketch no face')) return
|
||||
|
||||
if (!context.store?.didDragInStream && butName(e).left) {
|
||||
if (!context.store?.didDragInStream && btnName(e).left) {
|
||||
sendSelectEventToEngine(
|
||||
e,
|
||||
videoRef.current,
|
||||
|
@ -195,14 +195,15 @@ export class CompletionRequester implements PluginValue {
|
||||
|
||||
private queuedUids: string[] = []
|
||||
|
||||
private _deffererCodeUpdate = deferExecution(() => {
|
||||
this.requestCompletions()
|
||||
}, changesDelay)
|
||||
|
||||
private _deffererUserSelect = deferExecution(() => {
|
||||
this.rejectSuggestionCommand()
|
||||
}, changesDelay)
|
||||
|
||||
// When a doc update needs to be sent to the server, this holds the
|
||||
// timeout handle for it. When null, the server has the up-to-date
|
||||
// document.
|
||||
private sendScheduledInput: number | null = null
|
||||
|
||||
constructor(readonly view: EditorView, client: LanguageServerClient) {
|
||||
this.client = client
|
||||
}
|
||||
@ -245,7 +246,34 @@ export class CompletionRequester implements PluginValue {
|
||||
}
|
||||
|
||||
this.lastPos = this.view.state.selection.main.head
|
||||
if (viewUpdate.docChanged) this._deffererCodeUpdate(true)
|
||||
if (viewUpdate.docChanged) this.scheduleUpdateDoc()
|
||||
}
|
||||
|
||||
scheduleUpdateDoc() {
|
||||
if (this.sendScheduledInput != null)
|
||||
window.clearTimeout(this.sendScheduledInput)
|
||||
this.sendScheduledInput = window.setTimeout(
|
||||
() => this.updateDoc(),
|
||||
changesDelay
|
||||
)
|
||||
}
|
||||
|
||||
updateDoc() {
|
||||
if (this.sendScheduledInput != null) {
|
||||
window.clearTimeout(this.sendScheduledInput)
|
||||
this.sendScheduledInput = null
|
||||
}
|
||||
|
||||
if (!this.client.ready) return
|
||||
try {
|
||||
this.requestCompletions()
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
ensureDocUpdated() {
|
||||
if (this.sendScheduledInput != null) this.updateDoc()
|
||||
}
|
||||
|
||||
ghostText(): GhostText | null {
|
||||
|
@ -27,13 +27,10 @@ export class KclPlugin implements PluginValue {
|
||||
this.client = client
|
||||
}
|
||||
|
||||
private _deffererCodeUpdate = deferExecution(() => {
|
||||
if (this.viewUpdate === null) {
|
||||
return
|
||||
}
|
||||
|
||||
kclManager.executeCode()
|
||||
}, changesDelay)
|
||||
// When a doc update needs to be sent to the server, this holds the
|
||||
// timeout handle for it. When null, the server has the up-to-date
|
||||
// document.
|
||||
private sendScheduledInput: number | null = null
|
||||
|
||||
private _deffererUserSelect = deferExecution(() => {
|
||||
if (this.viewUpdate === null) {
|
||||
@ -101,7 +98,34 @@ export class KclPlugin implements PluginValue {
|
||||
codeManager.code = newCode
|
||||
codeManager.writeToFile()
|
||||
|
||||
this._deffererCodeUpdate(true)
|
||||
this.scheduleUpdateDoc()
|
||||
}
|
||||
|
||||
scheduleUpdateDoc() {
|
||||
if (this.sendScheduledInput != null)
|
||||
window.clearTimeout(this.sendScheduledInput)
|
||||
this.sendScheduledInput = window.setTimeout(
|
||||
() => this.updateDoc(),
|
||||
changesDelay
|
||||
)
|
||||
}
|
||||
|
||||
updateDoc() {
|
||||
if (this.sendScheduledInput != null) {
|
||||
window.clearTimeout(this.sendScheduledInput)
|
||||
this.sendScheduledInput = null
|
||||
}
|
||||
|
||||
if (!this.client.ready) return
|
||||
try {
|
||||
kclManager.executeCode()
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
ensureDocUpdated() {
|
||||
if (this.sendScheduledInput != null) this.updateDoc()
|
||||
}
|
||||
|
||||
async updateUnits(
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { useLayoutEffect, useEffect, useRef } from 'react'
|
||||
import { useLayoutEffect, useEffect, useRef, useState } from 'react'
|
||||
import { engineCommandManager, kclManager } from 'lib/singletons'
|
||||
import { deferExecution } from 'lib/utils'
|
||||
import { Themes } from 'lib/theme'
|
||||
import { makeDefaultPlanes, modifyGrid } from 'lang/wasm'
|
||||
import { useModelingContext } from './useModelingContext'
|
||||
import { useAppState } from 'AppState'
|
||||
import { useAppState, useAppStream } from 'AppState'
|
||||
|
||||
export function useSetupEngineManager(
|
||||
streamRef: React.RefObject<HTMLDivElement>,
|
||||
@ -28,9 +28,7 @@ export function useSetupEngineManager(
|
||||
}
|
||||
) {
|
||||
const { setAppState } = useAppState()
|
||||
|
||||
const streamWidth = streamRef?.current?.offsetWidth
|
||||
const streamHeight = streamRef?.current?.offsetHeight
|
||||
const { setMediaStream } = useAppStream()
|
||||
|
||||
const hasSetNonZeroDimensions = useRef<boolean>(false)
|
||||
|
||||
@ -40,59 +38,60 @@ export function useSetupEngineManager(
|
||||
engineCommandManager.pool = settings.pool
|
||||
}
|
||||
|
||||
const startEngineInstance = () => {
|
||||
const startEngineInstance = (restart: boolean = false) => {
|
||||
// Load the engine command manager once with the initial width and height,
|
||||
// then we do not want to reload it.
|
||||
const { width: quadWidth, height: quadHeight } = getDimensions(
|
||||
streamWidth,
|
||||
streamHeight
|
||||
streamRef?.current?.offsetWidth ?? 0,
|
||||
streamRef?.current?.offsetHeight ?? 0
|
||||
)
|
||||
if (
|
||||
!hasSetNonZeroDimensions.current &&
|
||||
quadHeight &&
|
||||
quadWidth &&
|
||||
settings.modelingSend
|
||||
) {
|
||||
engineCommandManager.start({
|
||||
setMediaStream: (mediaStream) =>
|
||||
settings.modelingSend({
|
||||
type: 'Set context',
|
||||
data: { mediaStream },
|
||||
}),
|
||||
setIsStreamReady: (isStreamReady) => setAppState({ isStreamReady }),
|
||||
width: quadWidth,
|
||||
height: quadHeight,
|
||||
executeCode: () => {
|
||||
// We only want to execute the code here that we already have set.
|
||||
// Nothing else.
|
||||
kclManager.isFirstRender = true
|
||||
return kclManager.executeCode(true, true).then(() => {
|
||||
kclManager.isFirstRender = false
|
||||
})
|
||||
},
|
||||
token,
|
||||
settings,
|
||||
makeDefaultPlanes: () => {
|
||||
return makeDefaultPlanes(kclManager.engineCommandManager)
|
||||
},
|
||||
modifyGrid: (hidden: boolean) => {
|
||||
return modifyGrid(kclManager.engineCommandManager, hidden)
|
||||
},
|
||||
})
|
||||
settings.modelingSend({
|
||||
type: 'Set context',
|
||||
data: {
|
||||
streamDimensions: {
|
||||
streamWidth: quadWidth,
|
||||
streamHeight: quadHeight,
|
||||
},
|
||||
},
|
||||
})
|
||||
hasSetNonZeroDimensions.current = true
|
||||
if (restart) {
|
||||
kclManager.isFirstRender = false
|
||||
}
|
||||
engineCommandManager.start({
|
||||
restart,
|
||||
setMediaStream: (mediaStream) => setMediaStream(mediaStream),
|
||||
setIsStreamReady: (isStreamReady) => setAppState({ isStreamReady }),
|
||||
width: quadWidth,
|
||||
height: quadHeight,
|
||||
executeCode: () => {
|
||||
// We only want to execute the code here that we already have set.
|
||||
// Nothing else.
|
||||
kclManager.isFirstRender = true
|
||||
return kclManager.executeCode(true, true).then(() => {
|
||||
kclManager.isFirstRender = false
|
||||
})
|
||||
},
|
||||
token,
|
||||
settings,
|
||||
makeDefaultPlanes: () => {
|
||||
return makeDefaultPlanes(kclManager.engineCommandManager)
|
||||
},
|
||||
modifyGrid: (hidden: boolean) => {
|
||||
return modifyGrid(kclManager.engineCommandManager, hidden)
|
||||
},
|
||||
})
|
||||
settings.modelingSend({
|
||||
type: 'Set context',
|
||||
data: {
|
||||
streamDimensions: {
|
||||
streamWidth: quadWidth,
|
||||
streamHeight: quadHeight,
|
||||
},
|
||||
},
|
||||
})
|
||||
hasSetNonZeroDimensions.current = true
|
||||
}
|
||||
|
||||
useLayoutEffect(startEngineInstance, [
|
||||
useLayoutEffect(() => {
|
||||
const { width: quadWidth, height: quadHeight } = getDimensions(
|
||||
streamRef?.current?.offsetWidth ?? 0,
|
||||
streamRef?.current?.offsetHeight ?? 0
|
||||
)
|
||||
if (!hasSetNonZeroDimensions.current && quadHeight && quadWidth) {
|
||||
startEngineInstance()
|
||||
}
|
||||
}, [
|
||||
streamRef?.current?.offsetWidth,
|
||||
streamRef?.current?.offsetHeight,
|
||||
settings.modelingSend,
|
||||
@ -101,8 +100,8 @@ export function useSetupEngineManager(
|
||||
useEffect(() => {
|
||||
const handleResize = deferExecution(() => {
|
||||
const { width, height } = getDimensions(
|
||||
streamRef?.current?.offsetWidth,
|
||||
streamRef?.current?.offsetHeight
|
||||
streamRef?.current?.offsetWidth ?? 0,
|
||||
streamRef?.current?.offsetHeight ?? 0
|
||||
)
|
||||
if (
|
||||
settings.modelingContext.store.streamDimensions.streamWidth !== width ||
|
||||
@ -125,10 +124,37 @@ export function useSetupEngineManager(
|
||||
}, 500)
|
||||
|
||||
const onOnline = () => {
|
||||
startEngineInstance()
|
||||
startEngineInstance(true)
|
||||
}
|
||||
|
||||
const onVisibilityChange = () => {
|
||||
if (window.document.visibilityState === 'visible') {
|
||||
if (
|
||||
!engineCommandManager.engineConnection?.isReady() &&
|
||||
!engineCommandManager.engineConnection?.isConnecting()
|
||||
) {
|
||||
startEngineInstance()
|
||||
}
|
||||
}
|
||||
}
|
||||
window.document.addEventListener('visibilitychange', onVisibilityChange)
|
||||
|
||||
const onAnyInput = () => {
|
||||
if (
|
||||
!engineCommandManager.engineConnection?.isReady() &&
|
||||
!engineCommandManager.engineConnection?.isConnecting()
|
||||
) {
|
||||
startEngineInstance()
|
||||
}
|
||||
}
|
||||
window.document.addEventListener('keydown', onAnyInput)
|
||||
window.document.addEventListener('mousemove', onAnyInput)
|
||||
window.document.addEventListener('mousedown', onAnyInput)
|
||||
window.document.addEventListener('scroll', onAnyInput)
|
||||
window.document.addEventListener('touchstart', onAnyInput)
|
||||
|
||||
const onOffline = () => {
|
||||
kclManager.isFirstRender = true
|
||||
engineCommandManager.tearDown()
|
||||
}
|
||||
|
||||
@ -136,11 +162,30 @@ export function useSetupEngineManager(
|
||||
window.addEventListener('offline', onOffline)
|
||||
window.addEventListener('resize', handleResize)
|
||||
return () => {
|
||||
window.document.removeEventListener(
|
||||
'visibilitychange',
|
||||
onVisibilityChange
|
||||
)
|
||||
window.document.removeEventListener('keydown', onAnyInput)
|
||||
window.document.removeEventListener('mousemove', onAnyInput)
|
||||
window.document.removeEventListener('mousedown', onAnyInput)
|
||||
window.document.removeEventListener('scroll', onAnyInput)
|
||||
window.document.removeEventListener('touchstart', onAnyInput)
|
||||
window.removeEventListener('online', onOnline)
|
||||
window.removeEventListener('offline', onOffline)
|
||||
window.removeEventListener('resize', handleResize)
|
||||
}
|
||||
}, [])
|
||||
|
||||
// Engine relies on many settings so we should rebind events when it changes
|
||||
// We have to list out the ones we care about because the settings object holds
|
||||
// non-settings too...
|
||||
}, [
|
||||
settings.enableSSAO,
|
||||
settings.highlightEdges,
|
||||
settings.showScaleGrid,
|
||||
settings.theme,
|
||||
settings.pool,
|
||||
])
|
||||
}
|
||||
|
||||
function getDimensions(streamWidth?: number, streamHeight?: number) {
|
||||
|
@ -6,7 +6,11 @@ import { modelingMachine } from 'machines/modelingMachine'
|
||||
import { authMachine } from 'machines/authMachine'
|
||||
import { settingsMachine } from 'machines/settingsMachine'
|
||||
import { homeMachine } from 'machines/homeMachine'
|
||||
import { Command, CommandSetConfig, CommandSetSchema } from 'lib/commandTypes'
|
||||
import {
|
||||
Command,
|
||||
StateMachineCommandSetConfig,
|
||||
StateMachineCommandSetSchema,
|
||||
} from 'lib/commandTypes'
|
||||
import { useKclContext } from 'lang/KclProvider'
|
||||
import { useNetworkContext } from 'hooks/useNetworkContext'
|
||||
import { NetworkHealthState } from 'hooks/useNetworkStatus'
|
||||
@ -21,20 +25,20 @@ export type AllMachines =
|
||||
|
||||
interface UseStateMachineCommandsArgs<
|
||||
T extends AllMachines,
|
||||
S extends CommandSetSchema<T>
|
||||
S extends StateMachineCommandSetSchema<T>
|
||||
> {
|
||||
machineId: T['id']
|
||||
state: StateFrom<T>
|
||||
send: Function
|
||||
actor: InterpreterFrom<T>
|
||||
commandBarConfig?: CommandSetConfig<T, S>
|
||||
commandBarConfig?: StateMachineCommandSetConfig<T, S>
|
||||
allCommandsRequireNetwork?: boolean
|
||||
onCancel?: () => void
|
||||
}
|
||||
|
||||
export default function useStateMachineCommands<
|
||||
T extends AnyStateMachine,
|
||||
S extends CommandSetSchema<T>
|
||||
S extends StateMachineCommandSetSchema<T>
|
||||
>({
|
||||
machineId,
|
||||
state,
|
||||
@ -58,9 +62,10 @@ export default function useStateMachineCommands<
|
||||
const newCommands = state.nextEvents
|
||||
.filter((_) => !allCommandsRequireNetwork || !disableAllButtons)
|
||||
.filter((e) => !['done.', 'error.'].some((n) => e.includes(n)))
|
||||
.map((type) =>
|
||||
.flatMap((type) =>
|
||||
createMachineCommand<T, S>({
|
||||
ownerMachine: machineId,
|
||||
// The group is the owner machine's ID.
|
||||
groupId: machineId,
|
||||
type,
|
||||
state,
|
||||
send,
|
||||
|
@ -13,6 +13,7 @@ import {
|
||||
UpdaterRestartModal,
|
||||
createUpdaterRestartModal,
|
||||
} from 'components/UpdaterRestartModal'
|
||||
import { AppStreamProvider } from 'AppState'
|
||||
|
||||
// uncomment for xstate inspector
|
||||
// import { DEV } from 'env'
|
||||
@ -26,28 +27,30 @@ const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement)
|
||||
|
||||
root.render(
|
||||
<HotkeysProvider>
|
||||
<Router />
|
||||
<Toaster
|
||||
position="bottom-center"
|
||||
toastOptions={{
|
||||
style: {
|
||||
borderRadius: '3px',
|
||||
},
|
||||
className:
|
||||
'bg-chalkboard-10 dark:bg-chalkboard-90 text-chalkboard-110 dark:text-chalkboard-10 rounded-sm border-chalkboard-20/50 dark:border-chalkboard-80/50',
|
||||
success: {
|
||||
iconTheme: {
|
||||
primary: 'oklch(89% 0.16 143.4deg)',
|
||||
secondary: 'oklch(48.62% 0.1654 142.5deg)',
|
||||
<AppStreamProvider>
|
||||
<Router />
|
||||
<Toaster
|
||||
position="bottom-center"
|
||||
toastOptions={{
|
||||
style: {
|
||||
borderRadius: '3px',
|
||||
},
|
||||
duration:
|
||||
window?.localStorage.getItem('playwright') === 'true'
|
||||
? 10 // speed up e2e tests
|
||||
: 1500,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<ModalContainer />
|
||||
className:
|
||||
'bg-chalkboard-10 dark:bg-chalkboard-90 text-chalkboard-110 dark:text-chalkboard-10 rounded-sm border-chalkboard-20/50 dark:border-chalkboard-80/50',
|
||||
success: {
|
||||
iconTheme: {
|
||||
primary: 'oklch(89% 0.16 143.4deg)',
|
||||
secondary: 'oklch(48.62% 0.1654 142.5deg)',
|
||||
},
|
||||
duration:
|
||||
window?.localStorage.getItem('playwright') === 'true'
|
||||
? 10 // speed up e2e tests
|
||||
: 1500,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<ModalContainer />
|
||||
</AppStreamProvider>
|
||||
</HotkeysProvider>
|
||||
)
|
||||
|
||||
|
@ -3,6 +3,8 @@ import { createContext, useContext, useEffect, useState } from 'react'
|
||||
import { type IndexLoaderData } from 'lib/types'
|
||||
import { useLoaderData } from 'react-router-dom'
|
||||
import { codeManager, kclManager } from 'lib/singletons'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import { Command } from 'lib/commandTypes'
|
||||
|
||||
const KclContext = createContext({
|
||||
code: codeManager?.code || '',
|
||||
@ -35,6 +37,7 @@ export function KclContextProvider({
|
||||
const [errors, setErrors] = useState<KCLError[]>([])
|
||||
const [logs, setLogs] = useState<string[]>([])
|
||||
const [wasmInitFailed, setWasmInitFailed] = useState(false)
|
||||
const { commandBarSend } = useCommandsContext()
|
||||
|
||||
useEffect(() => {
|
||||
codeManager.registerCallBacks({
|
||||
@ -50,6 +53,28 @@ export function KclContextProvider({
|
||||
})
|
||||
}, [])
|
||||
|
||||
// Add format code to command palette.
|
||||
useEffect(() => {
|
||||
const commands: Command[] = [
|
||||
{
|
||||
name: 'format-code',
|
||||
displayName: 'Format Code',
|
||||
description: 'Nicely formats the KCL code in the editor.',
|
||||
needsReview: false,
|
||||
groupId: 'code',
|
||||
icon: 'code',
|
||||
onSubmit: (data) => {
|
||||
kclManager.format()
|
||||
},
|
||||
},
|
||||
]
|
||||
commandBarSend({ type: 'Add commands', data: { commands } })
|
||||
|
||||
return () => {
|
||||
commandBarSend({ type: 'Remove commands', data: { commands } })
|
||||
}
|
||||
}, [kclManager, commandBarSend])
|
||||
|
||||
return (
|
||||
<KclContext.Provider
|
||||
value={{
|
||||
|
@ -302,6 +302,30 @@ class EngineConnection extends EventTarget {
|
||||
mediaStream?: MediaStream
|
||||
freezeFrame: boolean = false
|
||||
|
||||
onIceCandidate = function (
|
||||
this: RTCPeerConnection,
|
||||
event: RTCPeerConnectionIceEvent
|
||||
) {}
|
||||
onIceCandidateError = function (
|
||||
this: RTCPeerConnection,
|
||||
event: RTCPeerConnectionIceErrorEvent
|
||||
) {}
|
||||
onConnectionStateChange = function (this: RTCPeerConnection, event: Event) {}
|
||||
onDataChannelOpen = function (this: RTCDataChannel, event: Event) {}
|
||||
onDataChannelClose = function (this: RTCDataChannel, event: Event) {}
|
||||
onDataChannelError = function (this: RTCDataChannel, event: Event) {}
|
||||
onDataChannelMessage = function (this: RTCDataChannel, event: MessageEvent) {}
|
||||
onDataChannel = function (
|
||||
this: RTCPeerConnection,
|
||||
event: RTCDataChannelEvent
|
||||
) {}
|
||||
onTrack = function (this: RTCPeerConnection, event: RTCTrackEvent) {}
|
||||
onWebSocketOpen = function (event: Event) {}
|
||||
onWebSocketClose = function (event: Event) {}
|
||||
onWebSocketError = function (event: Event) {}
|
||||
onWebSocketMessage = function (event: MessageEvent) {}
|
||||
onNetworkStatusReady = () => {}
|
||||
|
||||
private _state: EngineConnectionState = {
|
||||
type: EngineConnectionStateType.Fresh,
|
||||
}
|
||||
@ -346,6 +370,7 @@ class EngineConnection extends EventTarget {
|
||||
private engineCommandManager: EngineCommandManager
|
||||
|
||||
private pingPongSpan: { ping?: Date; pong?: Date }
|
||||
private pingIntervalId: ReturnType<typeof setInterval>
|
||||
|
||||
constructor({
|
||||
engineCommandManager,
|
||||
@ -368,7 +393,7 @@ class EngineConnection extends EventTarget {
|
||||
// Without an interval ping, our connection will timeout.
|
||||
// If this.freezeFrame is true we skip this logic so only reconnect
|
||||
// happens on mouse move
|
||||
setInterval(() => {
|
||||
this.pingIntervalId = setInterval(() => {
|
||||
if (this.freezeFrame) return
|
||||
|
||||
switch (this.state.type as EngineConnectionStateType) {
|
||||
@ -434,6 +459,44 @@ class EngineConnection extends EventTarget {
|
||||
tearDown(opts?: { freeze: boolean }) {
|
||||
this.freezeFrame = opts?.freeze ?? false
|
||||
this.disconnectAll()
|
||||
clearInterval(this.pingIntervalId)
|
||||
|
||||
this.pc?.removeEventListener('icecandidate', this.onIceCandidate)
|
||||
this.pc?.removeEventListener('icecandidateerror', this.onIceCandidateError)
|
||||
this.pc?.removeEventListener(
|
||||
'connectionstatechange',
|
||||
this.onConnectionStateChange
|
||||
)
|
||||
this.pc?.removeEventListener('track', this.onTrack)
|
||||
|
||||
this.unreliableDataChannel?.removeEventListener(
|
||||
'open',
|
||||
this.onDataChannelOpen
|
||||
)
|
||||
this.unreliableDataChannel?.removeEventListener(
|
||||
'close',
|
||||
this.onDataChannelClose
|
||||
)
|
||||
this.unreliableDataChannel?.removeEventListener(
|
||||
'error',
|
||||
this.onDataChannelError
|
||||
)
|
||||
this.unreliableDataChannel?.removeEventListener(
|
||||
'message',
|
||||
this.onDataChannelMessage
|
||||
)
|
||||
this.pc?.removeEventListener('datachannel', this.onDataChannel)
|
||||
|
||||
this.websocket?.removeEventListener('open', this.onWebSocketOpen)
|
||||
this.websocket?.removeEventListener('close', this.onWebSocketClose)
|
||||
this.websocket?.removeEventListener('error', this.onWebSocketError)
|
||||
this.websocket?.removeEventListener('message', this.onWebSocketMessage)
|
||||
|
||||
window.removeEventListener(
|
||||
'use-network-status-ready',
|
||||
this.onNetworkStatusReady
|
||||
)
|
||||
|
||||
this.state = {
|
||||
type: EngineConnectionStateType.Disconnecting,
|
||||
value: { type: DisconnectingType.Quit },
|
||||
@ -477,7 +540,7 @@ class EngineConnection extends EventTarget {
|
||||
},
|
||||
}
|
||||
|
||||
this.pc.addEventListener('icecandidate', (event) => {
|
||||
this.onIceCandidate = (event: RTCPeerConnectionIceEvent) => {
|
||||
if (event.candidate === null) {
|
||||
return
|
||||
}
|
||||
@ -499,18 +562,20 @@ class EngineConnection extends EventTarget {
|
||||
usernameFragment: event.candidate.usernameFragment || undefined,
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
this.pc.addEventListener('icecandidate', this.onIceCandidate)
|
||||
|
||||
this.pc.addEventListener('icecandidateerror', (_event: Event) => {
|
||||
this.onIceCandidateError = (_event: Event) => {
|
||||
const event = _event as RTCPeerConnectionIceErrorEvent
|
||||
console.warn(
|
||||
`ICE candidate returned an error: ${event.errorCode}: ${event.errorText} for ${event.url}`
|
||||
)
|
||||
})
|
||||
}
|
||||
this.pc.addEventListener('icecandidateerror', this.onIceCandidateError)
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/connectionstatechange_event
|
||||
// Event type: generic Event type...
|
||||
this.pc.addEventListener('connectionstatechange', (event: any) => {
|
||||
this.onConnectionStateChange = (event: any) => {
|
||||
console.log('connectionstatechange: ' + event.target?.connectionState)
|
||||
switch (event.target?.connectionState) {
|
||||
// From what I understand, only after have we done the ICE song and
|
||||
@ -539,9 +604,13 @@ class EngineConnection extends EventTarget {
|
||||
default:
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
this.pc.addEventListener(
|
||||
'connectionstatechange',
|
||||
this.onConnectionStateChange
|
||||
)
|
||||
|
||||
this.pc.addEventListener('track', (event) => {
|
||||
this.onTrack = (event) => {
|
||||
const mediaStream = event.streams[0]
|
||||
|
||||
this.state = {
|
||||
@ -625,9 +694,10 @@ class EngineConnection extends EventTarget {
|
||||
// to pass it to the rest of the application.
|
||||
|
||||
this.mediaStream = mediaStream
|
||||
})
|
||||
}
|
||||
this.pc.addEventListener('track', this.onTrack)
|
||||
|
||||
this.pc.addEventListener('datachannel', (event) => {
|
||||
this.onDataChannel = (event) => {
|
||||
this.unreliableDataChannel = event.channel
|
||||
|
||||
this.state = {
|
||||
@ -638,7 +708,7 @@ class EngineConnection extends EventTarget {
|
||||
},
|
||||
}
|
||||
|
||||
this.unreliableDataChannel.addEventListener('open', (event) => {
|
||||
this.onDataChannelOpen = (event) => {
|
||||
this.state = {
|
||||
type: EngineConnectionStateType.Connecting,
|
||||
value: {
|
||||
@ -654,14 +724,22 @@ class EngineConnection extends EventTarget {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent(EngineConnectionEvents.Opened, { detail: this })
|
||||
)
|
||||
})
|
||||
}
|
||||
this.unreliableDataChannel?.addEventListener(
|
||||
'open',
|
||||
this.onDataChannelOpen
|
||||
)
|
||||
|
||||
this.unreliableDataChannel.addEventListener('close', (event) => {
|
||||
this.onDataChannelClose = (event) => {
|
||||
this.disconnectAll()
|
||||
this.finalizeIfAllConnectionsClosed()
|
||||
})
|
||||
}
|
||||
this.unreliableDataChannel?.addEventListener(
|
||||
'close',
|
||||
this.onDataChannelClose
|
||||
)
|
||||
|
||||
this.unreliableDataChannel.addEventListener('error', (event) => {
|
||||
this.onDataChannelError = (event) => {
|
||||
this.disconnectAll()
|
||||
|
||||
this.state = {
|
||||
@ -674,8 +752,13 @@ class EngineConnection extends EventTarget {
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
this.unreliableDataChannel.addEventListener('message', (event) => {
|
||||
}
|
||||
this.unreliableDataChannel?.addEventListener(
|
||||
'error',
|
||||
this.onDataChannelError
|
||||
)
|
||||
|
||||
this.onDataChannelMessage = (event) => {
|
||||
const result: UnreliableResponses = JSON.parse(event.data)
|
||||
Object.values(
|
||||
this.engineCommandManager.unreliableSubscriptions[result.type] || {}
|
||||
@ -697,8 +780,13 @@ class EngineConnection extends EventTarget {
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
this.unreliableDataChannel.addEventListener(
|
||||
'message',
|
||||
this.onDataChannelMessage
|
||||
)
|
||||
}
|
||||
this.pc.addEventListener('datachannel', this.onDataChannel)
|
||||
}
|
||||
|
||||
const createWebSocketConnection = () => {
|
||||
@ -712,7 +800,7 @@ class EngineConnection extends EventTarget {
|
||||
this.websocket = new WebSocket(this.url, [])
|
||||
this.websocket.binaryType = 'arraybuffer'
|
||||
|
||||
this.websocket.addEventListener('open', (event) => {
|
||||
this.onWebSocketOpen = (event) => {
|
||||
this.state = {
|
||||
type: EngineConnectionStateType.Connecting,
|
||||
value: {
|
||||
@ -733,14 +821,16 @@ class EngineConnection extends EventTarget {
|
||||
// Send an initial ping
|
||||
this.send({ type: 'ping' })
|
||||
this.pingPongSpan.ping = new Date()
|
||||
})
|
||||
}
|
||||
this.websocket.addEventListener('open', this.onWebSocketOpen)
|
||||
|
||||
this.websocket.addEventListener('close', (event) => {
|
||||
this.onWebSocketClose = (event) => {
|
||||
this.disconnectAll()
|
||||
this.finalizeIfAllConnectionsClosed()
|
||||
})
|
||||
}
|
||||
this.websocket.addEventListener('close', this.onWebSocketClose)
|
||||
|
||||
this.websocket.addEventListener('error', (event) => {
|
||||
this.onWebSocketError = (event) => {
|
||||
this.disconnectAll()
|
||||
|
||||
this.state = {
|
||||
@ -753,9 +843,10 @@ class EngineConnection extends EventTarget {
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
this.websocket.addEventListener('error', this.onWebSocketError)
|
||||
|
||||
this.websocket.addEventListener('message', (event) => {
|
||||
this.onWebSocketMessage = (event) => {
|
||||
// In the EngineConnection, we're looking for messages to/from
|
||||
// the server that relate to the ICE handshake, or WebRTC
|
||||
// negotiation. There may be other messages (including ArrayBuffer
|
||||
@ -960,15 +1051,20 @@ class EngineConnection extends EventTarget {
|
||||
})
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
this.websocket.addEventListener('message', this.onWebSocketMessage)
|
||||
}
|
||||
|
||||
if (reconnecting) {
|
||||
createWebSocketConnection()
|
||||
} else {
|
||||
window.addEventListener('use-network-status-ready', () => {
|
||||
this.onNetworkStatusReady = () => {
|
||||
createWebSocketConnection()
|
||||
})
|
||||
}
|
||||
window.addEventListener(
|
||||
'use-network-status-ready',
|
||||
this.onNetworkStatusReady
|
||||
)
|
||||
}
|
||||
}
|
||||
// Do not change this back to an object or any, we should only be sending the
|
||||
@ -1154,7 +1250,15 @@ export class EngineCommandManager extends EventTarget {
|
||||
private makeDefaultPlanes: () => Promise<DefaultPlanes> | null = () => null
|
||||
private modifyGrid: (hidden: boolean) => Promise<void> | null = () => null
|
||||
|
||||
private onEngineConnectionOpened = () => {}
|
||||
private onEngineConnectionClosed = () => {}
|
||||
private onEngineConnectionStarted = ({ detail: engineConnection }: any) => {}
|
||||
private onEngineConnectionNewTrack = ({
|
||||
detail,
|
||||
}: CustomEvent<NewTrackArgs>) => {}
|
||||
|
||||
start({
|
||||
restart,
|
||||
setMediaStream,
|
||||
setIsStreamReady,
|
||||
width,
|
||||
@ -1170,6 +1274,7 @@ export class EngineCommandManager extends EventTarget {
|
||||
showScaleGrid: false,
|
||||
},
|
||||
}: {
|
||||
restart?: boolean
|
||||
setMediaStream: (stream: MediaStream) => void
|
||||
setIsStreamReady: (isStreamReady: boolean) => void
|
||||
width: number
|
||||
@ -1215,162 +1320,168 @@ export class EngineCommandManager extends EventTarget {
|
||||
})
|
||||
)
|
||||
|
||||
this.onEngineConnectionOpened = () => {
|
||||
// Set the stream background color
|
||||
// This takes RGBA values from 0-1
|
||||
// So we convert from the conventional 0-255 found in Figma
|
||||
|
||||
void this.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'set_background_color',
|
||||
color: getThemeColorForEngine(settings.theme),
|
||||
},
|
||||
})
|
||||
|
||||
// Sets the default line colors
|
||||
const opposingTheme = getOppositeTheme(settings.theme)
|
||||
this.sendSceneCommand({
|
||||
cmd_id: uuidv4(),
|
||||
type: 'modeling_cmd_req',
|
||||
cmd: {
|
||||
type: 'set_default_system_properties',
|
||||
color: getThemeColorForEngine(opposingTheme),
|
||||
},
|
||||
})
|
||||
|
||||
// Set the edge lines visibility
|
||||
this.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'edge_lines_visible' as any, // TODO: update kittycad.ts to use the correct type
|
||||
hidden: !settings.highlightEdges,
|
||||
},
|
||||
})
|
||||
|
||||
this._camControlsCameraChange()
|
||||
this.sendSceneCommand({
|
||||
// CameraControls subscribes to default_camera_get_settings response events
|
||||
// firing this at connection ensure the camera's are synced initially
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_get_settings',
|
||||
},
|
||||
})
|
||||
// We want modify the grid first because we don't want it to flash.
|
||||
// Ideally these would already be default hidden in engine (TODO do
|
||||
// that) https://github.com/KittyCAD/engine/issues/2282
|
||||
this.modifyGrid(!settings.showScaleGrid)?.then(async () => {
|
||||
await this.initPlanes()
|
||||
this.resolveReady()
|
||||
setIsStreamReady(true)
|
||||
await executeCode()
|
||||
})
|
||||
}
|
||||
this.engineConnection.addEventListener(
|
||||
EngineConnectionEvents.Opened,
|
||||
() => {
|
||||
// Set the stream background color
|
||||
// This takes RGBA values from 0-1
|
||||
// So we convert from the conventional 0-255 found in Figma
|
||||
|
||||
void this.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'set_background_color',
|
||||
color: getThemeColorForEngine(settings.theme),
|
||||
},
|
||||
})
|
||||
|
||||
// Sets the default line colors
|
||||
const opposingTheme = getOppositeTheme(settings.theme)
|
||||
this.sendSceneCommand({
|
||||
cmd_id: uuidv4(),
|
||||
type: 'modeling_cmd_req',
|
||||
cmd: {
|
||||
type: 'set_default_system_properties',
|
||||
color: getThemeColorForEngine(opposingTheme),
|
||||
},
|
||||
})
|
||||
|
||||
// Set the edge lines visibility
|
||||
this.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'edge_lines_visible' as any, // TODO: update kittycad.ts to use the correct type
|
||||
hidden: !settings.highlightEdges,
|
||||
},
|
||||
})
|
||||
|
||||
this._camControlsCameraChange()
|
||||
this.sendSceneCommand({
|
||||
// CameraControls subscribes to default_camera_get_settings response events
|
||||
// firing this at connection ensure the camera's are synced initially
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_get_settings',
|
||||
},
|
||||
})
|
||||
// We want modify the grid first because we don't want it to flash.
|
||||
// Ideally these would already be default hidden in engine (TODO do
|
||||
// that) https://github.com/KittyCAD/engine/issues/2282
|
||||
this.modifyGrid(!settings.showScaleGrid)?.then(async () => {
|
||||
await this.initPlanes()
|
||||
this.resolveReady()
|
||||
setIsStreamReady(true)
|
||||
await executeCode()
|
||||
})
|
||||
}
|
||||
this.onEngineConnectionOpened
|
||||
)
|
||||
|
||||
this.onEngineConnectionClosed = () => {
|
||||
setIsStreamReady(false)
|
||||
}
|
||||
this.engineConnection.addEventListener(
|
||||
EngineConnectionEvents.Closed,
|
||||
() => {
|
||||
setIsStreamReady(false)
|
||||
}
|
||||
this.onEngineConnectionClosed
|
||||
)
|
||||
|
||||
this.engineConnection.addEventListener(
|
||||
EngineConnectionEvents.ConnectionStarted,
|
||||
({ detail: engineConnection }: any) => {
|
||||
engineConnection?.pc?.addEventListener(
|
||||
'datachannel',
|
||||
(event: RTCDataChannelEvent) => {
|
||||
let unreliableDataChannel = event.channel
|
||||
this.onEngineConnectionStarted = ({ detail: engineConnection }: any) => {
|
||||
engineConnection?.pc?.addEventListener(
|
||||
'datachannel',
|
||||
(event: RTCDataChannelEvent) => {
|
||||
let unreliableDataChannel = event.channel
|
||||
|
||||
unreliableDataChannel.addEventListener(
|
||||
'message',
|
||||
(event: MessageEvent) => {
|
||||
const result: UnreliableResponses = JSON.parse(event.data)
|
||||
Object.values(
|
||||
this.unreliableSubscriptions[result.type] || {}
|
||||
).forEach(
|
||||
// TODO: There is only one response that uses the unreliable channel atm,
|
||||
// highlight_set_entity, if there are more it's likely they will all have the same
|
||||
// sequence logic, but I'm not sure if we use a single global sequence or a sequence
|
||||
// per unreliable subscription.
|
||||
(callback) => {
|
||||
let data = result?.data
|
||||
if (isHighlightSetEntity_type(data)) {
|
||||
if (
|
||||
data.sequence !== undefined &&
|
||||
data.sequence > this.inSequence
|
||||
) {
|
||||
this.inSequence = data.sequence
|
||||
callback(result)
|
||||
}
|
||||
unreliableDataChannel.addEventListener(
|
||||
'message',
|
||||
(event: MessageEvent) => {
|
||||
const result: UnreliableResponses = JSON.parse(event.data)
|
||||
Object.values(
|
||||
this.unreliableSubscriptions[result.type] || {}
|
||||
).forEach(
|
||||
// TODO: There is only one response that uses the unreliable channel atm,
|
||||
// highlight_set_entity, if there are more it's likely they will all have the same
|
||||
// sequence logic, but I'm not sure if we use a single global sequence or a sequence
|
||||
// per unreliable subscription.
|
||||
(callback) => {
|
||||
let data = result?.data
|
||||
if (isHighlightSetEntity_type(data)) {
|
||||
if (
|
||||
data.sequence !== undefined &&
|
||||
data.sequence > this.inSequence
|
||||
) {
|
||||
this.inSequence = data.sequence
|
||||
callback(result)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
// When the EngineConnection starts a connection, we want to register
|
||||
// callbacks into the WebSocket/PeerConnection.
|
||||
engineConnection.websocket?.addEventListener('message', ((
|
||||
event: MessageEvent
|
||||
) => {
|
||||
if (event.data instanceof ArrayBuffer) {
|
||||
// If the data is an ArrayBuffer, it's the result of an export command,
|
||||
// because in all other cases we send JSON strings. But in the case of
|
||||
// export we send a binary blob.
|
||||
// Pass this to our export function.
|
||||
exportSave(event.data).then(() => {
|
||||
this.pendingExport?.resolve()
|
||||
}, this.pendingExport?.reject)
|
||||
} else {
|
||||
const message: Models['WebSocketResponse_type'] = JSON.parse(
|
||||
event.data
|
||||
)
|
||||
if (
|
||||
message.success &&
|
||||
(message.resp.type === 'modeling' ||
|
||||
message.resp.type === 'modeling_batch') &&
|
||||
message.request_id
|
||||
) {
|
||||
this.handleModelingCommand(
|
||||
message.resp,
|
||||
message.request_id,
|
||||
message
|
||||
}
|
||||
)
|
||||
} else if (
|
||||
!message.success &&
|
||||
message.request_id &&
|
||||
this.artifactMap[message.request_id]
|
||||
) {
|
||||
this.handleFailedModelingCommand(message.request_id, message)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
// When the EngineConnection starts a connection, we want to register
|
||||
// callbacks into the WebSocket/PeerConnection.
|
||||
engineConnection.websocket?.addEventListener('message', ((
|
||||
event: MessageEvent
|
||||
) => {
|
||||
if (event.data instanceof ArrayBuffer) {
|
||||
// If the data is an ArrayBuffer, it's the result of an export command,
|
||||
// because in all other cases we send JSON strings. But in the case of
|
||||
// export we send a binary blob.
|
||||
// Pass this to our export function.
|
||||
exportSave(event.data).then(() => {
|
||||
this.pendingExport?.resolve()
|
||||
}, this.pendingExport?.reject)
|
||||
} else {
|
||||
const message: Models['WebSocketResponse_type'] = JSON.parse(
|
||||
event.data
|
||||
)
|
||||
if (
|
||||
message.success &&
|
||||
(message.resp.type === 'modeling' ||
|
||||
message.resp.type === 'modeling_batch') &&
|
||||
message.request_id
|
||||
) {
|
||||
this.handleModelingCommand(
|
||||
message.resp,
|
||||
message.request_id,
|
||||
message
|
||||
)
|
||||
} else if (
|
||||
!message.success &&
|
||||
message.request_id &&
|
||||
this.artifactMap[message.request_id]
|
||||
) {
|
||||
this.handleFailedModelingCommand(message.request_id, message)
|
||||
}
|
||||
}) as EventListener)
|
||||
}
|
||||
}) as EventListener)
|
||||
|
||||
this.engineConnection?.addEventListener(
|
||||
EngineConnectionEvents.NewTrack,
|
||||
(({ detail: { mediaStream } }: CustomEvent<NewTrackArgs>) => {
|
||||
mediaStream.getVideoTracks()[0].addEventListener('mute', () => {
|
||||
console.error(
|
||||
'video track mute: check webrtc internals -> inbound rtp'
|
||||
)
|
||||
})
|
||||
this.onEngineConnectionNewTrack = ({
|
||||
detail: { mediaStream },
|
||||
}: CustomEvent<NewTrackArgs>) => {
|
||||
mediaStream.getVideoTracks()[0].addEventListener('mute', () => {
|
||||
console.error(
|
||||
'video track mute: check webrtc internals -> inbound rtp'
|
||||
)
|
||||
})
|
||||
|
||||
setMediaStream(mediaStream)
|
||||
}) as EventListener
|
||||
)
|
||||
|
||||
this.engineConnection?.connect()
|
||||
setMediaStream(mediaStream)
|
||||
}
|
||||
this.engineConnection?.addEventListener(
|
||||
EngineConnectionEvents.NewTrack,
|
||||
this.onEngineConnectionNewTrack as EventListener
|
||||
)
|
||||
|
||||
this.engineConnection?.connect()
|
||||
}
|
||||
this.engineConnection.addEventListener(
|
||||
EngineConnectionEvents.ConnectionStarted,
|
||||
this.onEngineConnectionStarted
|
||||
)
|
||||
}
|
||||
|
||||
@ -1629,7 +1740,26 @@ export class EngineCommandManager extends EventTarget {
|
||||
}
|
||||
tearDown() {
|
||||
if (this.engineConnection) {
|
||||
this.engineConnection.removeEventListener(
|
||||
EngineConnectionEvents.Opened,
|
||||
this.onEngineConnectionOpened
|
||||
)
|
||||
this.engineConnection.removeEventListener(
|
||||
EngineConnectionEvents.Closed,
|
||||
this.onEngineConnectionClosed
|
||||
)
|
||||
this.engineConnection.removeEventListener(
|
||||
EngineConnectionEvents.ConnectionStarted,
|
||||
this.onEngineConnectionStarted
|
||||
)
|
||||
this.engineConnection.removeEventListener(
|
||||
EngineConnectionEvents.NewTrack,
|
||||
this.onEngineConnectionNewTrack as EventListener
|
||||
)
|
||||
|
||||
this.engineConnection?.tearDown()
|
||||
this.engineConnection = undefined
|
||||
|
||||
// Our window.tearDown assignment causes this case to happen which is
|
||||
// only really for tests.
|
||||
// @ts-ignore
|
||||
|
@ -64,7 +64,7 @@ export interface MouseGuard {
|
||||
rotate: MouseGuardHandler
|
||||
}
|
||||
|
||||
export const butName = (e: React.MouseEvent) => ({
|
||||
export const btnName = (e: React.MouseEvent) => ({
|
||||
middle: !!(e.buttons & 4) || e.button === 1,
|
||||
right: !!(e.buttons & 2) || e.button === 2,
|
||||
left: !!(e.buttons & 1) || e.button === 0,
|
||||
@ -75,8 +75,8 @@ export const cameraMouseDragGuards: Record<CameraSystem, MouseGuard> = {
|
||||
pan: {
|
||||
description: 'Right click + Shift + drag or middle click + drag',
|
||||
callback: (e) =>
|
||||
(butName(e).middle && noModifiersPressed(e)) ||
|
||||
(butName(e).right && e.shiftKey),
|
||||
(btnName(e).middle && noModifiersPressed(e)) ||
|
||||
(btnName(e).right && e.shiftKey),
|
||||
},
|
||||
zoom: {
|
||||
description: 'Scroll wheel or Right click + Ctrl + drag',
|
||||
@ -85,15 +85,15 @@ export const cameraMouseDragGuards: Record<CameraSystem, MouseGuard> = {
|
||||
},
|
||||
rotate: {
|
||||
description: 'Right click + drag',
|
||||
callback: (e) => butName(e).right && noModifiersPressed(e),
|
||||
callback: (e) => btnName(e).right && noModifiersPressed(e),
|
||||
},
|
||||
},
|
||||
OnShape: {
|
||||
pan: {
|
||||
description: 'Right click + Ctrl + drag or middle click + drag',
|
||||
callback: (e) =>
|
||||
(butName(e).right && e.ctrlKey) ||
|
||||
(butName(e).middle && noModifiersPressed(e)),
|
||||
(btnName(e).right && e.ctrlKey) ||
|
||||
(btnName(e).middle && noModifiersPressed(e)),
|
||||
},
|
||||
zoom: {
|
||||
description: 'Scroll wheel',
|
||||
@ -102,72 +102,72 @@ export const cameraMouseDragGuards: Record<CameraSystem, MouseGuard> = {
|
||||
},
|
||||
rotate: {
|
||||
description: 'Right click + drag',
|
||||
callback: (e) => butName(e).right && noModifiersPressed(e),
|
||||
callback: (e) => btnName(e).right && noModifiersPressed(e),
|
||||
},
|
||||
},
|
||||
'Trackpad Friendly': {
|
||||
pan: {
|
||||
description: 'Left click + Alt + Shift + drag or middle click + drag',
|
||||
callback: (e) =>
|
||||
(butName(e).left && e.altKey && e.shiftKey && !e.metaKey) ||
|
||||
(butName(e).middle && noModifiersPressed(e)),
|
||||
(btnName(e).left && e.altKey && e.shiftKey && !e.metaKey) ||
|
||||
(btnName(e).middle && noModifiersPressed(e)),
|
||||
},
|
||||
zoom: {
|
||||
description: 'Scroll wheel or Left click + Alt + OS + drag',
|
||||
dragCallback: (e) => butName(e).left && e.altKey && e.metaKey,
|
||||
dragCallback: (e) => btnName(e).left && e.altKey && e.metaKey,
|
||||
scrollCallback: () => true,
|
||||
},
|
||||
rotate: {
|
||||
description: 'Left click + Alt + drag',
|
||||
callback: (e) => butName(e).left && e.altKey && !e.shiftKey && !e.metaKey,
|
||||
callback: (e) => btnName(e).left && e.altKey && !e.shiftKey && !e.metaKey,
|
||||
lenientDragStartButton: 0,
|
||||
},
|
||||
},
|
||||
Solidworks: {
|
||||
pan: {
|
||||
description: 'Right click + Ctrl + drag',
|
||||
callback: (e) => butName(e).right && e.ctrlKey,
|
||||
callback: (e) => btnName(e).right && e.ctrlKey,
|
||||
lenientDragStartButton: 2,
|
||||
},
|
||||
zoom: {
|
||||
description: 'Scroll wheel or Middle click + Shift + drag',
|
||||
dragCallback: (e) => butName(e).middle && e.shiftKey,
|
||||
dragCallback: (e) => btnName(e).middle && e.shiftKey,
|
||||
scrollCallback: () => true,
|
||||
},
|
||||
rotate: {
|
||||
description: 'Middle click + drag',
|
||||
callback: (e) => butName(e).middle && noModifiersPressed(e),
|
||||
callback: (e) => btnName(e).middle && noModifiersPressed(e),
|
||||
},
|
||||
},
|
||||
NX: {
|
||||
pan: {
|
||||
description: 'Middle click + Shift + drag',
|
||||
callback: (e) => butName(e).middle && e.shiftKey,
|
||||
callback: (e) => btnName(e).middle && e.shiftKey,
|
||||
},
|
||||
zoom: {
|
||||
description: 'Scroll wheel or Middle click + Ctrl + drag',
|
||||
dragCallback: (e) => butName(e).middle && e.ctrlKey,
|
||||
dragCallback: (e) => btnName(e).middle && e.ctrlKey,
|
||||
scrollCallback: () => true,
|
||||
},
|
||||
rotate: {
|
||||
description: 'Middle click + drag',
|
||||
callback: (e) => butName(e).middle && noModifiersPressed(e),
|
||||
callback: (e) => btnName(e).middle && noModifiersPressed(e),
|
||||
},
|
||||
},
|
||||
Creo: {
|
||||
pan: {
|
||||
description: 'Left click + Ctrl + drag',
|
||||
callback: (e) => butName(e).left && !butName(e).right && e.ctrlKey,
|
||||
callback: (e) => btnName(e).left && !btnName(e).right && e.ctrlKey,
|
||||
},
|
||||
zoom: {
|
||||
description: 'Scroll wheel or Right click + Ctrl + drag',
|
||||
dragCallback: (e) => butName(e).right && !butName(e).left && e.ctrlKey,
|
||||
dragCallback: (e) => btnName(e).right && !btnName(e).left && e.ctrlKey,
|
||||
scrollCallback: () => true,
|
||||
},
|
||||
rotate: {
|
||||
description: 'Middle (or Left + Right) click + Ctrl + drag',
|
||||
callback: (e) => {
|
||||
const b = butName(e)
|
||||
const b = btnName(e)
|
||||
return (b.middle || (b.left && b.right)) && e.ctrlKey
|
||||
},
|
||||
},
|
||||
@ -175,7 +175,7 @@ export const cameraMouseDragGuards: Record<CameraSystem, MouseGuard> = {
|
||||
AutoCAD: {
|
||||
pan: {
|
||||
description: 'Middle click + drag',
|
||||
callback: (e) => butName(e).middle && noModifiersPressed(e),
|
||||
callback: (e) => btnName(e).middle && noModifiersPressed(e),
|
||||
},
|
||||
zoom: {
|
||||
description: 'Scroll wheel',
|
||||
@ -184,7 +184,7 @@ export const cameraMouseDragGuards: Record<CameraSystem, MouseGuard> = {
|
||||
},
|
||||
rotate: {
|
||||
description: 'Middle click + Shift + drag',
|
||||
callback: (e) => butName(e).middle && e.shiftKey,
|
||||
callback: (e) => btnName(e).middle && e.shiftKey,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { CommandSetConfig } from 'lib/commandTypes'
|
||||
import { StateMachineCommandSetConfig } from 'lib/commandTypes'
|
||||
import { authMachine } from 'machines/authMachine'
|
||||
|
||||
type AuthCommandSchema = {}
|
||||
|
||||
export const authCommandBarConfig: CommandSetConfig<
|
||||
export const authCommandBarConfig: StateMachineCommandSetConfig<
|
||||
typeof authMachine,
|
||||
AuthCommandSchema
|
||||
> = {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { CommandSetConfig } from 'lib/commandTypes'
|
||||
import { StateMachineCommandSetConfig } from 'lib/commandTypes'
|
||||
import { homeMachine } from 'machines/homeMachine'
|
||||
|
||||
export type HomeCommandSchema = {
|
||||
@ -17,7 +17,7 @@ export type HomeCommandSchema = {
|
||||
}
|
||||
}
|
||||
|
||||
export const homeCommandBarConfig: CommandSetConfig<
|
||||
export const homeCommandBarConfig: StateMachineCommandSetConfig<
|
||||
typeof homeMachine,
|
||||
HomeCommandSchema
|
||||
> = {
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Models } from '@kittycad/lib'
|
||||
import { CommandSetConfig, KclCommandValue } from 'lib/commandTypes'
|
||||
import { StateMachineCommandSetConfig, KclCommandValue } from 'lib/commandTypes'
|
||||
import { KCL_DEFAULT_LENGTH } from 'lib/constants'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { modelingMachine } from 'machines/modelingMachine'
|
||||
import { modelingMachine, SketchTool } from 'machines/modelingMachine'
|
||||
|
||||
type OutputFormat = Models['OutputFormat_type']
|
||||
type OutputTypeKey = OutputFormat['type']
|
||||
@ -27,9 +27,12 @@ export type ModelingCommandSchema = {
|
||||
// result: (typeof EXTRUSION_RESULTS)[number]
|
||||
distance: KclCommandValue
|
||||
}
|
||||
'change tool': {
|
||||
tool: SketchTool
|
||||
}
|
||||
}
|
||||
|
||||
export const modelingMachineConfig: CommandSetConfig<
|
||||
export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
||||
typeof modelingMachine,
|
||||
ModelingCommandSchema
|
||||
> = {
|
||||
@ -37,22 +40,47 @@ export const modelingMachineConfig: CommandSetConfig<
|
||||
description: 'Enter sketch mode.',
|
||||
icon: 'sketch',
|
||||
},
|
||||
// TODO the event is no 'change tool' with data: 'line', 'rectangle' etc
|
||||
// 'Equip Line tool': {
|
||||
// description: 'Start drawing straight lines.',
|
||||
// icon: 'line',
|
||||
// displayName: 'Line',
|
||||
// },
|
||||
// 'Equip tangential arc to': {
|
||||
// description: 'Start drawing an arc tangent to the current segment.',
|
||||
// icon: 'arc',
|
||||
// displayName: 'Tangential Arc',
|
||||
// },
|
||||
// 'Equip rectangle tool': {
|
||||
// description: 'Start drawing a rectangle.',
|
||||
// icon: 'rectangle',
|
||||
// displayName: 'Rectangle',
|
||||
// },
|
||||
'change tool': [
|
||||
{
|
||||
description: 'Start drawing straight lines.',
|
||||
icon: 'line',
|
||||
displayName: 'Line',
|
||||
args: {
|
||||
tool: {
|
||||
defaultValue: 'line',
|
||||
required: true,
|
||||
skip: true,
|
||||
inputType: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'Start drawing an arc tangent to the current segment.',
|
||||
icon: 'arc',
|
||||
displayName: 'Tangential Arc',
|
||||
args: {
|
||||
tool: {
|
||||
defaultValue: 'tangentialArc',
|
||||
required: true,
|
||||
skip: true,
|
||||
inputType: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'Start drawing a rectangle.',
|
||||
icon: 'rectangle',
|
||||
displayName: 'Rectangle',
|
||||
args: {
|
||||
tool: {
|
||||
defaultValue: 'rectangle',
|
||||
required: true,
|
||||
skip: true,
|
||||
inputType: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
Export: {
|
||||
description: 'Export the current model.',
|
||||
icon: 'exportFile',
|
||||
|
@ -124,7 +124,8 @@ export function createSettingsCommand({
|
||||
displayName: `Settings · ${decamelize(type.replaceAll('.', ' · '), {
|
||||
separator: ' ',
|
||||
})}`,
|
||||
ownerMachine: 'settings',
|
||||
description: settingConfig.description,
|
||||
groupId: 'settings',
|
||||
icon: 'settings',
|
||||
needsReview: false,
|
||||
onSubmit: (data) => {
|
||||
|
@ -33,13 +33,13 @@ export interface KclExpressionWithVariable extends KclExpression {
|
||||
export type KclCommandValue = KclExpression | KclExpressionWithVariable
|
||||
export type CommandInputType = (typeof INPUT_TYPES)[number]
|
||||
|
||||
export type CommandSetSchema<T extends AnyStateMachine> = Partial<{
|
||||
export type StateMachineCommandSetSchema<T extends AnyStateMachine> = Partial<{
|
||||
[EventType in EventFrom<T>['type']]: Record<string, any>
|
||||
}>
|
||||
|
||||
export type CommandSet<
|
||||
export type StateMachineCommandSet<
|
||||
T extends AllMachines,
|
||||
Schema extends CommandSetSchema<T>
|
||||
Schema extends StateMachineCommandSetSchema<T>
|
||||
> = Partial<{
|
||||
[EventType in EventFrom<T>['type']]: Command<
|
||||
T,
|
||||
@ -48,24 +48,28 @@ export type CommandSet<
|
||||
>
|
||||
}>
|
||||
|
||||
export type CommandSetConfig<
|
||||
/**
|
||||
* A configuration object for a set of commands tied to a state machine.
|
||||
* Each event type can have one or more commands associated with it.
|
||||
* @param T The state machine type.
|
||||
* @param Schema The schema for the command set, defined by the developer.
|
||||
*/
|
||||
export type StateMachineCommandSetConfig<
|
||||
T extends AllMachines,
|
||||
Schema extends CommandSetSchema<T>
|
||||
Schema extends StateMachineCommandSetSchema<T>
|
||||
> = Partial<{
|
||||
[EventType in EventFrom<T>['type']]: CommandConfig<
|
||||
T,
|
||||
EventFrom<T>['type'],
|
||||
Schema[EventType]
|
||||
>
|
||||
[EventType in EventFrom<T>['type']]:
|
||||
| CommandConfig<T, EventFrom<T>['type'], Schema[EventType]>
|
||||
| CommandConfig<T, EventFrom<T>['type'], Schema[EventType]>[]
|
||||
}>
|
||||
|
||||
export type Command<
|
||||
T extends AnyStateMachine = AnyStateMachine,
|
||||
CommandName extends EventFrom<T>['type'] = EventFrom<T>['type'],
|
||||
CommandSchema extends CommandSetSchema<T>[CommandName] = CommandSetSchema<T>[CommandName]
|
||||
CommandSchema extends StateMachineCommandSetSchema<T>[CommandName] = StateMachineCommandSetSchema<T>[CommandName]
|
||||
> = {
|
||||
name: CommandName
|
||||
ownerMachine: T['id']
|
||||
groupId: T['id']
|
||||
needsReview: boolean
|
||||
onSubmit: (data?: CommandSchema) => void
|
||||
onCancel?: () => void
|
||||
@ -81,10 +85,10 @@ export type Command<
|
||||
export type CommandConfig<
|
||||
T extends AnyStateMachine = AnyStateMachine,
|
||||
CommandName extends EventFrom<T>['type'] = EventFrom<T>['type'],
|
||||
CommandSchema extends CommandSetSchema<T>[CommandName] = CommandSetSchema<T>[CommandName]
|
||||
CommandSchema extends StateMachineCommandSetSchema<T>[CommandName] = StateMachineCommandSetSchema<T>[CommandName]
|
||||
> = Omit<
|
||||
Command<T, CommandName, CommandSchema>,
|
||||
'name' | 'ownerMachine' | 'onSubmit' | 'onCancel' | 'args' | 'needsReview'
|
||||
'name' | 'groupId' | 'onSubmit' | 'onCancel' | 'args' | 'needsReview'
|
||||
> & {
|
||||
needsReview?: true
|
||||
args?: {
|
||||
|
@ -11,20 +11,20 @@ import {
|
||||
CommandArgument,
|
||||
CommandArgumentConfig,
|
||||
CommandConfig,
|
||||
CommandSetConfig,
|
||||
CommandSetSchema,
|
||||
StateMachineCommandSetConfig,
|
||||
StateMachineCommandSetSchema,
|
||||
} from './commandTypes'
|
||||
|
||||
interface CreateMachineCommandProps<
|
||||
T extends AnyStateMachine,
|
||||
S extends CommandSetSchema<T>
|
||||
S extends StateMachineCommandSetSchema<T>
|
||||
> {
|
||||
type: EventFrom<T>['type']
|
||||
ownerMachine: T['id']
|
||||
groupId: T['id']
|
||||
state: StateFrom<T>
|
||||
send: Function
|
||||
actor: InterpreterFrom<T>
|
||||
commandBarConfig?: CommandSetConfig<T, S>
|
||||
commandBarConfig?: StateMachineCommandSetConfig<T, S>
|
||||
onCancel?: () => void
|
||||
}
|
||||
|
||||
@ -32,22 +32,39 @@ interface CreateMachineCommandProps<
|
||||
// from a more terse Command Bar Meta definition.
|
||||
export function createMachineCommand<
|
||||
T extends AnyStateMachine,
|
||||
S extends CommandSetSchema<T>
|
||||
S extends StateMachineCommandSetSchema<T>
|
||||
>({
|
||||
ownerMachine,
|
||||
groupId,
|
||||
type,
|
||||
state,
|
||||
send,
|
||||
actor,
|
||||
commandBarConfig,
|
||||
onCancel,
|
||||
}: CreateMachineCommandProps<T, S>): Command<
|
||||
T,
|
||||
typeof type,
|
||||
S[typeof type]
|
||||
> | null {
|
||||
}: CreateMachineCommandProps<T, S>):
|
||||
| Command<T, typeof type, S[typeof type]>
|
||||
| Command<T, typeof type, S[typeof type]>[]
|
||||
| null {
|
||||
const commandConfig = commandBarConfig && commandBarConfig[type]
|
||||
if (!commandConfig) return null
|
||||
// There may be no command config for this event type,
|
||||
// or there may be multiple commands to create.
|
||||
if (!commandConfig) {
|
||||
return null
|
||||
} else if (commandConfig instanceof Array) {
|
||||
return commandConfig
|
||||
.map((config) =>
|
||||
createMachineCommand({
|
||||
groupId,
|
||||
type,
|
||||
state,
|
||||
send,
|
||||
actor,
|
||||
commandBarConfig: { [type]: config },
|
||||
onCancel,
|
||||
})
|
||||
)
|
||||
.filter((c) => c !== null) as Command<T, typeof type, S[typeof type]>[]
|
||||
}
|
||||
|
||||
// Hide commands based on platform by returning `null`
|
||||
// so the consumer can filter them out
|
||||
@ -62,8 +79,9 @@ export function createMachineCommand<
|
||||
|
||||
const command: Command<T, typeof type, S[typeof type]> = {
|
||||
name: type,
|
||||
ownerMachine: ownerMachine,
|
||||
groupId,
|
||||
icon,
|
||||
description: commandConfig.description,
|
||||
needsReview: commandConfig.needsReview || false,
|
||||
onSubmit: (data?: S[typeof type]) => {
|
||||
if (data !== undefined && data !== null) {
|
||||
@ -84,6 +102,10 @@ export function createMachineCommand<
|
||||
command.onCancel = onCancel
|
||||
}
|
||||
|
||||
if ('displayName' in commandConfig) {
|
||||
command.displayName = commandConfig.displayName
|
||||
}
|
||||
|
||||
return command
|
||||
}
|
||||
|
||||
@ -92,7 +114,7 @@ export function createMachineCommand<
|
||||
// bundled together into the args for a Command.
|
||||
function buildCommandArguments<
|
||||
T extends AnyStateMachine,
|
||||
S extends CommandSetSchema<T>,
|
||||
S extends StateMachineCommandSetSchema<T>,
|
||||
CommandName extends EventFrom<T>['type'] = EventFrom<T>['type']
|
||||
>(
|
||||
state: StateFrom<T>,
|
||||
@ -112,7 +134,7 @@ function buildCommandArguments<
|
||||
|
||||
export function buildCommandArgument<
|
||||
T extends AnyStateMachine,
|
||||
O extends CommandSetSchema<T> = CommandSetSchema<T>
|
||||
O extends StateMachineCommandSetSchema<T> = StateMachineCommandSetSchema<T>
|
||||
>(
|
||||
arg: CommandArgumentConfig<O, T>,
|
||||
context: ContextFrom<T>,
|
||||
|
@ -57,7 +57,7 @@ export type CommandBarMachineEvent =
|
||||
}
|
||||
| {
|
||||
type: 'Find and select command'
|
||||
data: { name: string; ownerMachine: string }
|
||||
data: { name: string; groupId: string }
|
||||
}
|
||||
| {
|
||||
type: 'Change current argument'
|
||||
@ -66,7 +66,7 @@ export type CommandBarMachineEvent =
|
||||
|
||||
export const commandBarMachine = createMachine(
|
||||
{
|
||||
/** @xstate-layout N4IgpgJg5mDOIC5QGED2BbdBDAdhABAEJYBOAxMgDaqxgDaADALqKgAONAlgC6eo6sQAD0QBaAJwA6AGwAmAKwBmBoukAWafIAcDcSoA0IAJ6JZDaZIDs8hgzV6AjA61a1DWQF8PhtJlwFiciowUkYWJBAOWB4+AQiRBFF5CwdpcVkHS1lpVyU5QxNEh1lFGTUsrUUtOQd5SwZLLx8MbDwiUkkqGkgyAHk2MBwwwSiY-kEEswZJbPltM0U3eXLZAsRFeQcrerUHRbTFvfkmkF9WgI6u2ggyADFONv98WkowAGNufDeW-2GI0d443iiHESkktUUilkskqdiUWjWCAcDC0kjUqnElkc6lkoK0JzOT0CnWo1zIAEEIARvn48LA-uwuIC4qAEqIHJjJPJcYtxFoYdJFFjVsZEG5pqCquJxJoGvJxOUCT82sSrj0AEpgdCoABuYC+yog9OYIyZsQmYmhWzMmTqLnU0kyikRGVRbj5lg2SmUam5StpFxIkgAymBXh8HlADQGyKHw58aecGZEzUDWYgchY7CisWoSlpQQ5EVDLJJxKkdIpyypUop-ed2kHCW0Xu9uD1kwDzcCEJCtlUNgWhZZ1OlnaKEBpZJItHVzDZ5xlpPWiZdDc8w22Ow5wozosyLUiVNMSg5ytVKmfrIipzO564z2otPVpI1vKd18SAOJYbgACzAEhI3wUgoAAV3QQZuFgCg-1wGAvjAkgSCgkCSHAyCcG4TtUxZYRED2TQZGSOw80qaQ7ARCc9mmZYSksextGkNJBRXFUOh-f9AOA0CIKgmCABE4E3D5oyTE1-lww8clKBjZF2Bh5SFZwXUUyRVDzJwNE2LQzzYwNJE4gCgJwKNeMw6DJHJAB3LAYlM-AHjYMDuFjMCACN0B4NCMKgnD927dMkWSUs8yY8RISfWQshvcsrDlapshULJPHfZsDKM7iHPM-jJAANSwShOAgX9IzICB+DASQHh1VAAGsqp1Qrit-MByXQvisP8sY8ISZwbCsDllBLSwz1qRFBQsRZ5WRIVkui-TG0M39jJ4jqLNgfLmpK3hTLIQCSFQIM2EoX8ADMjvQSQmqKna2vWvyJL3HrD1SEoyzSdJUkUm9dnUtQn10aK6hkxbiU1HVODAay3M87zE1+J6UwCtN8IQUauR0OZ0ksFwUUqRFPWmUdouPXR3TBjoIahmHKQIHKuqRrtUYSaVShhLF+TkeTzBFQosVKDRM2RVInEdSmg2p6GyE1bU9R8zrsKZqSexFqQHWlHNXHzCbFhnX1+UydFkVxiXJClmGAFEIG8hmld3ZGXp7TR5C5RSCxdjlQV1tR9bqaVdhsSFxDN5AALeOrgPa3ysJgiqcCqmr6sa7bWujxXjQd5nesQZYthsblIQYJx6hUic9AsdEFT2HQzByVLmgDJaw-eSOHPTjbysq6qcFqhrrtT9sO-4ugd1NFGc4QXEPo5eVLGsFxlnKREXGnZI9HcT1mOikO0s-S5w7bqNh9j-bkKOyQTvOy6B9utOHtj7qD1V8pSyrHJZ3STY4QmjQ0R2Ww0oSjuF3u+HAqAIBwEEOlRs48nZBVENkKQNprC42qBoJ0LothaXMJ9aElRXDLj3k3VUpJIBwOfgg4oMx8y41SPUOY8p5DFk2GiKoxsoRVmhGoM2cY2zAQRngChgU0a7HZtFH0ilRp1D5usVh6JXCKD2CUdI8lQ7rlbB8chkkJ6HkxFsBeulbQZAUCwrYCiqzIhyE+fqZtMomTMg-aCwiWYEV2NOBUCghQ6EFGzYsvpwQUT5MxawFECx2JWllRxMdLI2TsrtKMTkXIuMnmeCiZYwrePMFWCKv1XZKVGroWwmxNARK4g4hWG0tp3wSSk16lQpC41ULjV8uxUjSBvFCNEDTdCOkWCY44xCGzgzAJDaGdTnaZHBADYcqg5DpCovzeRno5izA0BeUOh8o5OPgDo+BaNvqV0xNFaE7gdYTnnvkmUChdKEMyF4LwQA */
|
||||
/** @xstate-layout N4IgpgJg5mDOIC5QGED2BbdBDAdhABAEJYBOAxMgDaqxgDaADALqKgAONAlgC6eo6sQAD0QBaAJwA6AGwAmAKwBmBoukAWafIAcDcSoA0IAJ6JZDaZIDs8hgzV6AjA61a1DWQF8PhtJlwFiciowUkYWJBAOWB4+AQiRBFF5CwdpcVkHS1lpVyU5QxNEh1lFGTUsrUUtOQd5SwZLLx8MbDwiUkkqGkgyAHk2MBwwwSiY-kEEswZJbPltM0U3eXLZAsRFeQcrerUHRbTFvfkmkF9WgI6u2ggyADFONv98WkowAGNufDeW-2GI0d443iiHESkktUUilkskqdiUWjWCAcDC0kjUqnElkc6lkoK0JzOT0CnWo1zIAEEIARvn48LA-uwuIC4qAEqIHJjJPJcYtxFoYdJFFjVsZEG5pqCquJxJoGvJxOUCT82sSrj0AEpgdCoABuYC+yog9OYIyZsQmYmhWzMmTqLnU0kyikRGVRbj5lg2SmUam5StpFxIkgAymBXh8HlADQGyKHw58aecGZEzUDWYgchY7CisWoSlpQQ5EVDLJJxKkdIpyypUop-ed2kHCW0Xu9uD1kwDzcCEJCtlUNgWhZZ1OlnaKEBpZJItHVzDZ5xlpPWiZdDc8w22O7JwozosyLUj3KiZZY85YtMUNgx5Ii81JuS5xBtPVCCyuVR0AOJYbgACzAEhI3wUgoAAV3QQZuFgCg-1wGAvjAkgSCgkCSHAyCcG4TtUxZYRED2TQZGSOw80qaQ7ARCc9mmZYSjPPFpDSQUP0DSQf3-QDgNAiCoJggAROBNw+aMkxNf5cMPHJSjPWRdhvZ9LGcF0b0kVQ8ycDRNkvNRWMbdjfwAoCcCjHjMOgyRyQAdywGITPwB42DA7hYzAgAjdAeDQjCoJw-du3TJE6mnWwoT0NIUTUAs71sMtdGsRYCyUtI9OJDijO49DeKw2BJAANSwShOAgX9IzICB+DASQHh1VAAGsqp1Qrit-MBySy8y-LGPCElSeoy2lZJcwcNRfXHQpBWmWQsRsPYdDPZJUu-QyuPssy+Py5qSt4EyyEAkhUCDNhKF-AAzQ70EkJqiu2tqOt88S926w9UhhLlykWXFHXcTJixsd60hHGxNj2Jag01HVODAKzXI8rzE1+R6U38tN8IQJSuR0OZ0gvHQXHGxBPWmUdppUWwFV0MHJAhqGYcpAh1qwrqDx7ZFajUmVpulEbqlqREsfBTQ0jcPM5P5KmaehshNW1PVvOy7Cka7VHeoYDkyjSPQtAvPMqkRQU1BnX1+UydFkQvCWwEhqWAFEIC8xnFd3ZHntZuTDZG4ob1nGxPTUfXrBnS8nDmEpT2ObxTnXVUALeOrgPanycvKyrqpwWqGqurbWsThXjWd5WesQZYtmBkplCceplIncK0SrXYqnccxxcj5s2OQWP4-s3PzJgiqcCqmr6sa7P2x7vi6AcAvJJ7XEy0dUFMWsFxlnKfn+S5CL3E9Jiuapjv3i7qNx+T-bDskY6zourObpz+6cuZgK0Y5Ua0TqEvXDkJR-YnR0s1xjkIMnDln3uuVsHwOxT1NCjIuR5nCSBUExJi1huRqyooUTYpYnzeyUFkKsy4Tg4FQBAOAgg26Nmga7QKohshSBtNYXGDonQumtPYaQfsSjLBHDefepJICUJZtQ4oMx8wXj6jebGt4JwbC2OiVwig9hh2hLpVu0cOhxjbMBBGeABFPwSA3ERRMlhKWCn9WRVQzZQirMo0BAYNzxn4RJGBh5MRbGXsHawGQFBmLrpUasOQorOAjs0OxaUVrGVMvfaCuiVYEV2KWTYg1yxyA0i6ZYaIPSL3lOkS8wSo6hOWpxCJ8te6WRsnZKMjlnIxNgSNIiiTF6vVSdI9JM1taXlxLzTwqiClBnSqtSJScLIFVvjtKANSXquENqoJwtgyZzRFIUQ4MhqgNACdNTQCpLbWyshMnsjpSwjXRJYHecgcmImsLIz0odNAaGqPiHpDYY6HwTlE+ATiqHPwohYewWQshmGhHrCcJz5Bck5toZwGhMheC8EAA */
|
||||
predictableActionArguments: true,
|
||||
tsTypes: {} as import('./commandBarMachine.typegen').Typegen0,
|
||||
context: {
|
||||
@ -120,9 +120,7 @@ export const commandBarMachine = createMachine(
|
||||
context.commands.filter(
|
||||
(c) =>
|
||||
!event.data.commands.some(
|
||||
(c2) =>
|
||||
c2.name === c.name &&
|
||||
c2.ownerMachine === c.ownerMachine
|
||||
(c2) => c2.name === c.name && c2.groupId === c.groupId
|
||||
)
|
||||
),
|
||||
}),
|
||||
@ -149,6 +147,10 @@ export const commandBarMachine = createMachine(
|
||||
cond: 'Command has no arguments',
|
||||
actions: ['Execute command'],
|
||||
},
|
||||
{
|
||||
target: 'Checking Arguments',
|
||||
cond: 'All arguments are skippable',
|
||||
},
|
||||
{
|
||||
target: 'Gathering arguments',
|
||||
actions: ['Set current argument to first non-skippable'],
|
||||
@ -393,9 +395,7 @@ export const commandBarMachine = createMachine(
|
||||
selectedCommand: (c, e) => {
|
||||
if (e.type !== 'Find and select command') return c.selectedCommand
|
||||
const found = c.commands.find(
|
||||
(cmd) =>
|
||||
cmd.name === e.data.name &&
|
||||
cmd.ownerMachine === e.data.ownerMachine
|
||||
(cmd) => cmd.name === e.data.name && cmd.groupId === e.data.groupId
|
||||
)
|
||||
|
||||
return !!found ? found : c.selectedCommand
|
||||
@ -514,7 +514,9 @@ export const commandBarMachine = createMachine(
|
||||
)
|
||||
|
||||
function sortCommands(a: Command, b: Command) {
|
||||
if (b.ownerMachine === 'auth') return -1
|
||||
if (a.ownerMachine === 'auth') return 1
|
||||
if (b.groupId === 'auth' && !(a.groupId === 'auth')) return -2
|
||||
if (a.groupId === 'auth' && !(b.groupId === 'auth')) return 2
|
||||
if (b.groupId === 'settings' && !(a.groupId === 'settings')) return -1
|
||||
if (a.groupId === 'settings' && !(b.groupId === 'settings')) return 1
|
||||
return a.name.localeCompare(b.name)
|
||||
}
|
||||
|
@ -57,6 +57,9 @@ const Home = () => {
|
||||
kclManager.cancelAllExecutions()
|
||||
}, [])
|
||||
|
||||
useHotkeys('backspace', (e) => {
|
||||
e.preventDefault()
|
||||
})
|
||||
useHotkeys(
|
||||
isTauri() ? 'mod+,' : 'shift+mod+,',
|
||||
() => navigate(paths.HOME + paths.SETTINGS),
|
||||
|
10
src/wasm-lib/Cargo.lock
generated
@ -1842,7 +1842,7 @@ dependencies = [
|
||||
"bincode",
|
||||
"either",
|
||||
"fnv",
|
||||
"itertools 0.10.5",
|
||||
"itertools 0.12.1",
|
||||
"lazy_static",
|
||||
"nom",
|
||||
"quick-xml",
|
||||
@ -2928,18 +2928,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.61"
|
||||
version = "1.0.62"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709"
|
||||
checksum = "f2675633b1499176c2dff06b0856a27976a8f9d436737b4cf4f312d4d91d8bbb"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.61"
|
||||
version = "1.0.62"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
|
||||
checksum = "d20468752b09f49e909e55a5d338caa8bedf615594e9d80bc4c565d30faf798c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -35,7 +35,7 @@ schemars = { version = "0.8.17", features = ["impl_json_schema", "url", "uuid1"]
|
||||
serde = { version = "1.0.204", features = ["derive"] }
|
||||
serde_json = "1.0.120"
|
||||
sha2 = "0.10.8"
|
||||
thiserror = "1.0.61"
|
||||
thiserror = "1.0.62"
|
||||
toml = "0.8.14"
|
||||
ts-rs = { version = "9.0.1", features = ["uuid-impl", "url-impl", "chrono-impl", "no-serde-warnings", "serde-json-impl"] }
|
||||
url = { version = "2.5.2", features = ["serde"] }
|
||||
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 82 KiB |
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 82 KiB |
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 88 KiB |
Before Width: | Height: | Size: 134 KiB After Width: | Height: | Size: 134 KiB |
Before Width: | Height: | Size: 126 KiB After Width: | Height: | Size: 126 KiB |
Before Width: | Height: | Size: 132 KiB After Width: | Height: | Size: 132 KiB |
Before Width: | Height: | Size: 130 KiB After Width: | Height: | Size: 130 KiB |
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 105 KiB |
Before Width: | Height: | Size: 123 KiB After Width: | Height: | Size: 123 KiB |
Before Width: | Height: | Size: 126 KiB After Width: | Height: | Size: 126 KiB |
Before Width: | Height: | Size: 127 KiB After Width: | Height: | Size: 127 KiB |
Before Width: | Height: | Size: 122 KiB After Width: | Height: | Size: 122 KiB |
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 98 KiB |
Before Width: | Height: | Size: 146 KiB After Width: | Height: | Size: 146 KiB |
Before Width: | Height: | Size: 160 KiB After Width: | Height: | Size: 160 KiB |
Before Width: | Height: | Size: 122 KiB After Width: | Height: | Size: 122 KiB |
Before Width: | Height: | Size: 132 KiB After Width: | Height: | Size: 132 KiB |
Before Width: | Height: | Size: 127 KiB After Width: | Height: | Size: 127 KiB |
Before Width: | Height: | Size: 119 KiB After Width: | Height: | Size: 119 KiB |
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 97 KiB |
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 120 KiB |
Before Width: | Height: | Size: 139 KiB After Width: | Height: | Size: 139 KiB |
Before Width: | Height: | Size: 125 KiB After Width: | Height: | Size: 125 KiB |
Before Width: | Height: | Size: 139 KiB After Width: | Height: | Size: 139 KiB |
Before Width: | Height: | Size: 124 KiB After Width: | Height: | Size: 124 KiB |
Before Width: | Height: | Size: 214 KiB After Width: | Height: | Size: 214 KiB |
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 117 KiB |
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 100 KiB |
Before Width: | Height: | Size: 125 KiB After Width: | Height: | Size: 125 KiB |
Before Width: | Height: | Size: 131 KiB After Width: | Height: | Size: 131 KiB |
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 120 KiB |
Before Width: | Height: | Size: 122 KiB After Width: | Height: | Size: 122 KiB |
Before Width: | Height: | Size: 114 KiB After Width: | Height: | Size: 114 KiB |
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 70 KiB |
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 70 KiB |
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 70 KiB |
Before Width: | Height: | Size: 136 KiB After Width: | Height: | Size: 136 KiB |
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 117 KiB |
Before Width: | Height: | Size: 131 KiB After Width: | Height: | Size: 131 KiB |
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 104 KiB |
Before Width: | Height: | Size: 111 KiB After Width: | Height: | Size: 111 KiB |
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 120 KiB |
Before Width: | Height: | Size: 189 KiB After Width: | Height: | Size: 189 KiB |
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 112 KiB |
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 112 KiB |
Before Width: | Height: | Size: 333 KiB After Width: | Height: | Size: 334 KiB |
Before Width: | Height: | Size: 129 KiB After Width: | Height: | Size: 129 KiB |
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
Before Width: | Height: | Size: 195 KiB After Width: | Height: | Size: 195 KiB |