Compare commits

...

12 Commits

Author SHA1 Message Date
f9419a98b5 Cut release v0.24.1 (#3014) 2024-07-11 21:51:58 -04:00
999f72bccf mediaStream (#3013)
* mediaStream

* make vitest happy

* fmt
2024-07-11 20:57:27 -04:00
9dbe74e008 cleaner hack (#3012) 2024-07-12 09:41:39 +10:00
88d9cdc52b Codemirror deferrers (#3006)
* Force document update requests when necessary

* fix up codemirror deferrers

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* lock

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fixups kcl/index

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fix copilot

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* docs

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: Marijn Haverbeke <marijn@haverbeke.berlin>
2024-07-11 16:05:19 -07:00
2dd1f0f213 refactor: Rename ownerMachine to groupId in Command (#3010)
* refactor: Rename ownerMachine to groupId in Command

Commands don't need to be part of a state machine.

* Fix formatting
2024-07-11 18:10:47 -04:00
b971f3ecf4 Fix CUT_RELEASE_PR eval in ci.yml (#3003) 2024-07-11 08:19:33 -04:00
2198bd7580 Rename function to use standard abbreviation (#2965) 2024-07-11 11:52:26 +00:00
5fa1497b75 Don't navigate when Backspace/Delete is pressed on the home screen (#2987) 2024-07-11 07:50:59 -04:00
ff86e41283 Roll your own Playwright retries (#3002)
* roll you own playwright retries

* tweak

* tweak

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)

* add retries for ubuntu too

* Revert "A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)"

This reverts commit 327cc196cd.

* Revert "add retries for ubuntu too"

This reverts commit db877748e2.

* add retries for ubuntu too

* whoopsie

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-07-11 17:37:59 +10:00
08e4c03ca7 send failing test to axiom (#2996)
* send failing test to axiom (ubuntu)

* forgot always

* rename

* Update .github/workflows/playwright.yml

Co-authored-by: Adam Sunderland <adam@kittycad.io>

* update to indivdual lines of json

* another fix

* tweak output

* log macos too

---------

Co-authored-by: Adam Sunderland <adam@kittycad.io>
2024-07-11 14:32:36 +10:00
c654582137 Build tauri updater test bundles on 'Cut release' PRs (#2927)
* WIP: Automate tauri updater tests
Fixes #2926

* Same product name

* Tweak uploads

* Add cat

* Fix macos universal builds for updater

* New artifact name

* Revert "New artifact name"

This reverts commit 61defcab18.

* Final check

* Clean up
2024-07-10 18:41:07 -04:00
6c2fa95a32 Fix perspective camera toggle in debug pane to update immediately (#2969) 2024-07-10 17:50:25 -04:00
36 changed files with 532 additions and 239 deletions

View File

@ -13,6 +13,7 @@ on:
# Will checkout the last commit from the default branch (main as of 2023-10-04) # Will checkout the last commit from the default branch (main as of 2023-10-04)
env: 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')) }} 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: concurrency:
@ -110,8 +111,14 @@ jobs:
echo "$(jq --arg name 'Zoo Modeling App (Nightly)' \ echo "$(jq --arg name 'Zoo Modeling App (Nightly)' \
'.productName=$name' src-tauri/tauri.release.conf.json --indent 2)" > src-tauri/tauri.release.conf.json '.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 - uses: actions/upload-artifact@v3
if: github.event_name == 'schedule' if: ${{ github.event_name == 'schedule' || env.CUT_RELEASE_PR == 'true' }}
with: with:
path: | path: |
package.json package.json
@ -377,6 +384,30 @@ jobs:
E2E_TAURI_ENABLED: true E2E_TAURI_ENABLED: true
TS_NODE_COMPILER_OPTIONS: '{"module": "commonjs"}' 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: publish-apps-release:
permissions: permissions:
contents: write contents: write

View File

@ -83,6 +83,20 @@ jobs:
uses: Swatinem/rust-cache@v2 uses: Swatinem/rust-cache@v2
with: with:
workspaces: './src/wasm-lib' 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) - name: Build Wasm (because rust diff)
if: needs.check-rust-changes.outputs.rust-changed == 'true' if: needs.check-rust-changes.outputs.rust-changed == 'true'
run: yarn build:wasm run: yarn build:wasm
@ -139,27 +153,60 @@ jobs:
with: with:
name: test-results-ubuntu-${{ github.sha }} name: test-results-ubuntu-${{ github.sha }}
path: test-results/ path: test-results/
- name: Run ubuntu/chrome flow retry failures - name: Run ubuntu/chrome flow (with retries)
id: retry id: retry
if: always() if: always()
run: | run: |
if [[ -d "test-results" ]]; if [[ ! -f "test-results/.last-run.json" ]]; then
then if [[ $(ls -1 "test-results" | wc -l) != "0" ]]; # if no last run artifact, than run plawright normally
then echo "retried=true" >> $GITHUB_OUTPUT; echo "run playwright normally"
else echo "retried=false" >> $GITHUB_OUTPUT; exit 0; yarn playwright test --project="Google Chrome" e2e/playwright/flow-tests.spec.ts || true
fi; # # send to axiom
else echo "retried=false" >> $GITHUB_OUTPUT; exit 0; node playwrightProcess.mjs | tee /tmp/github-actions.log > /dev/null 2>&1
fi; fi
yarn playwright test --project="Google Chrome" --last-failed e2e/playwright/flow-tests.spec.ts
env: retry=1
CI: true max_retrys=4
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
- name: Run ubuntu/chrome flow # retry failed tests, doing our own retries because using inbuilt playwright retries causes connection issues
if: steps.retry.outputs.retried == 'false' while [[ $retry -le $max_retrys ]]; do
run: yarn playwright test --project="Google Chrome" e2e/playwright/flow-tests.spec.ts 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: env:
CI: true CI: true
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }} 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 - uses: actions/upload-artifact@v4
if: always() if: always()
with: with:
@ -226,6 +273,20 @@ jobs:
uses: Swatinem/rust-cache@v2 uses: Swatinem/rust-cache@v2
with: with:
workspaces: './src/wasm-lib' 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) - name: Build Wasm (because rust diff)
if: needs.check-rust-changes.outputs.rust-changed == 'true' if: needs.check-rust-changes.outputs.rust-changed == 'true'
run: yarn build:wasm run: yarn build:wasm
@ -241,26 +302,52 @@ jobs:
with: with:
name: test-results-macos-${{ github.sha }} name: test-results-macos-${{ github.sha }}
path: test-results/ path: test-results/
- name: Run macos/safari flow retry failures - name: Run macos/safari flow (with retries)
id: retry id: retry
if: always() if: always()
run: | run: |
if [[ -d "test-results" ]]; if [[ ! -f "test-results/.last-run.json" ]]; then
then if [[ $(ls -1 "test-results" | wc -l) != "0" ]]; # if no last run artifact, than run plawright normally
then echo "retried=true" >> $GITHUB_OUTPUT; echo "run playwright normally"
else echo "retried=false" >> $GITHUB_OUTPUT; exit 0; yarn playwright test --project="webkit" e2e/playwright/flow-tests.spec.ts || true
fi; # # send to axiom
else echo "retried=false" >> $GITHUB_OUTPUT; exit 0; node playwrightProcess.mjs | tee /tmp/github-actions.log > /dev/null 2>&1
fi; fi
yarn playwright test --project="webkit" --last-failed e2e/playwright/flow-tests.spec.ts
env: retry=1
CI: true max_retrys=4
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
- name: Run macos/safari flow # retry failed tests, doing our own retries because using inbuilt playwright retries causes connection issues
if: steps.retry.outputs.retried == 'false' while [[ $retry -le $max_retrys ]]; do
# webkit doesn't work on Ubuntu because of the same reason tauri doesn't (webRTC issues) if [[ -f "test-results/.last-run.json" ]]; then
# TODO remove this and the matrix and run all tests on ubuntu when this is fixed failed_tests=$(jq '.failedTests | length' test-results/.last-run.json)
run: yarn playwright test --project="webkit" e2e/playwright/flow-tests.spec.ts 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: env:
CI: true CI: true
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }} token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}

File diff suppressed because one or more lines are too long

View File

@ -7726,6 +7726,31 @@ test('Basic default modeling and sketch hotkeys work', async ({ page }) => {
await expect(page.locator('.cm-content')).toContainText('extrude(') 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('Sketch on face', async ({ page }) => {
test.setTimeout(90_000) test.setTimeout(90_000)
const u = await getUtils(page) const u = await getUtils(page)

View File

@ -1,6 +1,6 @@
{ {
"name": "untitled-app", "name": "untitled-app",
"version": "0.24.0", "version": "0.24.1",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@codemirror/autocomplete": "^6.17.0", "@codemirror/autocomplete": "^6.17.0",

View File

@ -93,23 +93,10 @@ export class LanguageServerPlugin implements PluginValue {
private doSemanticTokens: boolean = false private doSemanticTokens: boolean = false
private doFoldingRanges: boolean = false private doFoldingRanges: boolean = false
private _defferer = deferExecution((code: string) => { // When a doc update needs to be sent to the server, this holds the
try { // timeout handle for it. When null, the server has the up-to-date
// Update the state (not the editor) with the new code. // document.
this.client.textDocumentDidChange({ private sendScheduled: number | null = null
textDocument: {
uri: this.getDocUri(),
version: this.documentVersion++,
},
contentChanges: [{ text: code }],
})
this.requestSemanticTokens()
this.updateFoldingRanges()
} catch (e) {
console.error(e)
}
}, this.changesDelay)
constructor(options: LanguageServerOptions, private view: EditorView) { constructor(options: LanguageServerOptions, private view: EditorView) {
this.client = options.client this.client = options.client
@ -152,14 +139,9 @@ export class LanguageServerPlugin implements PluginValue {
} }
update(viewUpdate: ViewUpdate) { update(viewUpdate: ViewUpdate) {
// If the doc didn't change we can return early. if (viewUpdate.docChanged) {
if (!viewUpdate.docChanged) { this.scheduleSendDoc()
return
} }
this.sendChange({
documentText: viewUpdate.state.doc.toString(),
})
} }
destroy() { destroy() {
@ -184,16 +166,6 @@ export class LanguageServerPlugin implements PluginValue {
this.updateFoldingRanges() this.updateFoldingRanges()
} }
async sendChange({ documentText }: { documentText: string }) {
if (!this.client.ready) return
this._defferer(documentText)
}
requestDiagnostics() {
this.sendChange({ documentText: this.getDocText() })
}
async requestHoverTooltip( async requestHoverTooltip(
view: EditorView, view: EditorView,
{ line, character }: { line: number; character: number } { line, character }: { line: number; character: number }
@ -204,7 +176,7 @@ export class LanguageServerPlugin implements PluginValue {
) )
return null return null
this.sendChange({ documentText: this.getDocText() }) this.ensureDocSent()
const result = await this.client.textDocumentHover({ const result = await this.client.textDocumentHover({
textDocument: { uri: this.getDocUri() }, textDocument: { uri: this.getDocUri() },
position: { line, character }, position: { line, character },
@ -227,6 +199,42 @@ export class LanguageServerPlugin implements PluginValue {
return { pos, end, create: (view) => ({ dom }), above: true } 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> { async getFoldingRanges(): Promise<LSP.FoldingRange[] | null> {
if ( if (
!this.doFoldingRanges || !this.doFoldingRanges ||
@ -284,13 +292,7 @@ export class LanguageServerPlugin implements PluginValue {
) )
return null return null
this.client.textDocumentDidChange({ this.ensureDocSent()
textDocument: {
uri: this.getDocUri(),
version: this.documentVersion++,
},
contentChanges: [{ text: this.getDocText() }],
})
const result = await this.client.textDocumentFormatting({ const result = await this.client.textDocumentFormatting({
textDocument: { uri: this.getDocUri() }, textDocument: { uri: this.getDocUri() },
@ -330,9 +332,7 @@ export class LanguageServerPlugin implements PluginValue {
) )
return null return null
this.sendChange({ this.ensureDocSent()
documentText: context.state.doc.toString(),
})
const result = await this.client.textDocumentCompletion({ const result = await this.client.textDocumentCompletion({
textDocument: { uri: this.getDocUri() }, textDocument: { uri: this.getDocUri() },

View File

@ -20,7 +20,10 @@ export default defineConfig({
/* Different amount of parallelism on CI and local. */ /* Different amount of parallelism on CI and local. */
workers: process.env.CI ? 4 : 4, workers: process.env.CI ? 4 : 4,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */ /* 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. */ /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: { use: {
/* Base URL to use in actions like `await page.goto('/')`. */ /* Base URL to use in actions like `await page.goto('/')`. */

65
playwrightProcess.mjs Normal file
View 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
View File

@ -332,7 +332,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.68", "syn 2.0.70",
] ]
[[package]] [[package]]
@ -361,13 +361,13 @@ checksum = "fbb36e985947064623dbd357f727af08ffd077f93d696782f3c56365fa2e2799"
[[package]] [[package]]
name = "async-trait" name = "async-trait"
version = "0.1.80" version = "0.1.81"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.68", "syn 2.0.70",
] ]
[[package]] [[package]]
@ -407,7 +407,7 @@ checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.68", "syn 2.0.70",
] ]
[[package]] [[package]]
@ -550,7 +550,7 @@ dependencies = [
"proc-macro-crate 3.1.0", "proc-macro-crate 3.1.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.68", "syn 2.0.70",
"syn_derive", "syn_derive",
] ]
@ -792,9 +792,9 @@ dependencies = [
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.7" version = "4.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f" checksum = "64acc1846d54c1fe936a78dc189c34e28d3f5afc348403f28ecf53660b9b8462"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive", "clap_derive",
@ -802,9 +802,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.5.7" version = "4.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f" checksum = "6fb8393d67ba2e7bfaf28a23458e4e2b543cc73a99595511eb207fdb8aede942"
dependencies = [ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
@ -816,14 +816,14 @@ dependencies = [
[[package]] [[package]]
name = "clap_derive" name = "clap_derive"
version = "4.5.5" version = "4.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6" checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085"
dependencies = [ dependencies = [
"heck 0.5.0", "heck 0.5.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.68", "syn 2.0.70",
] ]
[[package]] [[package]]
@ -1073,7 +1073,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331"
dependencies = [ dependencies = [
"quote", "quote",
"syn 2.0.68", "syn 2.0.70",
] ]
[[package]] [[package]]
@ -1083,7 +1083,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f" checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f"
dependencies = [ dependencies = [
"quote", "quote",
"syn 2.0.68", "syn 2.0.70",
] ]
[[package]] [[package]]
@ -1107,7 +1107,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"strsim 0.10.0", "strsim 0.10.0",
"syn 2.0.68", "syn 2.0.70",
] ]
[[package]] [[package]]
@ -1118,7 +1118,7 @@ checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f"
dependencies = [ dependencies = [
"darling_core", "darling_core",
"quote", "quote",
"syn 2.0.68", "syn 2.0.70",
] ]
[[package]] [[package]]
@ -1179,7 +1179,7 @@ checksum = "4078275de501a61ceb9e759d37bdd3d7210e654dbc167ac1a3678ef4435ed57b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.68", "syn 2.0.70",
"synstructure", "synstructure",
] ]
@ -1216,7 +1216,7 @@ dependencies = [
"regex", "regex",
"serde", "serde",
"serde_tokenstream", "serde_tokenstream",
"syn 2.0.68", "syn 2.0.70",
] ]
[[package]] [[package]]
@ -1227,7 +1227,7 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.68", "syn 2.0.70",
] ]
[[package]] [[package]]
@ -1288,7 +1288,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.68", "syn 2.0.70",
] ]
[[package]] [[package]]
@ -1320,7 +1320,7 @@ checksum = "f2b99bf03862d7f545ebc28ddd33a665b50865f4dfd84031a393823879bd4c54"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.68", "syn 2.0.70",
] ]
[[package]] [[package]]
@ -1427,7 +1427,7 @@ checksum = "5c785274071b1b420972453b306eeca06acf4633829db4223b58a2a8c5953bc4"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.68", "syn 2.0.70",
] ]
[[package]] [[package]]
@ -1588,7 +1588,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.68", "syn 2.0.70",
] ]
[[package]] [[package]]
@ -1704,7 +1704,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.68", "syn 2.0.70",
] ]
[[package]] [[package]]
@ -1980,7 +1980,7 @@ dependencies = [
"proc-macro-error", "proc-macro-error",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.68", "syn 2.0.70",
] ]
[[package]] [[package]]
@ -2008,7 +2008,7 @@ dependencies = [
"inflections", "inflections",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.68", "syn 2.0.70",
] ]
[[package]] [[package]]
@ -2083,7 +2083,7 @@ dependencies = [
"proc-macro-error", "proc-macro-error",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.68", "syn 2.0.70",
] ]
[[package]] [[package]]
@ -2571,7 +2571,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-lib" name = "kcl-lib"
version = "0.1.70" version = "0.1.72"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"approx", "approx",
@ -3377,7 +3377,7 @@ dependencies = [
"regex", "regex",
"regex-syntax 0.8.3", "regex-syntax 0.8.3",
"structmeta", "structmeta",
"syn 2.0.68", "syn 2.0.70",
] ]
[[package]] [[package]]
@ -3496,7 +3496,7 @@ dependencies = [
"phf_shared 0.11.2", "phf_shared 0.11.2",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.68", "syn 2.0.70",
] ]
[[package]] [[package]]
@ -3564,7 +3564,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.68", "syn 2.0.70",
] ]
[[package]] [[package]]
@ -4438,7 +4438,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"serde_derive_internals", "serde_derive_internals",
"syn 2.0.68", "syn 2.0.70",
] ]
[[package]] [[package]]
@ -4523,9 +4523,9 @@ dependencies = [
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.203" version = "1.0.204"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
@ -4552,13 +4552,13 @@ dependencies = [
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.203" version = "1.0.204"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.68", "syn 2.0.70",
] ]
[[package]] [[package]]
@ -4569,7 +4569,7 @@ checksum = "330f01ce65a3a5fe59a60c82f3c9a024b573b8a6e875bd233fe5f934e71d54e3"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.68", "syn 2.0.70",
] ]
[[package]] [[package]]
@ -4602,7 +4602,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.68", "syn 2.0.70",
] ]
[[package]] [[package]]
@ -4623,7 +4623,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"serde", "serde",
"syn 2.0.68", "syn 2.0.70",
] ]
[[package]] [[package]]
@ -4665,7 +4665,7 @@ dependencies = [
"darling", "darling",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.68", "syn 2.0.70",
] ]
[[package]] [[package]]
@ -4933,7 +4933,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"structmeta-derive", "structmeta-derive",
"syn 2.0.68", "syn 2.0.70",
] ]
[[package]] [[package]]
@ -4944,7 +4944,7 @@ checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.68", "syn 2.0.70",
] ]
[[package]] [[package]]
@ -4966,7 +4966,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"rustversion", "rustversion",
"syn 2.0.68", "syn 2.0.70",
] ]
[[package]] [[package]]
@ -4999,9 +4999,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.68" version = "2.0.70"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9" checksum = "2f0209b68b3613b093e0ec905354eccaedcfe83b8cb37cbdeae64026c3064c16"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -5017,7 +5017,7 @@ dependencies = [
"proc-macro-error", "proc-macro-error",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.68", "syn 2.0.70",
] ]
[[package]] [[package]]
@ -5034,7 +5034,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.68", "syn 2.0.70",
] ]
[[package]] [[package]]
@ -5251,7 +5251,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"sha2", "sha2",
"syn 2.0.68", "syn 2.0.70",
"tauri-utils", "tauri-utils",
"thiserror", "thiserror",
"time", "time",
@ -5269,7 +5269,7 @@ dependencies = [
"heck 0.5.0", "heck 0.5.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.68", "syn 2.0.70",
"tauri-codegen", "tauri-codegen",
"tauri-utils", "tauri-utils",
] ]
@ -5642,7 +5642,7 @@ checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.68", "syn 2.0.70",
] ]
[[package]] [[package]]
@ -5740,7 +5740,7 @@ checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.68", "syn 2.0.70",
] ]
[[package]] [[package]]
@ -5940,7 +5940,7 @@ checksum = "84fd902d4e0b9a4b27f2f440108dc034e1758628a9b702f8ec61ad66355422fa"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.68", "syn 2.0.70",
] ]
[[package]] [[package]]
@ -5969,7 +5969,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.68", "syn 2.0.70",
] ]
[[package]] [[package]]
@ -6099,7 +6099,7 @@ checksum = "c88cc88fd23b5a04528f3a8436024f20010a16ec18eb23c164b1242f65860130"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.68", "syn 2.0.70",
"termcolor", "termcolor",
] ]
@ -6280,9 +6280,9 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "1.9.1" version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5de17fd2f7da591098415cff336e12965a28061ddace43b59cb3c430179c9439" checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314"
dependencies = [ dependencies = [
"getrandom 0.2.14", "getrandom 0.2.14",
"serde", "serde",
@ -6316,7 +6316,7 @@ dependencies = [
"proc-macro-error", "proc-macro-error",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.68", "syn 2.0.70",
] ]
[[package]] [[package]]
@ -6415,7 +6415,7 @@ dependencies = [
"once_cell", "once_cell",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.68", "syn 2.0.70",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@ -6449,7 +6449,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.68", "syn 2.0.70",
"wasm-bindgen-backend", "wasm-bindgen-backend",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@ -6590,7 +6590,7 @@ checksum = "ac1345798ecd8122468840bcdf1b95e5dc6d2206c5e4b0eafa078d061f59c9bc"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.68", "syn 2.0.70",
] ]
[[package]] [[package]]
@ -6696,7 +6696,7 @@ checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.68", "syn 2.0.70",
] ]
[[package]] [[package]]
@ -6707,7 +6707,7 @@ checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.68", "syn 2.0.70",
] ]
[[package]] [[package]]
@ -7159,7 +7159,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.68", "syn 2.0.70",
] ]
[[package]] [[package]]

View File

@ -80,5 +80,5 @@
} }
}, },
"productName": "Zoo Modeling App", "productName": "Zoo Modeling App",
"version": "0.24.0" "version": "0.24.1"
} }

View File

@ -39,3 +39,32 @@ export const AppStateProvider = ({ children }: { children: ReactNode }) => {
</AppStateContext.Provider> </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>
)
}

View File

@ -114,7 +114,7 @@ export function Toolbar({
() => () =>
commandBarSend({ commandBarSend({
type: 'Find and select command', type: 'Find and select command',
data: { name: 'Extrude', ownerMachine: 'modeling' }, data: { name: 'Extrude', groupId: 'modeling' },
}), }),
{ enabled: !disableAllButtons, scopes: ['modeling'] } { enabled: !disableAllButtons, scopes: ['modeling'] }
) )
@ -378,7 +378,7 @@ export function Toolbar({
onClick={() => onClick={() =>
commandBarSend({ commandBarSend({
type: 'Find and select command', type: 'Find and select command',
data: { name: 'Extrude', ownerMachine: 'modeling' }, data: { name: 'Extrude', groupId: 'modeling' },
}) })
} }
disabled={!state.can('Extrude') || disableAllButtons} disabled={!state.can('Extrude') || disableAllButtons}

View File

@ -518,9 +518,9 @@ export class CameraControls {
direction.normalize() direction.normalize()
this.camera.position.copy(this.target).addScaledVector(direction, distance) this.camera.position.copy(this.target).addScaledVector(direction, distance)
} }
usePerspectiveCamera = async () => { usePerspectiveCamera = async (forceSend = false) => {
this._usePerspectiveCamera() this._usePerspectiveCamera()
if (this.syncDirection === 'clientToEngine') { if (forceSend || this.syncDirection === 'clientToEngine') {
await this.engineCommandManager.sendSceneCommand({ await this.engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req', type: 'modeling_cmd_req',
cmd_id: uuidv4(), cmd_id: uuidv4(),

View File

@ -717,7 +717,7 @@ export const CamDebugSettings = () => {
if (camSettings.type === 'perspective') { if (camSettings.type === 'perspective') {
sceneInfra.camControls.useOrthographicCamera() sceneInfra.camControls.useOrthographicCamera()
} else { } else {
sceneInfra.camControls.usePerspectiveCamera() sceneInfra.camControls.usePerspectiveCamera(true)
} }
}} }}
/> />

View File

@ -82,11 +82,11 @@ function ProjectMenuPopover({
}) { }) {
const { commandBarState, commandBarSend } = useCommandsContext() const { commandBarState, commandBarSend } = useCommandsContext()
const { onProjectClose } = useLspContext() const { onProjectClose } = useLspContext()
const exportCommandInfo = { name: 'Export', ownerMachine: 'modeling' } const exportCommandInfo = { name: 'Export', groupId: 'modeling' }
const findCommand = (obj: { name: string; ownerMachine: string }) => const findCommand = (obj: { name: string; groupId: string }) =>
Boolean( Boolean(
commandBarState.context.commands.find( commandBarState.context.commands.find(
(c) => c.name === obj.name && c.ownerMachine === obj.ownerMachine (c) => c.name === obj.name && c.groupId === obj.groupId
) )
) )

View File

@ -6,9 +6,10 @@ import { useModelingContext } from 'hooks/useModelingContext'
import { useNetworkContext } from 'hooks/useNetworkContext' import { useNetworkContext } from 'hooks/useNetworkContext'
import { NetworkHealthState } from 'hooks/useNetworkStatus' import { NetworkHealthState } from 'hooks/useNetworkStatus'
import { ClientSideScene } from 'clientSideScene/ClientSideSceneComp' import { ClientSideScene } from 'clientSideScene/ClientSideSceneComp'
import { butName } from 'lib/cameraControls' import { btnName } from 'lib/cameraControls'
import { sendSelectEventToEngine } from 'lib/selections' import { sendSelectEventToEngine } from 'lib/selections'
import { kclManager, engineCommandManager, sceneInfra } from 'lib/singletons' import { kclManager, engineCommandManager, sceneInfra } from 'lib/singletons'
import { useAppStream } from 'AppState'
export const Stream = () => { export const Stream = () => {
const [isLoading, setIsLoading] = useState(true) const [isLoading, setIsLoading] = useState(true)
@ -17,6 +18,7 @@ export const Stream = () => {
const videoRef = useRef<HTMLVideoElement>(null) const videoRef = useRef<HTMLVideoElement>(null)
const { settings } = useSettingsAuthContext() const { settings } = useSettingsAuthContext()
const { state, send, context } = useModelingContext() const { state, send, context } = useModelingContext()
const { mediaStream } = useAppStream()
const { overallState } = useNetworkContext() const { overallState } = useNetworkContext()
const [isFreezeFrame, setIsFreezeFrame] = useState(false) const [isFreezeFrame, setIsFreezeFrame] = useState(false)
@ -124,10 +126,10 @@ export const Stream = () => {
) )
return return
if (!videoRef.current) return if (!videoRef.current) return
if (!context.store?.mediaStream) return if (!mediaStream) return
// Do not immediately play the stream! // Do not immediately play the stream!
videoRef.current.srcObject = context.store.mediaStream videoRef.current.srcObject = mediaStream
videoRef.current.pause() videoRef.current.pause()
send({ send({
@ -136,7 +138,7 @@ export const Stream = () => {
videoElement: videoRef.current, videoElement: videoRef.current,
}, },
}) })
}, [context.store?.mediaStream]) }, [mediaStream])
const handleMouseDown: MouseEventHandler<HTMLDivElement> = (e) => { const handleMouseDown: MouseEventHandler<HTMLDivElement> = (e) => {
if (!isNetworkOkay) return if (!isNetworkOkay) return
@ -172,7 +174,7 @@ export const Stream = () => {
if (state.matches('Sketch')) return if (state.matches('Sketch')) return
if (state.matches('Sketch no face')) return if (state.matches('Sketch no face')) return
if (!context.store?.didDragInStream && butName(e).left) { if (!context.store?.didDragInStream && btnName(e).left) {
sendSelectEventToEngine( sendSelectEventToEngine(
e, e,
videoRef.current, videoRef.current,

View File

@ -195,14 +195,15 @@ export class CompletionRequester implements PluginValue {
private queuedUids: string[] = [] private queuedUids: string[] = []
private _deffererCodeUpdate = deferExecution(() => {
this.requestCompletions()
}, changesDelay)
private _deffererUserSelect = deferExecution(() => { private _deffererUserSelect = deferExecution(() => {
this.rejectSuggestionCommand() this.rejectSuggestionCommand()
}, changesDelay) }, 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) { constructor(readonly view: EditorView, client: LanguageServerClient) {
this.client = client this.client = client
} }
@ -245,7 +246,34 @@ export class CompletionRequester implements PluginValue {
} }
this.lastPos = this.view.state.selection.main.head 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 { ghostText(): GhostText | null {

View File

@ -27,13 +27,10 @@ export class KclPlugin implements PluginValue {
this.client = client this.client = client
} }
private _deffererCodeUpdate = deferExecution(() => { // When a doc update needs to be sent to the server, this holds the
if (this.viewUpdate === null) { // timeout handle for it. When null, the server has the up-to-date
return // document.
} private sendScheduledInput: number | null = null
kclManager.executeCode()
}, changesDelay)
private _deffererUserSelect = deferExecution(() => { private _deffererUserSelect = deferExecution(() => {
if (this.viewUpdate === null) { if (this.viewUpdate === null) {
@ -101,7 +98,34 @@ export class KclPlugin implements PluginValue {
codeManager.code = newCode codeManager.code = newCode
codeManager.writeToFile() 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( async updateUnits(

View File

@ -4,7 +4,7 @@ import { deferExecution } from 'lib/utils'
import { Themes } from 'lib/theme' import { Themes } from 'lib/theme'
import { makeDefaultPlanes, modifyGrid } from 'lang/wasm' import { makeDefaultPlanes, modifyGrid } from 'lang/wasm'
import { useModelingContext } from './useModelingContext' import { useModelingContext } from './useModelingContext'
import { useAppState } from 'AppState' import { useAppState, useAppStream } from 'AppState'
export function useSetupEngineManager( export function useSetupEngineManager(
streamRef: React.RefObject<HTMLDivElement>, streamRef: React.RefObject<HTMLDivElement>,
@ -28,6 +28,7 @@ export function useSetupEngineManager(
} }
) { ) {
const { setAppState } = useAppState() const { setAppState } = useAppState()
const { setMediaStream } = useAppStream()
const streamWidth = streamRef?.current?.offsetWidth const streamWidth = streamRef?.current?.offsetWidth
const streamHeight = streamRef?.current?.offsetHeight const streamHeight = streamRef?.current?.offsetHeight
@ -54,11 +55,7 @@ export function useSetupEngineManager(
settings.modelingSend settings.modelingSend
) { ) {
engineCommandManager.start({ engineCommandManager.start({
setMediaStream: (mediaStream) => setMediaStream: setMediaStream,
settings.modelingSend({
type: 'Set context',
data: { mediaStream },
}),
setIsStreamReady: (isStreamReady) => setAppState({ isStreamReady }), setIsStreamReady: (isStreamReady) => setAppState({ isStreamReady }),
width: quadWidth, width: quadWidth,
height: quadHeight, height: quadHeight,

View File

@ -60,7 +60,8 @@ export default function useStateMachineCommands<
.filter((e) => !['done.', 'error.'].some((n) => e.includes(n))) .filter((e) => !['done.', 'error.'].some((n) => e.includes(n)))
.map((type) => .map((type) =>
createMachineCommand<T, S>({ createMachineCommand<T, S>({
ownerMachine: machineId, // The group is the owner machine's ID.
groupId: machineId,
type, type,
state, state,
send, send,

View File

@ -13,6 +13,7 @@ import {
UpdaterRestartModal, UpdaterRestartModal,
createUpdaterRestartModal, createUpdaterRestartModal,
} from 'components/UpdaterRestartModal' } from 'components/UpdaterRestartModal'
import { AppStreamProvider } from 'AppState'
// uncomment for xstate inspector // uncomment for xstate inspector
// import { DEV } from 'env' // import { DEV } from 'env'
@ -26,28 +27,30 @@ const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement)
root.render( root.render(
<HotkeysProvider> <HotkeysProvider>
<Router /> <AppStreamProvider>
<Toaster <Router />
position="bottom-center" <Toaster
toastOptions={{ position="bottom-center"
style: { toastOptions={{
borderRadius: '3px', 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)',
}, },
duration: className:
window?.localStorage.getItem('playwright') === 'true' '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',
? 10 // speed up e2e tests success: {
: 1500, iconTheme: {
}, primary: 'oklch(89% 0.16 143.4deg)',
}} secondary: 'oklch(48.62% 0.1654 142.5deg)',
/> },
<ModalContainer /> duration:
window?.localStorage.getItem('playwright') === 'true'
? 10 // speed up e2e tests
: 1500,
},
}}
/>
<ModalContainer />
</AppStreamProvider>
</HotkeysProvider> </HotkeysProvider>
) )

View File

@ -64,7 +64,7 @@ export interface MouseGuard {
rotate: MouseGuardHandler rotate: MouseGuardHandler
} }
export const butName = (e: React.MouseEvent) => ({ export const btnName = (e: React.MouseEvent) => ({
middle: !!(e.buttons & 4) || e.button === 1, middle: !!(e.buttons & 4) || e.button === 1,
right: !!(e.buttons & 2) || e.button === 2, right: !!(e.buttons & 2) || e.button === 2,
left: !!(e.buttons & 1) || e.button === 0, left: !!(e.buttons & 1) || e.button === 0,
@ -75,8 +75,8 @@ export const cameraMouseDragGuards: Record<CameraSystem, MouseGuard> = {
pan: { pan: {
description: 'Right click + Shift + drag or middle click + drag', description: 'Right click + Shift + drag or middle click + drag',
callback: (e) => callback: (e) =>
(butName(e).middle && noModifiersPressed(e)) || (btnName(e).middle && noModifiersPressed(e)) ||
(butName(e).right && e.shiftKey), (btnName(e).right && e.shiftKey),
}, },
zoom: { zoom: {
description: 'Scroll wheel or Right click + Ctrl + drag', description: 'Scroll wheel or Right click + Ctrl + drag',
@ -85,15 +85,15 @@ export const cameraMouseDragGuards: Record<CameraSystem, MouseGuard> = {
}, },
rotate: { rotate: {
description: 'Right click + drag', description: 'Right click + drag',
callback: (e) => butName(e).right && noModifiersPressed(e), callback: (e) => btnName(e).right && noModifiersPressed(e),
}, },
}, },
OnShape: { OnShape: {
pan: { pan: {
description: 'Right click + Ctrl + drag or middle click + drag', description: 'Right click + Ctrl + drag or middle click + drag',
callback: (e) => callback: (e) =>
(butName(e).right && e.ctrlKey) || (btnName(e).right && e.ctrlKey) ||
(butName(e).middle && noModifiersPressed(e)), (btnName(e).middle && noModifiersPressed(e)),
}, },
zoom: { zoom: {
description: 'Scroll wheel', description: 'Scroll wheel',
@ -102,72 +102,72 @@ export const cameraMouseDragGuards: Record<CameraSystem, MouseGuard> = {
}, },
rotate: { rotate: {
description: 'Right click + drag', description: 'Right click + drag',
callback: (e) => butName(e).right && noModifiersPressed(e), callback: (e) => btnName(e).right && noModifiersPressed(e),
}, },
}, },
'Trackpad Friendly': { 'Trackpad Friendly': {
pan: { pan: {
description: 'Left click + Alt + Shift + drag or middle click + drag', description: 'Left click + Alt + Shift + drag or middle click + drag',
callback: (e) => callback: (e) =>
(butName(e).left && e.altKey && e.shiftKey && !e.metaKey) || (btnName(e).left && e.altKey && e.shiftKey && !e.metaKey) ||
(butName(e).middle && noModifiersPressed(e)), (btnName(e).middle && noModifiersPressed(e)),
}, },
zoom: { zoom: {
description: 'Scroll wheel or Left click + Alt + OS + drag', 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, scrollCallback: () => true,
}, },
rotate: { rotate: {
description: 'Left click + Alt + drag', 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, lenientDragStartButton: 0,
}, },
}, },
Solidworks: { Solidworks: {
pan: { pan: {
description: 'Right click + Ctrl + drag', description: 'Right click + Ctrl + drag',
callback: (e) => butName(e).right && e.ctrlKey, callback: (e) => btnName(e).right && e.ctrlKey,
lenientDragStartButton: 2, lenientDragStartButton: 2,
}, },
zoom: { zoom: {
description: 'Scroll wheel or Middle click + Shift + drag', description: 'Scroll wheel or Middle click + Shift + drag',
dragCallback: (e) => butName(e).middle && e.shiftKey, dragCallback: (e) => btnName(e).middle && e.shiftKey,
scrollCallback: () => true, scrollCallback: () => true,
}, },
rotate: { rotate: {
description: 'Middle click + drag', description: 'Middle click + drag',
callback: (e) => butName(e).middle && noModifiersPressed(e), callback: (e) => btnName(e).middle && noModifiersPressed(e),
}, },
}, },
NX: { NX: {
pan: { pan: {
description: 'Middle click + Shift + drag', description: 'Middle click + Shift + drag',
callback: (e) => butName(e).middle && e.shiftKey, callback: (e) => btnName(e).middle && e.shiftKey,
}, },
zoom: { zoom: {
description: 'Scroll wheel or Middle click + Ctrl + drag', description: 'Scroll wheel or Middle click + Ctrl + drag',
dragCallback: (e) => butName(e).middle && e.ctrlKey, dragCallback: (e) => btnName(e).middle && e.ctrlKey,
scrollCallback: () => true, scrollCallback: () => true,
}, },
rotate: { rotate: {
description: 'Middle click + drag', description: 'Middle click + drag',
callback: (e) => butName(e).middle && noModifiersPressed(e), callback: (e) => btnName(e).middle && noModifiersPressed(e),
}, },
}, },
Creo: { Creo: {
pan: { pan: {
description: 'Left click + Ctrl + drag', 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: { zoom: {
description: 'Scroll wheel or Right click + Ctrl + drag', 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, scrollCallback: () => true,
}, },
rotate: { rotate: {
description: 'Middle (or Left + Right) click + Ctrl + drag', description: 'Middle (or Left + Right) click + Ctrl + drag',
callback: (e) => { callback: (e) => {
const b = butName(e) const b = btnName(e)
return (b.middle || (b.left && b.right)) && e.ctrlKey return (b.middle || (b.left && b.right)) && e.ctrlKey
}, },
}, },
@ -175,7 +175,7 @@ export const cameraMouseDragGuards: Record<CameraSystem, MouseGuard> = {
AutoCAD: { AutoCAD: {
pan: { pan: {
description: 'Middle click + drag', description: 'Middle click + drag',
callback: (e) => butName(e).middle && noModifiersPressed(e), callback: (e) => btnName(e).middle && noModifiersPressed(e),
}, },
zoom: { zoom: {
description: 'Scroll wheel', description: 'Scroll wheel',
@ -184,7 +184,7 @@ export const cameraMouseDragGuards: Record<CameraSystem, MouseGuard> = {
}, },
rotate: { rotate: {
description: 'Middle click + Shift + drag', description: 'Middle click + Shift + drag',
callback: (e) => butName(e).middle && e.shiftKey, callback: (e) => btnName(e).middle && e.shiftKey,
}, },
}, },
} }

View File

@ -124,7 +124,7 @@ export function createSettingsCommand({
displayName: `Settings · ${decamelize(type.replaceAll('.', ' · '), { displayName: `Settings · ${decamelize(type.replaceAll('.', ' · '), {
separator: ' ', separator: ' ',
})}`, })}`,
ownerMachine: 'settings', groupId: 'settings',
icon: 'settings', icon: 'settings',
needsReview: false, needsReview: false,
onSubmit: (data) => { onSubmit: (data) => {

View File

@ -65,7 +65,7 @@ export type Command<
CommandSchema extends CommandSetSchema<T>[CommandName] = CommandSetSchema<T>[CommandName] CommandSchema extends CommandSetSchema<T>[CommandName] = CommandSetSchema<T>[CommandName]
> = { > = {
name: CommandName name: CommandName
ownerMachine: T['id'] groupId: T['id']
needsReview: boolean needsReview: boolean
onSubmit: (data?: CommandSchema) => void onSubmit: (data?: CommandSchema) => void
onCancel?: () => void onCancel?: () => void
@ -84,7 +84,7 @@ export type CommandConfig<
CommandSchema extends CommandSetSchema<T>[CommandName] = CommandSetSchema<T>[CommandName] CommandSchema extends CommandSetSchema<T>[CommandName] = CommandSetSchema<T>[CommandName]
> = Omit< > = Omit<
Command<T, CommandName, CommandSchema>, Command<T, CommandName, CommandSchema>,
'name' | 'ownerMachine' | 'onSubmit' | 'onCancel' | 'args' | 'needsReview' 'name' | 'groupId' | 'onSubmit' | 'onCancel' | 'args' | 'needsReview'
> & { > & {
needsReview?: true needsReview?: true
args?: { args?: {

View File

@ -20,7 +20,7 @@ interface CreateMachineCommandProps<
S extends CommandSetSchema<T> S extends CommandSetSchema<T>
> { > {
type: EventFrom<T>['type'] type: EventFrom<T>['type']
ownerMachine: T['id'] groupId: T['id']
state: StateFrom<T> state: StateFrom<T>
send: Function send: Function
actor: InterpreterFrom<T> actor: InterpreterFrom<T>
@ -34,7 +34,7 @@ export function createMachineCommand<
T extends AnyStateMachine, T extends AnyStateMachine,
S extends CommandSetSchema<T> S extends CommandSetSchema<T>
>({ >({
ownerMachine, groupId,
type, type,
state, state,
send, send,
@ -62,7 +62,7 @@ export function createMachineCommand<
const command: Command<T, typeof type, S[typeof type]> = { const command: Command<T, typeof type, S[typeof type]> = {
name: type, name: type,
ownerMachine: ownerMachine, groupId,
icon, icon,
needsReview: commandConfig.needsReview || false, needsReview: commandConfig.needsReview || false,
onSubmit: (data?: S[typeof type]) => { onSubmit: (data?: S[typeof type]) => {

View File

@ -57,7 +57,7 @@ export type CommandBarMachineEvent =
} }
| { | {
type: 'Find and select command' type: 'Find and select command'
data: { name: string; ownerMachine: string } data: { name: string; groupId: string }
} }
| { | {
type: 'Change current argument' type: 'Change current argument'
@ -120,9 +120,7 @@ export const commandBarMachine = createMachine(
context.commands.filter( context.commands.filter(
(c) => (c) =>
!event.data.commands.some( !event.data.commands.some(
(c2) => (c2) => c2.name === c.name && c2.groupId === c.groupId
c2.name === c.name &&
c2.ownerMachine === c.ownerMachine
) )
), ),
}), }),
@ -393,9 +391,7 @@ export const commandBarMachine = createMachine(
selectedCommand: (c, e) => { selectedCommand: (c, e) => {
if (e.type !== 'Find and select command') return c.selectedCommand if (e.type !== 'Find and select command') return c.selectedCommand
const found = c.commands.find( const found = c.commands.find(
(cmd) => (cmd) => cmd.name === e.data.name && cmd.groupId === e.data.groupId
cmd.name === e.data.name &&
cmd.ownerMachine === e.data.ownerMachine
) )
return !!found ? found : c.selectedCommand return !!found ? found : c.selectedCommand
@ -514,7 +510,7 @@ export const commandBarMachine = createMachine(
) )
function sortCommands(a: Command, b: Command) { function sortCommands(a: Command, b: Command) {
if (b.ownerMachine === 'auth') return -1 if (b.groupId === 'auth') return -1
if (a.ownerMachine === 'auth') return 1 if (a.groupId === 'auth') return 1
return a.name.localeCompare(b.name) return a.name.localeCompare(b.name)
} }

View File

@ -138,7 +138,6 @@ export type SegmentOverlayPayload =
} }
interface Store { interface Store {
mediaStream?: MediaStream
videoElement?: HTMLVideoElement videoElement?: HTMLVideoElement
buttonDownInStream: number | undefined buttonDownInStream: number | undefined
didDragInStream: boolean didDragInStream: boolean

View File

@ -57,6 +57,9 @@ const Home = () => {
kclManager.cancelAllExecutions() kclManager.cancelAllExecutions()
}, []) }, [])
useHotkeys('backspace', (e) => {
e.preventDefault()
})
useHotkeys( useHotkeys(
isTauri() ? 'mod+,' : 'shift+mod+,', isTauri() ? 'mod+,' : 'shift+mod+,',
() => navigate(paths.HOME + paths.SETTINGS), () => navigate(paths.HOME + paths.SETTINGS),

Binary file not shown.

Before

Width:  |  Height:  |  Size: 333 KiB

After

Width:  |  Height:  |  Size: 333 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 147 KiB

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 333 KiB

After

Width:  |  Height:  |  Size: 333 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 KiB

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

After

Width:  |  Height:  |  Size: 117 KiB