Compare commits
38 Commits
Author | SHA1 | Date | |
---|---|---|---|
0e8d0083c4 | |||
4f4167b247 | |||
fbc2e9d02c | |||
33b15e818b | |||
6cebb84ae0 | |||
85403e47e4 | |||
0dfee64e3b | |||
6370d45f94 | |||
fb3e922180 | |||
1257ec0327 | |||
08e9fe2e52 | |||
7cec1d45fe | |||
93710bc8f2 | |||
87e7e9447f | |||
8be113d284 | |||
7cfc927d5c | |||
c0f04d5f86 | |||
3dbc701f26 | |||
16e7ae38e3 | |||
24c7260327 | |||
72cfc4a471 | |||
2d128ed32e | |||
cd6749ba02 | |||
7243405e1b | |||
c8da057ec2 | |||
220fe5b2b8 | |||
4e6429de49 | |||
5391a65b18 | |||
592628917a | |||
4c6e8633f7 | |||
c5150468a2 | |||
39126dbff1 | |||
f86a69f12a | |||
de354ee5d3 | |||
dfef7338ee | |||
ee08948f54 | |||
832f6b65e2 | |||
68efd77c5d |
3
.github/workflows/ci.yml
vendored
@ -138,6 +138,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
if: github.event_name == 'schedule'
|
||||
|
||||
- name: Copy updated .json files
|
||||
if: github.event_name == 'schedule'
|
||||
@ -377,6 +378,8 @@ jobs:
|
||||
TS_NODE_COMPILER_OPTIONS: '{"module": "commonjs"}'
|
||||
|
||||
publish-apps-release:
|
||||
permissions:
|
||||
contents: write
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event_name == 'release' || github.event_name == 'schedule' }}
|
||||
needs: [check-format, check-types, check-typos, build-test-web, prepare-json-files, build-test-apps]
|
||||
|
26
README.md
@ -124,36 +124,20 @@ Before you submit a contribution PR to this repo, please ensure that:
|
||||
|
||||
## Release a new version
|
||||
|
||||
1. Bump the versions in the .json files by creating a `Cut release v{x}.{y}.{z}` PR, committing the changes from
|
||||
1. Bump the versions by running `./make-realease.sh` while on a fresh pull of main
|
||||
|
||||
```bash
|
||||
VERSION=x.y.z yarn run bump-jsons
|
||||
```
|
||||
|
||||
Alternatively you can try the experimental `make-release.sh` bash script that will create the branch with the updated json files for you.
|
||||
That will create the branch with the updated json files for you.
|
||||
run `./make-release.sh` for a patch update
|
||||
run `./make-release.sh "minor"` for minor
|
||||
run `./make-release.sh "major"` for major
|
||||
|
||||
The PR may serve as a place to discuss the human-readable changelog and extra QA. A quick way of getting PR's merged since the last bump is to [use this PR filter](https://github.com/KittyCAD/modeling-app/pulls?q=is%3Apr+sort%3Aupdated-desc+is%3Amerged+), open up the browser console and paste in the following
|
||||
After it runs you should just need to push the push the branch and open a PR (it will suggest a changelog for you too, delete any that are not user facing)
|
||||
|
||||
```typescript
|
||||
console.log(
|
||||
'- ' +
|
||||
Array.from(
|
||||
document.querySelectorAll('[data-hovercard-type="pull_request"]')
|
||||
).map((a) => `[${a.innerText}](${a.href})`).join(`
|
||||
- `)
|
||||
)
|
||||
```
|
||||
|
||||
grab the md list and delete any that are older than the last bump
|
||||
The PR may serve as a place to discuss the human-readable changelog and extra QA.
|
||||
|
||||
2. Merge the PR
|
||||
|
||||
3. Create a new release and tag pointing to the bump version commit using semantic versioning `v{x}.{y}.{z}`
|
||||
|
||||
4. A new Action kicks in at https://github.com/KittyCAD/modeling-app/actions, uploading artifacts to the release
|
||||
3. Profit (A new Action kicks in at https://github.com/KittyCAD/modeling-app/actions if the PR was correctly named)
|
||||
|
||||
## Fuzzing the parser
|
||||
|
||||
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 70 KiB |
@ -232,6 +232,7 @@ export async function getUtils(page: Page) {
|
||||
|
||||
return {
|
||||
waitForAuthSkipAppStart: () => waitForAuthAndLsp(page),
|
||||
waitForPageLoad: () => waitForPageLoad(page),
|
||||
removeCurrentCode: () => removeCurrentCode(page),
|
||||
sendCustomCmd: (cmd: EngineCommand) => sendCustomCmd(page, cmd),
|
||||
updateCamPosition: async (xyz: [number, number, number]) => {
|
||||
|
33
package.json
@ -1,14 +1,15 @@
|
||||
{
|
||||
"name": "untitled-app",
|
||||
"version": "0.22.7",
|
||||
"version": "0.23.1",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.16.3",
|
||||
"@codemirror/autocomplete": "^6.17.0",
|
||||
"@codemirror/commands": "^6.6.0",
|
||||
"@codemirror/language": "^6.10.2",
|
||||
"@codemirror/lint": "^6.8.1",
|
||||
"@codemirror/search": "^6.5.6",
|
||||
"@codemirror/state": "^6.4.1",
|
||||
"@codemirror/theme-one-dark": "^6.1.2",
|
||||
"@csstools/postcss-oklab-function": "^3.0.16",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.5.2",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.5.2",
|
||||
@ -19,23 +20,22 @@
|
||||
"@kittycad/lib": "^0.0.69",
|
||||
"@react-hook/resize-observer": "^2.0.1",
|
||||
"@replit/codemirror-interact": "^6.3.1",
|
||||
"@tauri-apps/api": "2.0.0-beta.12",
|
||||
"@tauri-apps/plugin-dialog": "^2.0.0-beta.2",
|
||||
"@tauri-apps/plugin-fs": "^2.0.0-beta.5",
|
||||
"@tauri-apps/plugin-http": "^2.0.0-beta.2",
|
||||
"@tauri-apps/plugin-os": "^2.0.0-beta.3",
|
||||
"@tauri-apps/plugin-process": "^2.0.0-beta.2",
|
||||
"@tauri-apps/plugin-shell": "^2.0.0-beta.2",
|
||||
"@tauri-apps/plugin-updater": "^2.0.0-beta.3",
|
||||
"@tauri-apps/api": "^2.0.0-beta.14",
|
||||
"@tauri-apps/plugin-dialog": "^2.0.0-beta.6",
|
||||
"@tauri-apps/plugin-fs": "^2.0.0-beta.6",
|
||||
"@tauri-apps/plugin-http": "^2.0.0-beta.7",
|
||||
"@tauri-apps/plugin-os": "^2.0.0-beta.6",
|
||||
"@tauri-apps/plugin-process": "^2.0.0-beta.6",
|
||||
"@tauri-apps/plugin-shell": "^2.0.0-beta.7",
|
||||
"@tauri-apps/plugin-updater": "^2.0.0-beta.6",
|
||||
"@ts-stack/markdown": "^1.5.0",
|
||||
"@tweenjs/tween.js": "^23.1.1",
|
||||
"@uiw/react-codemirror": "^4.21.25",
|
||||
"@xstate/inspect": "^0.8.0",
|
||||
"@xstate/react": "^3.2.2",
|
||||
"codemirror": "^6.0.1",
|
||||
"decamelize": "^6.0.0",
|
||||
"fuse.js": "^7.0.0",
|
||||
"html2canvas-pro": "^1.5.0",
|
||||
"html2canvas-pro": "^1.5.2",
|
||||
"json-rpc-2.0": "^1.6.0",
|
||||
"jszip": "^3.10.1",
|
||||
"re-resizable": "^6.9.11",
|
||||
@ -48,7 +48,7 @@
|
||||
"react-modal-promise": "^1.0.2",
|
||||
"react-router-dom": "^6.23.1",
|
||||
"sketch-helpers": "^0.0.4",
|
||||
"three": "^0.164.1",
|
||||
"three": "^0.166.1",
|
||||
"typescript": "^5.4.5",
|
||||
"ua-parser-js": "^1.0.37",
|
||||
"uuid": "^9.0.1",
|
||||
@ -56,8 +56,7 @@
|
||||
"vscode-languageserver-protocol": "^3.17.5",
|
||||
"vscode-uri": "^3.0.8",
|
||||
"web-vitals": "^3.5.2",
|
||||
"xstate": "^4.38.2",
|
||||
"zustand": "^4.5.2"
|
||||
"xstate": "^4.38.2"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "vite",
|
||||
@ -110,7 +109,7 @@
|
||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||
"@babel/preset-env": "^7.24.3",
|
||||
"@iarna/toml": "^2.2.5",
|
||||
"@playwright/test": "^1.44.1",
|
||||
"@playwright/test": "^1.45.1",
|
||||
"@tauri-apps/cli": "==2.0.0-beta.13",
|
||||
"@testing-library/jest-dom": "^5.14.1",
|
||||
"@testing-library/react": "^15.0.2",
|
||||
@ -158,7 +157,7 @@
|
||||
"vitest": "^1.6.0",
|
||||
"vitest-webgl-canvas-mock": "^1.1.0",
|
||||
"wait-on": "^7.2.0",
|
||||
"wasm-pack": "^0.12.1",
|
||||
"wasm-pack": "^0.13.0",
|
||||
"ws": "^8.17.0",
|
||||
"yarn": "^1.22.22"
|
||||
}
|
||||
|
@ -1,10 +1,7 @@
|
||||
import { autocompletion } from '@codemirror/autocomplete'
|
||||
import { foldService, syntaxTree } from '@codemirror/language'
|
||||
import { foldService } from '@codemirror/language'
|
||||
import { Extension, EditorState } from '@codemirror/state'
|
||||
import { ViewPlugin } from '@codemirror/view'
|
||||
|
||||
import { CompletionTriggerKind } from 'vscode-languageserver-protocol'
|
||||
|
||||
import {
|
||||
docPathFacet,
|
||||
LanguageServerPlugin,
|
||||
@ -13,7 +10,6 @@ import {
|
||||
workspaceFolders,
|
||||
LanguageServerOptions,
|
||||
} from './plugin/lsp'
|
||||
import { offsetToPos } from './plugin/util'
|
||||
|
||||
export type { LanguageServerClientOptions } from './client'
|
||||
export { LanguageServerClient } from './client'
|
||||
@ -24,14 +20,15 @@ export {
|
||||
LspWorkerEventType,
|
||||
} from './client/codec'
|
||||
export type { LanguageServerOptions } from './plugin/lsp'
|
||||
export type { TransactionInfo, RelevantUpdate } from './plugin/annotations'
|
||||
export { updateInfo, TransactionAnnotation } from './plugin/annotations'
|
||||
export {
|
||||
LanguageServerPlugin,
|
||||
LanguageServerPluginSpec,
|
||||
docPathFacet,
|
||||
languageId,
|
||||
workspaceFolders,
|
||||
lspSemanticTokensEvent,
|
||||
lspDiagnosticsEvent,
|
||||
lspFormatCodeEvent,
|
||||
} from './plugin/lsp'
|
||||
export { posToOffset, offsetToPos } from './plugin/util'
|
||||
|
||||
@ -51,63 +48,10 @@ export function lspPlugin(options: LanguageServerOptions): Extension {
|
||||
if (plugin == null) return null
|
||||
// Get the folding ranges from the language server.
|
||||
// Since this is async we directly need to update the folding ranges after.
|
||||
return plugin?.foldingRange(lineStart, lineEnd)
|
||||
const range = plugin?.foldingRange(lineStart, lineEnd)
|
||||
return range
|
||||
}),
|
||||
]
|
||||
|
||||
if (options.client.getServerCapabilities().completionProvider) {
|
||||
ext.push(
|
||||
autocompletion({
|
||||
defaultKeymap: false,
|
||||
override: [
|
||||
async (context) => {
|
||||
if (plugin === null) {
|
||||
return null
|
||||
}
|
||||
|
||||
const { state, pos, explicit } = context
|
||||
|
||||
let nodeBefore = syntaxTree(state).resolveInner(pos, -1)
|
||||
if (
|
||||
nodeBefore.name === 'BlockComment' ||
|
||||
nodeBefore.name === 'LineComment'
|
||||
)
|
||||
return null
|
||||
|
||||
const line = state.doc.lineAt(pos)
|
||||
let trigKind: CompletionTriggerKind = CompletionTriggerKind.Invoked
|
||||
let trigChar: string | undefined
|
||||
if (
|
||||
!explicit &&
|
||||
plugin.client
|
||||
.getServerCapabilities()
|
||||
.completionProvider?.triggerCharacters?.includes(
|
||||
line.text[pos - line.from - 1]
|
||||
)
|
||||
) {
|
||||
trigKind = CompletionTriggerKind.TriggerCharacter
|
||||
trigChar = line.text[pos - line.from - 1]
|
||||
}
|
||||
if (
|
||||
trigKind === CompletionTriggerKind.Invoked &&
|
||||
!context.matchBefore(/\w+$/)
|
||||
) {
|
||||
return null
|
||||
}
|
||||
|
||||
return await plugin.requestCompletion(
|
||||
context,
|
||||
offsetToPos(state.doc, pos),
|
||||
{
|
||||
triggerKind: trigKind,
|
||||
triggerCharacter: trigChar,
|
||||
}
|
||||
)
|
||||
},
|
||||
],
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
return ext
|
||||
}
|
||||
|
@ -1,131 +0,0 @@
|
||||
import { hasNextSnippetField, pickedCompletion } from '@codemirror/autocomplete'
|
||||
import { Annotation, Transaction } from '@codemirror/state'
|
||||
import type { ViewUpdate } from '@codemirror/view'
|
||||
|
||||
export enum LspAnnotation {
|
||||
SemanticTokens = 'semantic-tokens',
|
||||
FormatCode = 'format-code',
|
||||
Diagnostics = 'diagnostics',
|
||||
}
|
||||
|
||||
const lspEvent = Annotation.define<LspAnnotation>()
|
||||
export const lspSemanticTokensEvent = lspEvent.of(LspAnnotation.SemanticTokens)
|
||||
export const lspFormatCodeEvent = lspEvent.of(LspAnnotation.FormatCode)
|
||||
export const lspDiagnosticsEvent = lspEvent.of(LspAnnotation.Diagnostics)
|
||||
|
||||
export enum TransactionAnnotation {
|
||||
Remote = 'remote',
|
||||
UserSelect = 'user.select',
|
||||
UserInput = 'user.input',
|
||||
UserMove = 'user.move',
|
||||
UserDelete = 'user.delete',
|
||||
UserUndo = 'user.undo',
|
||||
UserRedo = 'user.redo',
|
||||
|
||||
SemanticTokens = 'SemanticTokens',
|
||||
FormatCode = 'FormatCode',
|
||||
Diagnostics = 'Diagnostics',
|
||||
|
||||
PickedCompletion = 'PickedCompletion',
|
||||
}
|
||||
|
||||
export interface TransactionInfo {
|
||||
annotations: TransactionAnnotation[]
|
||||
time: number | null
|
||||
docChanged: boolean
|
||||
addToHistory: boolean
|
||||
inSnippet: boolean
|
||||
transaction: Transaction
|
||||
}
|
||||
|
||||
export const updateInfo = (update: ViewUpdate): TransactionInfo[] => {
|
||||
let transactionInfos: TransactionInfo[] = []
|
||||
|
||||
for (const tr of update.transactions) {
|
||||
let annotations: TransactionAnnotation[] = []
|
||||
|
||||
if (tr.isUserEvent('select')) {
|
||||
annotations.push(TransactionAnnotation.UserSelect)
|
||||
}
|
||||
|
||||
if (tr.isUserEvent('input')) {
|
||||
annotations.push(TransactionAnnotation.UserInput)
|
||||
}
|
||||
if (tr.isUserEvent('delete')) {
|
||||
annotations.push(TransactionAnnotation.UserDelete)
|
||||
}
|
||||
if (tr.isUserEvent('undo')) {
|
||||
annotations.push(TransactionAnnotation.UserUndo)
|
||||
}
|
||||
if (tr.isUserEvent('redo')) {
|
||||
annotations.push(TransactionAnnotation.UserRedo)
|
||||
}
|
||||
if (tr.isUserEvent('move')) {
|
||||
annotations.push(TransactionAnnotation.UserMove)
|
||||
}
|
||||
|
||||
if (tr.annotation(pickedCompletion) !== undefined) {
|
||||
annotations.push(TransactionAnnotation.PickedCompletion)
|
||||
}
|
||||
|
||||
if (tr.annotation(lspSemanticTokensEvent.type) !== undefined) {
|
||||
annotations.push(TransactionAnnotation.SemanticTokens)
|
||||
}
|
||||
|
||||
if (tr.annotation(lspFormatCodeEvent.type) !== undefined) {
|
||||
annotations.push(TransactionAnnotation.FormatCode)
|
||||
}
|
||||
|
||||
if (tr.annotation(lspDiagnosticsEvent.type) !== undefined) {
|
||||
annotations.push(TransactionAnnotation.Diagnostics)
|
||||
}
|
||||
|
||||
if (tr.annotation(Transaction.remote) !== undefined) {
|
||||
annotations.push(TransactionAnnotation.Remote)
|
||||
}
|
||||
|
||||
transactionInfos.push({
|
||||
annotations,
|
||||
time: tr.annotation(Transaction.time) || null,
|
||||
docChanged: tr.docChanged,
|
||||
addToHistory: tr.annotation(Transaction.addToHistory) || false,
|
||||
inSnippet: hasNextSnippetField(update.state),
|
||||
transaction: tr,
|
||||
})
|
||||
}
|
||||
|
||||
return transactionInfos
|
||||
}
|
||||
|
||||
export interface RelevantUpdate {
|
||||
overall: boolean
|
||||
userSelect: boolean
|
||||
time: number | null
|
||||
}
|
||||
|
||||
export const relevantUpdate = (update: ViewUpdate): RelevantUpdate => {
|
||||
const infos = updateInfo(update)
|
||||
// Make sure we are not in a snippet
|
||||
if (infos.some((info) => info.inSnippet)) {
|
||||
return {
|
||||
overall: false,
|
||||
userSelect: false,
|
||||
time: null,
|
||||
}
|
||||
}
|
||||
return {
|
||||
overall: infos.some(
|
||||
(info) =>
|
||||
info.docChanged ||
|
||||
info.annotations.includes(TransactionAnnotation.UserInput) ||
|
||||
info.annotations.includes(TransactionAnnotation.UserDelete) ||
|
||||
info.annotations.includes(TransactionAnnotation.UserUndo) ||
|
||||
info.annotations.includes(TransactionAnnotation.UserRedo) ||
|
||||
info.annotations.includes(TransactionAnnotation.UserMove)
|
||||
),
|
||||
userSelect: infos.some((info) =>
|
||||
info.annotations.includes(TransactionAnnotation.UserSelect)
|
||||
),
|
||||
time: infos.length ? infos[0].time : null,
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
import {
|
||||
acceptCompletion,
|
||||
autocompletion,
|
||||
clearSnippet,
|
||||
closeCompletion,
|
||||
hasNextSnippetField,
|
||||
@ -8,10 +9,17 @@ import {
|
||||
prevSnippetField,
|
||||
startCompletion,
|
||||
} from '@codemirror/autocomplete'
|
||||
import { Prec } from '@codemirror/state'
|
||||
import { EditorView, keymap, KeyBinding } from '@codemirror/view'
|
||||
import { Prec, Extension } from '@codemirror/state'
|
||||
import { EditorView, keymap, KeyBinding, ViewPlugin } from '@codemirror/view'
|
||||
|
||||
import { CompletionItemKind } from 'vscode-languageserver-protocol'
|
||||
import {
|
||||
CompletionItemKind,
|
||||
CompletionTriggerKind,
|
||||
} from 'vscode-languageserver-protocol'
|
||||
|
||||
import { LanguageServerPlugin } from './lsp'
|
||||
import { offsetToPos } from './util'
|
||||
import { syntaxTree } from '@codemirror/language'
|
||||
|
||||
export const CompletionItemKindMap = Object.fromEntries(
|
||||
Object.entries(CompletionItemKind).map(([key, value]) => [value, key])
|
||||
@ -46,6 +54,59 @@ const lspAutocompleteKeymap: readonly KeyBinding[] = [
|
||||
},
|
||||
]
|
||||
|
||||
export const lspAutocompleteKeymapExt = Prec.highest(
|
||||
keymap.computeN([], () => [lspAutocompleteKeymap])
|
||||
)
|
||||
const lspAutocompleteKeymapExt = Prec.highest(keymap.of(lspAutocompleteKeymap))
|
||||
|
||||
export default function lspAutocompleteExt(
|
||||
plugin: ViewPlugin<LanguageServerPlugin>
|
||||
): Extension {
|
||||
return [
|
||||
lspAutocompleteKeymapExt,
|
||||
autocompletion({
|
||||
defaultKeymap: false,
|
||||
override: [
|
||||
async (context) => {
|
||||
const { state, pos, explicit, view } = context
|
||||
let value = view?.plugin(plugin)
|
||||
if (!value) return null
|
||||
|
||||
let nodeBefore = syntaxTree(state).resolveInner(pos, -1)
|
||||
if (
|
||||
nodeBefore.name === 'BlockComment' ||
|
||||
nodeBefore.name === 'LineComment'
|
||||
)
|
||||
return null
|
||||
|
||||
const line = state.doc.lineAt(pos)
|
||||
let trigKind: CompletionTriggerKind = CompletionTriggerKind.Invoked
|
||||
let trigChar: string | undefined
|
||||
if (
|
||||
!explicit &&
|
||||
value.client
|
||||
.getServerCapabilities()
|
||||
.completionProvider?.triggerCharacters?.includes(
|
||||
line.text[pos - line.from - 1]
|
||||
)
|
||||
) {
|
||||
trigKind = CompletionTriggerKind.TriggerCharacter
|
||||
trigChar = line.text[pos - line.from - 1]
|
||||
}
|
||||
if (
|
||||
trigKind === CompletionTriggerKind.Invoked &&
|
||||
!context.matchBefore(/\w+$/)
|
||||
) {
|
||||
return null
|
||||
}
|
||||
|
||||
return await value.requestCompletion(
|
||||
context,
|
||||
offsetToPos(state.doc, pos),
|
||||
{
|
||||
triggerKind: trigKind,
|
||||
triggerCharacter: trigChar,
|
||||
}
|
||||
)
|
||||
},
|
||||
],
|
||||
}),
|
||||
]
|
||||
}
|
||||
|
@ -4,7 +4,13 @@ import type {
|
||||
CompletionResult,
|
||||
} from '@codemirror/autocomplete'
|
||||
import { completeFromList, snippetCompletion } from '@codemirror/autocomplete'
|
||||
import { Facet, StateEffect, Extension, Transaction } from '@codemirror/state'
|
||||
import {
|
||||
Facet,
|
||||
StateEffect,
|
||||
Extension,
|
||||
Transaction,
|
||||
Annotation,
|
||||
} from '@codemirror/state'
|
||||
import type {
|
||||
ViewUpdate,
|
||||
PluginValue,
|
||||
@ -22,15 +28,10 @@ import {
|
||||
import { URI } from 'vscode-uri'
|
||||
|
||||
import { LanguageServerClient } from '../client'
|
||||
import {
|
||||
lspSemanticTokensEvent,
|
||||
lspFormatCodeEvent,
|
||||
relevantUpdate,
|
||||
} from './annotations'
|
||||
import { CompletionItemKindMap } from './autocomplete'
|
||||
import { addToken, SemanticToken } from './semantic-tokens'
|
||||
import { deferExecution, posToOffset, formatMarkdownContents } from './util'
|
||||
import { lspAutocompleteKeymapExt } from './autocomplete'
|
||||
import lspAutocompleteExt from './autocomplete'
|
||||
import lspHoverExt from './hover'
|
||||
import lspFormatExt from './format'
|
||||
import lspIndentExt from './indent'
|
||||
@ -47,6 +48,17 @@ export const workspaceFolders = Facet.define<
|
||||
LSP.WorkspaceFolder[]
|
||||
>({ combine: useLast })
|
||||
|
||||
export enum LspAnnotation {
|
||||
SemanticTokens = 'semantic-tokens',
|
||||
FormatCode = 'format-code',
|
||||
Diagnostics = 'diagnostics',
|
||||
}
|
||||
|
||||
const lspEvent = Annotation.define<LspAnnotation>()
|
||||
export const lspSemanticTokensEvent = lspEvent.of(LspAnnotation.SemanticTokens)
|
||||
export const lspFormatCodeEvent = lspEvent.of(LspAnnotation.FormatCode)
|
||||
export const lspDiagnosticsEvent = lspEvent.of(LspAnnotation.Diagnostics)
|
||||
|
||||
export interface LanguageServerOptions {
|
||||
// We assume this is the main project directory, we are currently working in.
|
||||
workspaceFolders: LSP.WorkspaceFolder[]
|
||||
@ -131,11 +143,6 @@ export class LanguageServerPlugin implements PluginValue {
|
||||
}
|
||||
|
||||
update(viewUpdate: ViewUpdate) {
|
||||
const isRelevant = relevantUpdate(viewUpdate)
|
||||
if (!isRelevant.overall) {
|
||||
return
|
||||
}
|
||||
|
||||
// If the doc didn't change we can return early.
|
||||
if (!viewUpdate.docChanged) {
|
||||
return
|
||||
@ -284,19 +291,16 @@ export class LanguageServerPlugin implements PluginValue {
|
||||
},
|
||||
})
|
||||
|
||||
if (!result) return null
|
||||
if (!result || !result.length) return null
|
||||
|
||||
for (let i = 0; i < result.length; i++) {
|
||||
const { range, newText } = result[i]
|
||||
this.view.dispatch({
|
||||
changes: {
|
||||
from: posToOffset(this.view.state.doc, range.start)!,
|
||||
to: posToOffset(this.view.state.doc, range.end)!,
|
||||
insert: newText,
|
||||
},
|
||||
annotations: [lspFormatCodeEvent, Transaction.addToHistory.of(true)],
|
||||
})
|
||||
}
|
||||
this.view.dispatch({
|
||||
changes: result.map(({ range, newText }) => ({
|
||||
from: posToOffset(this.view.state.doc, range.start)!,
|
||||
to: posToOffset(this.view.state.doc, range.end)!,
|
||||
insert: newText,
|
||||
})),
|
||||
annotations: lspFormatCodeEvent,
|
||||
})
|
||||
}
|
||||
|
||||
async requestCompletion(
|
||||
@ -552,7 +556,7 @@ export class LanguageServerPluginSpec
|
||||
{
|
||||
provide(plugin: ViewPlugin<LanguageServerPlugin>): Extension {
|
||||
return [
|
||||
lspAutocompleteKeymapExt,
|
||||
lspAutocompleteExt(plugin),
|
||||
lspFormatExt(plugin),
|
||||
lspHoverExt(plugin),
|
||||
lspIndentExt(),
|
||||
|
@ -4,7 +4,7 @@ import { EditorView, Decoration, DecorationSet } from '@codemirror/view'
|
||||
|
||||
import { Tag, tags } from '@lezer/highlight'
|
||||
|
||||
import { lspSemanticTokensEvent } from './annotations'
|
||||
import { lspSemanticTokensEvent } from './lsp'
|
||||
|
||||
export interface SemanticToken {
|
||||
from: number
|
||||
|
535
src-tauri/Cargo.lock
generated
@ -11,7 +11,7 @@ rust-version = "1.70"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "2.0.0-beta.13", features = [] }
|
||||
tauri-build = { version = "2.0.0-beta.18", features = [] }
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
@ -20,18 +20,18 @@ kittycad = "0.3.5"
|
||||
log = "0.4.21"
|
||||
oauth2 = "4.4.2"
|
||||
serde_json = "1.0"
|
||||
tauri = { version = "2.0.0-beta.15", features = [ "devtools", "unstable"] }
|
||||
tauri-plugin-cli = { version = "2.0.0-beta.3" }
|
||||
tauri-plugin-deep-link = { version = "2.0.0-beta.3" }
|
||||
tauri = { version = "2.0.0-beta.23", features = [ "devtools", "unstable"] }
|
||||
tauri-plugin-cli = { version = "2.0.0-beta.7" }
|
||||
tauri-plugin-deep-link = { version = "2.0.0-beta.8" }
|
||||
tauri-plugin-dialog = { version = "2.0.0-beta.6" }
|
||||
tauri-plugin-fs = { version = "2.0.0-beta.9" }
|
||||
tauri-plugin-http = { version = "2.0.0-beta.6" }
|
||||
tauri-plugin-log = { version = "2.0.0-beta.4" }
|
||||
tauri-plugin-os = { version = "2.0.0-beta.2" }
|
||||
tauri-plugin-persisted-scope = { version = "2.0.0-beta.7" }
|
||||
tauri-plugin-process = { version = "2.0.0-beta.2" }
|
||||
tauri-plugin-shell = { version = "2.0.0-beta.2" }
|
||||
tauri-plugin-updater = { version = "2.0.0-beta.4" }
|
||||
tauri-plugin-fs = { version = "2.0.0-beta.10" }
|
||||
tauri-plugin-http = { version = "2.0.0-beta.11" }
|
||||
tauri-plugin-log = { version = "2.0.0-beta.7" }
|
||||
tauri-plugin-os = { version = "2.0.0-beta.7" }
|
||||
tauri-plugin-persisted-scope = { version = "2.0.0-beta.10" }
|
||||
tauri-plugin-process = { version = "2.0.0-beta.7" }
|
||||
tauri-plugin-shell = { version = "2.0.0-beta.8" }
|
||||
tauri-plugin-updater = { version = "2.0.0-beta.9" }
|
||||
tokio = { version = "1.37.0", features = ["time", "fs", "process"] }
|
||||
toml = "0.8.2"
|
||||
url = "2.5.0"
|
||||
|
@ -63,16 +63,22 @@
|
||||
"subcommands": {}
|
||||
},
|
||||
"deep-link": {
|
||||
"domains": [
|
||||
"mobile": [
|
||||
{
|
||||
"host": "app.zoo.dev"
|
||||
}
|
||||
]
|
||||
],
|
||||
"desktop": {
|
||||
"schemes": [
|
||||
"zoo",
|
||||
"zoo-modeling-app"
|
||||
]
|
||||
}
|
||||
},
|
||||
"shell": {
|
||||
"open": true
|
||||
}
|
||||
},
|
||||
"productName": "Zoo Modeling App",
|
||||
"version": "0.22.7"
|
||||
"version": "0.23.1"
|
||||
}
|
||||
|
28
src/App.tsx
@ -1,6 +1,5 @@
|
||||
import { MouseEventHandler, useEffect, useRef } from 'react'
|
||||
import { MouseEventHandler, useEffect, useMemo, useRef } from 'react'
|
||||
import { uuidv4 } from 'lib/utils'
|
||||
import { useStore } from './useStore'
|
||||
import { useHotKeyListener } from './hooks/useHotKeyListener'
|
||||
import { Stream } from './components/Stream'
|
||||
import { EngineCommand } from './lang/std/engineConnection'
|
||||
@ -44,22 +43,15 @@ export function App() {
|
||||
}, [projectName, projectPath])
|
||||
|
||||
useHotKeyListener()
|
||||
const { buttonDownInStream, didDragInStream, streamDimensions, setHtmlRef } =
|
||||
useStore((s) => ({
|
||||
buttonDownInStream: s.buttonDownInStream,
|
||||
didDragInStream: s.didDragInStream,
|
||||
streamDimensions: s.streamDimensions,
|
||||
setHtmlRef: s.setHtmlRef,
|
||||
}))
|
||||
|
||||
useEffect(() => {
|
||||
setHtmlRef(ref)
|
||||
}, [ref])
|
||||
const { context } = useModelingContext()
|
||||
|
||||
const { auth, settings } = useSettingsAuthContext()
|
||||
const token = auth?.context?.token
|
||||
|
||||
const coreDumpManager = new CoreDumpManager(engineCommandManager, ref, token)
|
||||
const coreDumpManager = useMemo(
|
||||
() => new CoreDumpManager(engineCommandManager, token),
|
||||
[]
|
||||
)
|
||||
|
||||
const {
|
||||
app: { onboardingStatus },
|
||||
@ -81,7 +73,7 @@ export function App() {
|
||||
(p) => p === onboardingStatus.current
|
||||
)
|
||||
? 'opacity-20'
|
||||
: didDragInStream
|
||||
: context.store?.didDragInStream
|
||||
? 'opacity-40'
|
||||
: ''
|
||||
|
||||
@ -99,11 +91,11 @@ export function App() {
|
||||
clientX: e.clientX,
|
||||
clientY: e.clientY,
|
||||
el: e.currentTarget,
|
||||
...streamDimensions,
|
||||
...context.store?.streamDimensions,
|
||||
})
|
||||
|
||||
const newCmdId = uuidv4()
|
||||
if (buttonDownInStream === undefined) {
|
||||
if (context.store?.buttonDownInStream === undefined) {
|
||||
debounceSocketSend({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd: {
|
||||
@ -125,7 +117,7 @@ export function App() {
|
||||
className={
|
||||
'transition-opacity transition-duration-75 ' +
|
||||
paneOpacity +
|
||||
(buttonDownInStream ? ' pointer-events-none' : '')
|
||||
(context.store?.buttonDownInStream ? ' pointer-events-none' : '')
|
||||
}
|
||||
project={{ project, file }}
|
||||
enableMenu={true}
|
||||
|
41
src/AppState.tsx
Normal file
@ -0,0 +1,41 @@
|
||||
import { createContext, useContext, useState, ReactNode } from 'react'
|
||||
|
||||
/*
|
||||
|
||||
This is for a very small handful of global state we need that doesn't fit into
|
||||
any of the Xstate machines.
|
||||
Please do not fill this up with junk.
|
||||
|
||||
*/
|
||||
|
||||
interface AppState {
|
||||
isStreamReady: boolean
|
||||
setAppState: (newAppState: Partial<AppState>) => void
|
||||
}
|
||||
|
||||
const AppStateContext = createContext<AppState>({
|
||||
isStreamReady: false,
|
||||
setAppState: () => {},
|
||||
})
|
||||
|
||||
export const useAppState = () => useContext(AppStateContext)
|
||||
|
||||
export const AppStateProvider = ({ children }: { children: ReactNode }) => {
|
||||
const [appState, _setAppState] = useState<AppState>({
|
||||
isStreamReady: false,
|
||||
setAppState: () => {},
|
||||
})
|
||||
const setAppState = (newAppState: Partial<AppState>) =>
|
||||
_setAppState({ ...appState, ...newAppState })
|
||||
|
||||
return (
|
||||
<AppStateContext.Provider
|
||||
value={{
|
||||
isStreamReady: appState.isStreamReady,
|
||||
setAppState,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</AppStateContext.Provider>
|
||||
)
|
||||
}
|
@ -33,6 +33,14 @@ import LspProvider from 'components/LspProvider'
|
||||
import { KclContextProvider } from 'lang/KclProvider'
|
||||
import { BROWSER_PROJECT_NAME } from 'lib/constants'
|
||||
import { getState, setState } from 'lib/tauri'
|
||||
import { CoreDumpManager } from 'lib/coredump'
|
||||
import { engineCommandManager } from 'lib/singletons'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import useHotkeyWrapper from 'lib/hotkeyWrapper'
|
||||
import toast from 'react-hot-toast'
|
||||
import { coreDump } from 'lang/wasm'
|
||||
import { useMemo } from 'react'
|
||||
import { AppStateProvider } from 'AppState'
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
@ -45,7 +53,9 @@ const router = createBrowserRouter([
|
||||
<SettingsAuthProvider>
|
||||
<LspProvider>
|
||||
<KclContextProvider>
|
||||
<Outlet />
|
||||
<AppStateProvider>
|
||||
<Outlet />
|
||||
</AppStateProvider>
|
||||
</KclContextProvider>
|
||||
</LspProvider>
|
||||
</SettingsAuthProvider>
|
||||
@ -87,6 +97,7 @@ const router = createBrowserRouter([
|
||||
<Auth>
|
||||
<FileMachineProvider>
|
||||
<ModelingMachineProvider>
|
||||
<CoreDump />
|
||||
<Outlet />
|
||||
<App />
|
||||
<CommandBar />
|
||||
@ -165,3 +176,30 @@ export const Router = () => {
|
||||
</NetworkContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
function CoreDump() {
|
||||
const { auth } = useSettingsAuthContext()
|
||||
const token = auth?.context?.token
|
||||
const coreDumpManager = useMemo(
|
||||
() => new CoreDumpManager(engineCommandManager, token),
|
||||
[]
|
||||
)
|
||||
useHotkeyWrapper(['meta + shift + .'], () => {
|
||||
toast.promise(
|
||||
coreDump(coreDumpManager, true),
|
||||
{
|
||||
loading: 'Starting core dump...',
|
||||
success: 'Core dump completed successfully',
|
||||
error: 'Error while exporting core dump',
|
||||
},
|
||||
{
|
||||
success: {
|
||||
// Note: this extended duration is especially important for Playwright e2e testing
|
||||
// default duration is 2000 - https://react-hot-toast.com/docs/toast#default-durations
|
||||
duration: 6000,
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
return null
|
||||
}
|
||||
|
@ -8,10 +8,14 @@ import { NetworkHealthState } from 'hooks/useNetworkStatus'
|
||||
import { ActionButton } from 'components/ActionButton'
|
||||
import { isSingleCursorInPipe } from 'lang/queryAst'
|
||||
import { useKclContext } from 'lang/KclProvider'
|
||||
import { useStore } from 'useStore'
|
||||
import { ActionButtonDropdown } from 'components/ActionButtonDropdown'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
import Tooltip from 'components/Tooltip'
|
||||
import { useAppState } from 'AppState'
|
||||
import {
|
||||
canRectangleTool,
|
||||
isEditingExistingSketch,
|
||||
} from 'machines/modelingMachine'
|
||||
|
||||
export function Toolbar({
|
||||
className = '',
|
||||
@ -38,38 +42,56 @@ export function Toolbar({
|
||||
const toolbarButtonsRef = useRef<HTMLUListElement>(null)
|
||||
const { overallState } = useNetworkContext()
|
||||
const { isExecuting } = useKclContext()
|
||||
const { isStreamReady } = useStore((s) => ({
|
||||
isStreamReady: s.isStreamReady,
|
||||
}))
|
||||
const { isStreamReady } = useAppState()
|
||||
|
||||
const disableAllButtons =
|
||||
(overallState !== NetworkHealthState.Ok &&
|
||||
overallState !== NetworkHealthState.Weak) ||
|
||||
isExecuting ||
|
||||
!isStreamReady
|
||||
|
||||
const disableLineButton =
|
||||
state.matches('Sketch.Rectangle tool.Awaiting second corner') ||
|
||||
disableAllButtons
|
||||
useHotkeys(
|
||||
'l',
|
||||
() =>
|
||||
state.matches('Sketch.Line tool')
|
||||
? send('CancelSketch')
|
||||
: send('Equip Line tool'),
|
||||
{ enabled: !disableAllButtons, scopes: ['sketch'] }
|
||||
: send({
|
||||
type: 'change tool',
|
||||
data: 'line',
|
||||
}),
|
||||
{ enabled: !disableLineButton, scopes: ['sketch'] }
|
||||
)
|
||||
const disableTangentialArc =
|
||||
(!isEditingExistingSketch(context) &&
|
||||
!state.matches('Sketch.Tangential arc to')) ||
|
||||
disableAllButtons
|
||||
useHotkeys(
|
||||
'a',
|
||||
() =>
|
||||
state.matches('Sketch.Tangential arc to')
|
||||
? send('CancelSketch')
|
||||
: send('Equip tangential arc to'),
|
||||
{ enabled: !disableAllButtons, scopes: ['sketch'] }
|
||||
: send({
|
||||
type: 'change tool',
|
||||
data: 'tangentialArc',
|
||||
}),
|
||||
{ enabled: !disableTangentialArc, scopes: ['sketch'] }
|
||||
)
|
||||
const disableRectangle =
|
||||
(!canRectangleTool(context) && !state.matches('Sketch.Rectangle tool')) ||
|
||||
disableAllButtons
|
||||
useHotkeys(
|
||||
'r',
|
||||
() =>
|
||||
state.matches('Sketch.Rectangle tool')
|
||||
? send('CancelSketch')
|
||||
: send('Equip rectangle tool'),
|
||||
{ enabled: !disableAllButtons, scopes: ['sketch'] }
|
||||
: send({
|
||||
type: 'change tool',
|
||||
data: 'rectangle',
|
||||
}),
|
||||
{ enabled: !disableRectangle, scopes: ['sketch'] }
|
||||
)
|
||||
useHotkeys(
|
||||
's',
|
||||
@ -82,7 +104,7 @@ export function Toolbar({
|
||||
useHotkeys(
|
||||
'esc',
|
||||
() =>
|
||||
state.matches('Sketch.SketchIdle')
|
||||
['Sketch no face', 'Sketch.SketchIdle'].some(state.matches)
|
||||
? send('Cancel')
|
||||
: send('CancelSketch'),
|
||||
{ enabled: !disableAllButtons, scopes: ['sketch'] }
|
||||
@ -225,6 +247,11 @@ export function Toolbar({
|
||||
</ActionButton>
|
||||
</li>
|
||||
)}
|
||||
{state.matches('Sketch no face') && (
|
||||
<li className="contents">
|
||||
<div className="mx-2 text-sm">click plane or face to sketch on</div>
|
||||
</li>
|
||||
)}
|
||||
{state.matches('Sketch') && !state.matches('idle') && (
|
||||
<>
|
||||
<li className="contents" key="line-button">
|
||||
@ -234,7 +261,10 @@ export function Toolbar({
|
||||
onClick={() =>
|
||||
state?.matches('Sketch.Line tool')
|
||||
? send('CancelSketch')
|
||||
: send('Equip Line tool')
|
||||
: send({
|
||||
type: 'change tool',
|
||||
data: 'line',
|
||||
})
|
||||
}
|
||||
aria-pressed={state?.matches('Sketch.Line tool')}
|
||||
iconStart={{
|
||||
@ -242,7 +272,7 @@ export function Toolbar({
|
||||
iconClassName,
|
||||
bgClassName,
|
||||
}}
|
||||
disabled={disableAllButtons}
|
||||
disabled={disableLineButton}
|
||||
>
|
||||
Line
|
||||
<Tooltip
|
||||
@ -261,7 +291,10 @@ export function Toolbar({
|
||||
onClick={() =>
|
||||
state.matches('Sketch.Tangential arc to')
|
||||
? send('CancelSketch')
|
||||
: send('Equip tangential arc to')
|
||||
: send({
|
||||
type: 'change tool',
|
||||
data: 'tangentialArc',
|
||||
})
|
||||
}
|
||||
aria-pressed={state.matches('Sketch.Tangential arc to')}
|
||||
iconStart={{
|
||||
@ -269,11 +302,7 @@ export function Toolbar({
|
||||
iconClassName,
|
||||
bgClassName,
|
||||
}}
|
||||
disabled={
|
||||
(!state.can('Equip tangential arc to') &&
|
||||
!state.matches('Sketch.Tangential arc to')) ||
|
||||
disableAllButtons
|
||||
}
|
||||
disabled={disableTangentialArc}
|
||||
>
|
||||
Tangential Arc
|
||||
<Tooltip
|
||||
@ -292,7 +321,10 @@ export function Toolbar({
|
||||
onClick={() =>
|
||||
state.matches('Sketch.Rectangle tool')
|
||||
? send('CancelSketch')
|
||||
: send('Equip rectangle tool')
|
||||
: send({
|
||||
type: 'change tool',
|
||||
data: 'rectangle',
|
||||
})
|
||||
}
|
||||
aria-pressed={state.matches('Sketch.Rectangle tool')}
|
||||
iconStart={{
|
||||
@ -300,13 +332,9 @@ export function Toolbar({
|
||||
iconClassName,
|
||||
bgClassName,
|
||||
}}
|
||||
disabled={
|
||||
(!state.can('Equip rectangle tool') &&
|
||||
!state.matches('Sketch.Rectangle tool')) ||
|
||||
disableAllButtons
|
||||
}
|
||||
disabled={disableRectangle}
|
||||
title={
|
||||
state.can('Equip rectangle tool')
|
||||
canRectangleTool(context)
|
||||
? 'Rectangle'
|
||||
: 'Can only be used when a sketch is empty currently'
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ import { Dialog, Popover, Transition } from '@headlessui/react'
|
||||
import { LineInputsType } from 'lang/std/sketchcombos'
|
||||
import toast from 'react-hot-toast'
|
||||
import { InstanceProps, create } from 'react-modal-promise'
|
||||
import { executeAst } from 'useStore'
|
||||
import { executeAst } from 'lang/langHelpers'
|
||||
import {
|
||||
deleteSegmentFromPipeExpression,
|
||||
makeRemoveSingleConstraintInput,
|
||||
|
@ -58,7 +58,7 @@ import {
|
||||
editorManager,
|
||||
} from 'lib/singletons'
|
||||
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
|
||||
import { executeAst, useStore } from 'useStore'
|
||||
import { executeAst } from 'lang/langHelpers'
|
||||
import {
|
||||
createArcGeometry,
|
||||
dashedStraight,
|
||||
@ -804,7 +804,7 @@ export class SceneEntities {
|
||||
|
||||
// Update the primary AST and unequip the rectangle tool
|
||||
await kclManager.executeAstMock(_ast)
|
||||
sceneInfra.modelingSend({ type: 'CancelSketch' })
|
||||
sceneInfra.modelingSend({ type: 'Finish rectangle' })
|
||||
|
||||
const { programMemory } = await executeAst({
|
||||
ast: _ast,
|
||||
@ -1444,11 +1444,10 @@ export class SceneEntities {
|
||||
selected.material.color = defaultPlaneColor(type)
|
||||
},
|
||||
onClick: async (args) => {
|
||||
const { streamDimensions } = useStore.getState()
|
||||
const { entity_id } = await sendSelectEventToEngine(
|
||||
args?.mouseEvent,
|
||||
document.getElementById('video-stream') as HTMLVideoElement,
|
||||
streamDimensions
|
||||
sceneInfra._streamDimensions
|
||||
)
|
||||
|
||||
let _entity_id = entity_id
|
||||
|
@ -103,6 +103,10 @@ export class SceneInfra {
|
||||
_baseUnit: BaseUnit = 'mm'
|
||||
_baseUnitMultiplier = 1
|
||||
_theme: Themes = Themes.System
|
||||
_streamDimensions: { streamWidth: number; streamHeight: number } = {
|
||||
streamWidth: 1280,
|
||||
streamHeight: 720,
|
||||
}
|
||||
extraSegmentTexture: Texture
|
||||
lastMouseState: MouseState = { type: 'idle' }
|
||||
onDragStartCallback: (arg: OnDragCallbackArgs) => void = () => {}
|
||||
|
@ -10,7 +10,7 @@ import { findAllPreviousVariables, PrevVariable } from '../lang/queryAst'
|
||||
import { engineCommandManager, kclManager } from 'lib/singletons'
|
||||
import { useKclContext } from 'lang/KclProvider'
|
||||
import { useModelingContext } from 'hooks/useModelingContext'
|
||||
import { executeAst } from 'useStore'
|
||||
import { executeAst } from 'lang/langHelpers'
|
||||
import { trap } from 'lib/trap'
|
||||
|
||||
export const AvailableVars = ({
|
||||
|
@ -24,7 +24,7 @@ export const CommandBar = () => {
|
||||
}, [pathname])
|
||||
|
||||
// Hook up keyboard shortcuts
|
||||
useHotkeyWrapper(['mod+k', 'ctrl+c'], () => {
|
||||
useHotkeyWrapper(['mod+k'], () => {
|
||||
if (commandBarState.context.commands.length === 0) return
|
||||
if (commandBarState.matches('Closed')) {
|
||||
commandBarSend({ type: 'Open' })
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { Completion } from '@codemirror/autocomplete'
|
||||
import { EditorState, EditorView, useCodeMirror } from '@uiw/react-codemirror'
|
||||
import { EditorView, ViewUpdate } from '@codemirror/view'
|
||||
import { EditorState } from '@codemirror/state'
|
||||
import { CustomIcon } from 'components/CustomIcon'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
@ -12,6 +13,7 @@ import { useEffect, useRef, useState } from 'react'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
import styles from './CommandBarKclInput.module.css'
|
||||
import { createIdentifier, createVariableDeclaration } from 'lang/modifyAst'
|
||||
import { useCodeMirror } from 'components/ModelingSidebar/ModelingPanes/CodeEditor'
|
||||
|
||||
function CommandBarKclInput({
|
||||
arg,
|
||||
@ -63,9 +65,7 @@ function CommandBarKclInput({
|
||||
|
||||
const { setContainer } = useCodeMirror({
|
||||
container: editorRef.current,
|
||||
value,
|
||||
indentWithTab: false,
|
||||
basicSetup: false,
|
||||
initialDocValue: value,
|
||||
autoFocus: true,
|
||||
selection: {
|
||||
anchor: 0,
|
||||
@ -74,7 +74,6 @@ function CommandBarKclInput({
|
||||
? previouslySetValue.valueText.length
|
||||
: defaultValue.length,
|
||||
},
|
||||
accessKey: 'command-bar',
|
||||
theme:
|
||||
settings.context.app.theme.current === 'system'
|
||||
? getSystemTheme()
|
||||
@ -96,8 +95,12 @@ function CommandBarKclInput({
|
||||
}
|
||||
return tr
|
||||
}),
|
||||
EditorView.updateListener.of((vu: ViewUpdate) => {
|
||||
if (vu.docChanged) {
|
||||
setValue(vu.state.doc.toString())
|
||||
}
|
||||
}),
|
||||
],
|
||||
onChange: (newValue) => setValue(newValue),
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -175,7 +175,11 @@ const FileTreeItem = ({
|
||||
codeManager.code
|
||||
)
|
||||
codeManager.writeToFile()
|
||||
kclManager.executeCode(true, true)
|
||||
|
||||
kclManager.isFirstRender = true
|
||||
kclManager.executeCode(true, true).then(() => {
|
||||
kclManager.isFirstRender = false
|
||||
})
|
||||
} else {
|
||||
// Let the lsp servers know we closed a file.
|
||||
onFileClose(currentFile?.path || null, project?.path || null)
|
||||
|
@ -1,11 +1,5 @@
|
||||
import type * as LSP from 'vscode-languageserver-protocol'
|
||||
import React, {
|
||||
createContext,
|
||||
useMemo,
|
||||
useEffect,
|
||||
useContext,
|
||||
useState,
|
||||
} from 'react'
|
||||
import React, { createContext, useMemo, useContext, useState } from 'react'
|
||||
import {
|
||||
LanguageServerClient,
|
||||
FromServer,
|
||||
@ -16,7 +10,6 @@ import {
|
||||
import { TEST, VITE_KC_API_BASE_URL } from 'env'
|
||||
import KclLanguageSupport from 'editor/plugins/lsp/kcl/language'
|
||||
import { copilotPlugin } from 'editor/plugins/lsp/copilot'
|
||||
import { useStore } from 'useStore'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import { Extension } from '@codemirror/state'
|
||||
import { LanguageSupport } from '@codemirror/language'
|
||||
@ -73,19 +66,8 @@ type LspContext = {
|
||||
|
||||
export const LspStateContext = createContext({} as LspContext)
|
||||
export const LspProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
const {
|
||||
isKclLspServerReady,
|
||||
isCopilotLspServerReady,
|
||||
setIsKclLspServerReady,
|
||||
setIsCopilotLspServerReady,
|
||||
} = useStore((s) => ({
|
||||
isKclLspServerReady: s.isKclLspServerReady,
|
||||
isCopilotLspServerReady: s.isCopilotLspServerReady,
|
||||
setIsKclLspServerReady: s.setIsKclLspServerReady,
|
||||
setIsCopilotLspServerReady: s.setIsCopilotLspServerReady,
|
||||
}))
|
||||
const [isLspReady, setIsLspReady] = useState(false)
|
||||
const [isCopilotReady, setIsCopilotReady] = useState(false)
|
||||
const [isKclLspReady, setIsKclLspReady] = useState(false)
|
||||
const [isCopilotLspReady, setIsCopilotLspReady] = useState(false)
|
||||
|
||||
const {
|
||||
auth,
|
||||
@ -132,7 +114,7 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
fromServer,
|
||||
intoServer,
|
||||
initializedCallback: () => {
|
||||
setIsLspReady(true)
|
||||
setIsKclLspReady(true)
|
||||
},
|
||||
})
|
||||
|
||||
@ -143,7 +125,7 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
])
|
||||
|
||||
useMemo(() => {
|
||||
if (!isTauri() && isKclLspServerReady && kclLspClient && codeManager.code) {
|
||||
if (!isTauri() && isKclLspReady && kclLspClient && codeManager.code) {
|
||||
kclLspClient.textDocumentDidOpen({
|
||||
textDocument: {
|
||||
uri: `file:///${PROJECT_ENTRYPOINT}`,
|
||||
@ -153,7 +135,7 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
},
|
||||
})
|
||||
}
|
||||
}, [kclLspClient, isKclLspServerReady])
|
||||
}, [kclLspClient, isKclLspReady])
|
||||
|
||||
// Here we initialize the plugin which will start the client.
|
||||
// Now that we have multi-file support the name of the file is a dep of
|
||||
@ -162,7 +144,7 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
// We do not want to restart the server, its just wasteful.
|
||||
const kclLSP = useMemo(() => {
|
||||
let plugin = null
|
||||
if (isKclLspServerReady && !TEST && kclLspClient) {
|
||||
if (isKclLspReady && !TEST && kclLspClient) {
|
||||
// Set up the lsp plugin.
|
||||
const lsp = new KclLanguageSupport({
|
||||
documentUri: `file:///${PROJECT_ENTRYPOINT}`,
|
||||
@ -193,7 +175,7 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
plugin = lsp
|
||||
}
|
||||
return plugin
|
||||
}, [kclLspClient, isKclLspServerReady])
|
||||
}, [kclLspClient, isKclLspReady])
|
||||
|
||||
const { lspClient: copilotLspClient } = useMemo(() => {
|
||||
if (!token || token === '' || TEST) {
|
||||
@ -225,7 +207,7 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
fromServer,
|
||||
intoServer,
|
||||
initializedCallback: () => {
|
||||
setIsCopilotReady(true)
|
||||
setIsCopilotLspReady(true)
|
||||
},
|
||||
})
|
||||
return { lspClient }
|
||||
@ -238,7 +220,7 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
// We do not want to restart the server, its just wasteful.
|
||||
const copilotLSP = useMemo(() => {
|
||||
let plugin = null
|
||||
if (isCopilotLspServerReady && !TEST && copilotLspClient) {
|
||||
if (isCopilotLspReady && !TEST && copilotLspClient) {
|
||||
// Set up the lsp plugin.
|
||||
const lsp = copilotPlugin({
|
||||
documentUri: `file:///${PROJECT_ENTRYPOINT}`,
|
||||
@ -250,7 +232,7 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
plugin = lsp
|
||||
}
|
||||
return plugin
|
||||
}, [copilotLspClient, isCopilotLspServerReady])
|
||||
}, [copilotLspClient, isCopilotLspReady])
|
||||
|
||||
let lspClients: LanguageServerClient[] = []
|
||||
if (kclLspClient) {
|
||||
@ -260,13 +242,6 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
lspClients.push(copilotLspClient)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setIsKclLspServerReady(isLspReady)
|
||||
}, [isLspReady])
|
||||
useEffect(() => {
|
||||
setIsCopilotLspServerReady(isCopilotReady)
|
||||
}, [isCopilotReady])
|
||||
|
||||
const onProjectClose = (
|
||||
file: FileEntry | null,
|
||||
projectPath: string | null,
|
||||
|
@ -30,7 +30,6 @@ import {
|
||||
applyConstraintAngleBetween,
|
||||
} from './Toolbar/SetAngleBetween'
|
||||
import { applyConstraintAngleLength } from './Toolbar/setAngleLength'
|
||||
import { useStore } from 'useStore'
|
||||
import {
|
||||
Selections,
|
||||
canExtrudeSelection,
|
||||
@ -54,13 +53,7 @@ import {
|
||||
sketchOnExtrudedFace,
|
||||
startSketchOnDefault,
|
||||
} from 'lang/modifyAst'
|
||||
import {
|
||||
Program,
|
||||
VariableDeclaration,
|
||||
coreDump,
|
||||
parse,
|
||||
recast,
|
||||
} from 'lang/wasm'
|
||||
import { Program, VariableDeclaration, parse, recast } from 'lang/wasm'
|
||||
import {
|
||||
getNodeFromPath,
|
||||
getNodePathFromSourceRange,
|
||||
@ -71,12 +64,10 @@ import { TEST } from 'env'
|
||||
import { exportFromEngine } from 'lib/exportFromEngine'
|
||||
import { Models } from '@kittycad/lib/dist/types/src'
|
||||
import toast from 'react-hot-toast'
|
||||
import { EditorSelection, Transaction } from '@uiw/react-codemirror'
|
||||
import { CoreDumpManager } from 'lib/coredump'
|
||||
import { EditorSelection, Transaction } from '@codemirror/state'
|
||||
import { useSearchParams } from 'react-router-dom'
|
||||
import { letEngineAnimateAndSyncCamAfter } from 'clientSideScene/CameraControls'
|
||||
import { getVarNameModal } from 'hooks/useToolbarGuards'
|
||||
import useHotkeyWrapper from 'lib/hotkeyWrapper'
|
||||
import { uuidv4 } from 'lib/utils'
|
||||
import { err, trap } from 'lib/trap'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
@ -112,38 +103,6 @@ export const ModelingMachineProvider = ({
|
||||
let [searchParams] = useSearchParams()
|
||||
const pool = searchParams.get('pool')
|
||||
|
||||
useSetupEngineManager(streamRef, token, {
|
||||
pool: pool,
|
||||
theme: theme.current,
|
||||
highlightEdges: highlightEdges.current,
|
||||
enableSSAO: enableSSAO.current,
|
||||
showScaleGrid: showScaleGrid.current,
|
||||
})
|
||||
const { htmlRef } = useStore((s) => ({
|
||||
htmlRef: s.htmlRef,
|
||||
}))
|
||||
const coreDumpManager = new CoreDumpManager(
|
||||
engineCommandManager,
|
||||
htmlRef,
|
||||
token
|
||||
)
|
||||
useHotkeyWrapper(['meta + shift + .'], () => {
|
||||
toast.promise(
|
||||
coreDump(coreDumpManager, true),
|
||||
{
|
||||
loading: 'Starting core dump...',
|
||||
success: 'Core dump completed successfully',
|
||||
error: 'Error while exporting core dump',
|
||||
},
|
||||
{
|
||||
success: {
|
||||
// Note: this extended duration is especially important for Playwright e2e testing
|
||||
// default duration is 2000 - https://react-hot-toast.com/docs/toast#default-durations
|
||||
duration: 6000,
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
const { commandBarState } = useCommandsContext()
|
||||
|
||||
// Settings machine setup
|
||||
@ -163,7 +122,13 @@ export const ModelingMachineProvider = ({
|
||||
modelingMachine,
|
||||
{
|
||||
actions: {
|
||||
'sketch exit execute': () => {
|
||||
'disable copilot': () => {
|
||||
editorManager.setCopilotEnabled(false)
|
||||
},
|
||||
'enable copilot': () => {
|
||||
editorManager.setCopilotEnabled(true)
|
||||
},
|
||||
'sketch exit execute': ({ store }) => {
|
||||
;(async () => {
|
||||
await sceneInfra.camControls.snapToPerspectiveBeforeHandingBackControlToEngine()
|
||||
|
||||
@ -197,7 +162,10 @@ export const ModelingMachineProvider = ({
|
||||
})
|
||||
}
|
||||
|
||||
kclManager.executeCode(true)
|
||||
store.videoElement?.pause()
|
||||
kclManager.executeCode(true).then(() => {
|
||||
store.videoElement?.play()
|
||||
})
|
||||
})()
|
||||
},
|
||||
'Set mouse state': assign({
|
||||
@ -473,17 +441,6 @@ export const ModelingMachineProvider = ({
|
||||
if (selectionRanges.codeBasedSelections.length <= 0) return false
|
||||
return true
|
||||
},
|
||||
'Sketch is empty': ({ sketchDetails }) => {
|
||||
const node = getNodeFromPath<VariableDeclaration>(
|
||||
kclManager.ast,
|
||||
sketchDetails?.sketchPathToNode || [],
|
||||
'VariableDeclaration'
|
||||
)
|
||||
// This should not be returning false, and it should be caught
|
||||
// but we need to simulate old behavior to move on.
|
||||
if (err(node)) return false
|
||||
return node.node?.declarations?.[0]?.init.type !== 'PipeExpression'
|
||||
},
|
||||
'Selection is on face': ({ selectionRanges }, { data }) => {
|
||||
if (data?.forceNewSketch) return false
|
||||
if (!isSingleCursorInPipe(selectionRanges, kclManager.ast))
|
||||
@ -514,11 +471,15 @@ export const ModelingMachineProvider = ({
|
||||
services: {
|
||||
'AST-undo-startSketchOn': async ({ sketchDetails }) => {
|
||||
if (!sketchDetails) return
|
||||
const newAst: Program = JSON.parse(JSON.stringify(kclManager.ast))
|
||||
const varDecIndex = sketchDetails.sketchPathToNode[1][0]
|
||||
// remove body item at varDecIndex
|
||||
newAst.body = newAst.body.filter((_, i) => i !== varDecIndex)
|
||||
await kclManager.executeAstMock(newAst)
|
||||
if (kclManager.ast.body.length) {
|
||||
// this assumes no changes have been made to the sketch besides what we did when entering the sketch
|
||||
// i.e. doesn't account for user's adding code themselves, maybe we need store a flag userEditedSinceSketchMode?
|
||||
const newAst: Program = JSON.parse(JSON.stringify(kclManager.ast))
|
||||
const varDecIndex = sketchDetails.sketchPathToNode[1][0]
|
||||
// remove body item at varDecIndex
|
||||
newAst.body = newAst.body.filter((_, i) => i !== varDecIndex)
|
||||
await kclManager.executeAstMock(newAst)
|
||||
}
|
||||
sceneInfra.setCallbacks({
|
||||
onClick: () => {},
|
||||
onDrag: () => {},
|
||||
@ -898,6 +859,16 @@ export const ModelingMachineProvider = ({
|
||||
}
|
||||
)
|
||||
|
||||
useSetupEngineManager(streamRef, token, {
|
||||
pool: pool,
|
||||
theme: theme.current,
|
||||
highlightEdges: highlightEdges.current,
|
||||
enableSSAO: enableSSAO.current,
|
||||
modelingSend,
|
||||
modelingContext: modelingState.context,
|
||||
showScaleGrid: showScaleGrid.current,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
kclManager.registerExecuteCallback(() => {
|
||||
modelingSend({ type: 'Re-execute' })
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useStore } from 'useStore'
|
||||
import styles from './ModelingPane.module.css'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import { useModelingContext } from 'hooks/useModelingContext'
|
||||
|
||||
export interface ModelingPaneProps
|
||||
extends React.PropsWithChildren,
|
||||
@ -33,11 +33,9 @@ export const ModelingPane = ({
|
||||
}: ModelingPaneProps) => {
|
||||
const { settings } = useSettingsAuthContext()
|
||||
const onboardingStatus = settings.context.app.onboardingStatus
|
||||
const { buttonDownInStream } = useStore((s) => ({
|
||||
buttonDownInStream: s.buttonDownInStream,
|
||||
}))
|
||||
const { context } = useModelingContext()
|
||||
const pointerEventsCssClass =
|
||||
buttonDownInStream || onboardingStatus.current === 'camera'
|
||||
context.store?.buttonDownInStream || onboardingStatus.current === 'camera'
|
||||
? 'pointer-events-none '
|
||||
: 'pointer-events-auto '
|
||||
return (
|
||||
|
171
src/components/ModelingSidebar/ModelingPanes/CodeEditor.tsx
Normal file
@ -0,0 +1,171 @@
|
||||
import React, {
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
forwardRef,
|
||||
useImperativeHandle,
|
||||
} from 'react'
|
||||
import {
|
||||
EditorState,
|
||||
EditorStateConfig,
|
||||
Extension,
|
||||
StateEffect,
|
||||
} from '@codemirror/state'
|
||||
import { EditorView } from '@codemirror/view'
|
||||
import { oneDark } from '@codemirror/theme-one-dark'
|
||||
|
||||
//reference: https://github.com/sachinraja/rodemirror/blob/main/src/use-first-render.ts
|
||||
const useFirstRender = () => {
|
||||
const firstRender = useRef(true)
|
||||
|
||||
useEffect(() => {
|
||||
firstRender.current = false
|
||||
}, [])
|
||||
|
||||
return firstRender.current
|
||||
}
|
||||
|
||||
const defaultLightThemeOption = EditorView.theme(
|
||||
{
|
||||
'&': {
|
||||
backgroundColor: '#fff',
|
||||
},
|
||||
},
|
||||
{
|
||||
dark: false,
|
||||
}
|
||||
)
|
||||
|
||||
interface CodeEditorRef {
|
||||
editor?: HTMLDivElement | null
|
||||
view?: EditorView
|
||||
state?: EditorState
|
||||
}
|
||||
|
||||
interface CodeEditorProps {
|
||||
onCreateEditor?: (view: EditorView | null) => void
|
||||
initialDocValue?: EditorStateConfig['doc']
|
||||
extensions?: Extension
|
||||
theme: 'light' | 'dark'
|
||||
autoFocus?: boolean
|
||||
selection?: EditorStateConfig['selection']
|
||||
}
|
||||
|
||||
interface UseCodeMirror extends CodeEditorProps {
|
||||
container?: HTMLDivElement | null
|
||||
}
|
||||
|
||||
const CodeEditor = forwardRef<CodeEditorRef, CodeEditorProps>((props, ref) => {
|
||||
const {
|
||||
onCreateEditor,
|
||||
extensions = [],
|
||||
initialDocValue,
|
||||
theme,
|
||||
autoFocus = false,
|
||||
selection,
|
||||
} = props
|
||||
const editor = useRef<HTMLDivElement>(null)
|
||||
|
||||
const { view, state, container } = useCodeMirror({
|
||||
container: editor.current,
|
||||
onCreateEditor,
|
||||
extensions,
|
||||
initialDocValue,
|
||||
theme,
|
||||
autoFocus,
|
||||
selection,
|
||||
})
|
||||
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
() => ({ editor: editor.current, view: view, state: state }),
|
||||
[editor, container, view, state]
|
||||
)
|
||||
|
||||
return <div ref={editor}></div>
|
||||
})
|
||||
|
||||
export function useCodeMirror(props: UseCodeMirror) {
|
||||
const {
|
||||
onCreateEditor,
|
||||
extensions = [],
|
||||
initialDocValue,
|
||||
theme,
|
||||
autoFocus = false,
|
||||
selection,
|
||||
} = props
|
||||
|
||||
const [container, setContainer] = useState<HTMLDivElement | null>()
|
||||
const [view, setView] = useState<EditorView>()
|
||||
const [state, setState] = useState<EditorState>()
|
||||
|
||||
const isFirstRender = useFirstRender()
|
||||
|
||||
const targetExtensions = useMemo(() => {
|
||||
let exts = Array.isArray(extensions) ? extensions : []
|
||||
if (theme === 'dark') {
|
||||
exts = [...exts, oneDark]
|
||||
} else if (theme === 'light') {
|
||||
exts = [...exts, defaultLightThemeOption]
|
||||
}
|
||||
|
||||
return exts
|
||||
}, [extensions, theme])
|
||||
|
||||
useEffect(() => {
|
||||
if (container && !state) {
|
||||
const config = {
|
||||
doc: initialDocValue,
|
||||
selection,
|
||||
extensions: [...Array.of(extensions)],
|
||||
}
|
||||
const stateCurrent = EditorState.create(config)
|
||||
setState(stateCurrent)
|
||||
if (!view) {
|
||||
const viewCurrent = new EditorView({
|
||||
state: stateCurrent,
|
||||
parent: container,
|
||||
})
|
||||
setView(viewCurrent)
|
||||
onCreateEditor && onCreateEditor(viewCurrent)
|
||||
}
|
||||
}
|
||||
return () => {
|
||||
if (view) {
|
||||
setState(undefined)
|
||||
setView(undefined)
|
||||
}
|
||||
}
|
||||
}, [container, state])
|
||||
|
||||
useEffect(() => setContainer(props.container), [props.container])
|
||||
|
||||
useEffect(
|
||||
() => () => {
|
||||
if (view) {
|
||||
view.destroy()
|
||||
setView(undefined)
|
||||
}
|
||||
},
|
||||
[view]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (autoFocus && view) {
|
||||
view.focus()
|
||||
}
|
||||
}, [autoFocus, view])
|
||||
|
||||
useEffect(() => {
|
||||
if (view && !isFirstRender) {
|
||||
view.dispatch({
|
||||
effects: StateEffect.reconfigure.of(targetExtensions),
|
||||
})
|
||||
}
|
||||
}, [targetExtensions])
|
||||
|
||||
return { view, setView, container, setContainer, state, setState }
|
||||
}
|
||||
|
||||
export default CodeEditor
|
@ -1,4 +1,3 @@
|
||||
import ReactCodeMirror from '@uiw/react-codemirror'
|
||||
import { TEST } from 'env'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import { Themes, getSystemTheme } from 'lib/theme'
|
||||
@ -43,6 +42,7 @@ import {
|
||||
closeBracketsKeymap,
|
||||
completionKeymap,
|
||||
} from '@codemirror/autocomplete'
|
||||
import CodeEditor from './CodeEditor'
|
||||
|
||||
export const editorShortcutMeta = {
|
||||
formatCode: {
|
||||
@ -185,15 +185,15 @@ export const KclEditorPane = () => {
|
||||
id="code-mirror-override"
|
||||
className={'absolute inset-0 ' + (cursorBlinking.current ? 'blink' : '')}
|
||||
>
|
||||
<ReactCodeMirror
|
||||
value={initialCode.current}
|
||||
<CodeEditor
|
||||
initialDocValue={initialCode.current}
|
||||
extensions={editorExtensions}
|
||||
theme={theme}
|
||||
onCreateEditor={(_editorView) => {
|
||||
if (_editorView === null) return
|
||||
|
||||
editorManager.setEditorView(_editorView)
|
||||
}}
|
||||
indentWithTab={false}
|
||||
basicSetup={false}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
@ -2,7 +2,6 @@ import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import { Resizable } from 're-resizable'
|
||||
import { HTMLAttributes, useCallback, useEffect, useState } from 'react'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
import { useStore } from 'useStore'
|
||||
import { Tab } from '@headlessui/react'
|
||||
import {
|
||||
SidebarPane,
|
||||
@ -15,6 +14,7 @@ import { ActionIcon } from 'components/ActionIcon'
|
||||
import styles from './ModelingSidebar.module.css'
|
||||
import { ModelingPane } from './ModelingPane'
|
||||
import { isTauri } from 'lib/isTauri'
|
||||
import { useModelingContext } from 'hooks/useModelingContext'
|
||||
|
||||
interface ModelingSidebarProps {
|
||||
paneOpacity: '' | 'opacity-20' | 'opacity-40'
|
||||
@ -23,14 +23,11 @@ interface ModelingSidebarProps {
|
||||
export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
|
||||
const { settings } = useSettingsAuthContext()
|
||||
const onboardingStatus = settings.context.app.onboardingStatus
|
||||
const { openPanes, buttonDownInStream } = useStore((s) => ({
|
||||
buttonDownInStream: s.buttonDownInStream,
|
||||
openPanes: s.openPanes,
|
||||
}))
|
||||
const { context } = useModelingContext()
|
||||
const pointerEventsCssClass =
|
||||
buttonDownInStream ||
|
||||
context.store?.buttonDownInStream ||
|
||||
onboardingStatus.current === 'camera' ||
|
||||
openPanes.length === 0
|
||||
context.store?.openPanes.length === 0
|
||||
? 'pointer-events-none '
|
||||
: 'pointer-events-auto '
|
||||
|
||||
@ -45,7 +42,7 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
|
||||
maxWidth={800}
|
||||
handleClasses={{
|
||||
right:
|
||||
(openPanes.length === 0 ? 'hidden ' : 'block ') +
|
||||
(context.store?.openPanes.length === 0 ? 'hidden ' : 'block ') +
|
||||
'translate-x-1/2 hover:bg-chalkboard-10 hover:dark:bg-chalkboard-110 bg-transparent transition-colors duration-75 transition-ease-out delay-100 ',
|
||||
left: 'hidden',
|
||||
top: 'hidden',
|
||||
@ -82,11 +79,10 @@ function ModelingSidebarSection({
|
||||
const { settings } = useSettingsAuthContext()
|
||||
const showDebugPanel = settings.context.modeling.showDebugPanel
|
||||
const paneIds = panes.map((pane) => pane.id)
|
||||
const { openPanes, setOpenPanes } = useStore((s) => ({
|
||||
openPanes: s.openPanes,
|
||||
setOpenPanes: s.setOpenPanes,
|
||||
}))
|
||||
const foundOpenPane = openPanes.find((pane) => paneIds.includes(pane))
|
||||
const { send, context } = useModelingContext()
|
||||
const foundOpenPane = context.store?.openPanes.find((pane) =>
|
||||
paneIds.includes(pane)
|
||||
)
|
||||
const [currentPane, setCurrentPane] = useState(
|
||||
foundOpenPane || ('none' as SidebarType | 'none')
|
||||
)
|
||||
@ -94,17 +90,37 @@ function ModelingSidebarSection({
|
||||
const togglePane = useCallback(
|
||||
(newPane: SidebarType | 'none') => {
|
||||
if (newPane === 'none') {
|
||||
setOpenPanes(openPanes.filter((p) => p !== currentPane))
|
||||
send({
|
||||
type: 'Set context',
|
||||
data: {
|
||||
openPanes: context.store?.openPanes.filter(
|
||||
(p) => p !== currentPane
|
||||
),
|
||||
},
|
||||
})
|
||||
setCurrentPane('none')
|
||||
} else if (newPane === currentPane) {
|
||||
setCurrentPane('none')
|
||||
setOpenPanes(openPanes.filter((p) => p !== newPane))
|
||||
send({
|
||||
type: 'Set context',
|
||||
data: {
|
||||
openPanes: context.store?.openPanes.filter((p) => p !== newPane),
|
||||
},
|
||||
})
|
||||
} else {
|
||||
setOpenPanes([...openPanes.filter((p) => p !== currentPane), newPane])
|
||||
send({
|
||||
type: 'Set context',
|
||||
data: {
|
||||
openPanes: [
|
||||
...context.store?.openPanes.filter((p) => p !== currentPane),
|
||||
newPane,
|
||||
],
|
||||
},
|
||||
})
|
||||
setCurrentPane(newPane)
|
||||
}
|
||||
},
|
||||
[openPanes, setOpenPanes, currentPane, setCurrentPane]
|
||||
[context.store?.openPanes, send, currentPane, setCurrentPane]
|
||||
)
|
||||
|
||||
// Filter out the debug panel if it's not supposed to be shown
|
||||
@ -122,11 +138,11 @@ function ModelingSidebarSection({
|
||||
if (
|
||||
!showDebugPanel.current &&
|
||||
currentPane === 'debug' &&
|
||||
openPanes.includes('debug')
|
||||
context.store?.openPanes.includes('debug')
|
||||
) {
|
||||
togglePane('debug')
|
||||
}
|
||||
}, [showDebugPanel.current, togglePane, openPanes])
|
||||
}, [showDebugPanel.current, togglePane, context.store?.openPanes])
|
||||
|
||||
return (
|
||||
<div className={'group contents ' + className} {...props}>
|
||||
@ -152,7 +168,9 @@ function ModelingSidebarSection({
|
||||
: ' border-r-0') +
|
||||
' p-2 col-start-1 col-span-1 h-fit w-fit flex flex-col items-start gap-2 ' +
|
||||
'bg-chalkboard-10 border border-solid border-chalkboard-20 dark:bg-chalkboard-90 dark:border-chalkboard-80 group-focus-within:border-primary dark:group-focus-within:border-chalkboard-50 ' +
|
||||
(openPanes.length === 1 && currentPane === 'none' ? 'pr-0.5' : '')
|
||||
(context.store?.openPanes.length === 1 && currentPane === 'none'
|
||||
? 'pr-0.5'
|
||||
: '')
|
||||
}
|
||||
>
|
||||
<Tab key="none" className="sr-only">
|
||||
@ -172,7 +190,7 @@ function ModelingSidebarSection({
|
||||
as="article"
|
||||
className={
|
||||
'col-start-2 col-span-1 ' +
|
||||
(openPanes.length === 1
|
||||
(context.store?.openPanes.length === 1
|
||||
? currentPane !== 'none'
|
||||
? `row-start-1 row-end-3`
|
||||
: `hidden`
|
||||
|
@ -2,22 +2,17 @@ import { coreDump } from 'lang/wasm'
|
||||
import { CoreDumpManager } from 'lib/coredump'
|
||||
import { CustomIcon } from './CustomIcon'
|
||||
import { engineCommandManager } from 'lib/singletons'
|
||||
import React from 'react'
|
||||
import React, { useMemo } from 'react'
|
||||
import toast from 'react-hot-toast'
|
||||
import Tooltip from './Tooltip'
|
||||
import { useStore } from 'useStore'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
|
||||
export const RefreshButton = ({ children }: React.PropsWithChildren) => {
|
||||
const { auth } = useSettingsAuthContext()
|
||||
const token = auth?.context?.token
|
||||
const { htmlRef } = useStore((s) => ({
|
||||
htmlRef: s.htmlRef,
|
||||
}))
|
||||
const coreDumpManager = new CoreDumpManager(
|
||||
engineCommandManager,
|
||||
htmlRef,
|
||||
token
|
||||
const coreDumpManager = useMemo(
|
||||
() => new CoreDumpManager(engineCommandManager, token),
|
||||
[]
|
||||
)
|
||||
|
||||
async function refresh() {
|
||||
|
@ -175,7 +175,12 @@ export const SettingsAuthProviderBase = ({
|
||||
id: `${event.type}.success`,
|
||||
})
|
||||
},
|
||||
'Execute AST': () => kclManager.executeCode(true, true),
|
||||
'Execute AST': () => {
|
||||
kclManager.isFirstRender = true
|
||||
kclManager.executeCode(true, true).then(() => {
|
||||
kclManager.isFirstRender = false
|
||||
})
|
||||
},
|
||||
},
|
||||
services: {
|
||||
'Persist settings': (context) =>
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { MouseEventHandler, useEffect, useRef, useState } from 'react'
|
||||
import { useStore } from '../useStore'
|
||||
import { getNormalisedCoordinates } from '../lib/utils'
|
||||
import Loading from './Loading'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
@ -9,26 +8,15 @@ import { NetworkHealthState } from 'hooks/useNetworkStatus'
|
||||
import { ClientSideScene } from 'clientSideScene/ClientSideSceneComp'
|
||||
import { butName } from 'lib/cameraControls'
|
||||
import { sendSelectEventToEngine } from 'lib/selections'
|
||||
import { kclManager } from 'lib/singletons'
|
||||
|
||||
export const Stream = ({ className = '' }: { className?: string }) => {
|
||||
export const Stream = () => {
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [isFirstRender, setIsFirstRender] = useState(kclManager.isFirstRender)
|
||||
const [clickCoords, setClickCoords] = useState<{ x: number; y: number }>()
|
||||
const videoRef = useRef<HTMLVideoElement>(null)
|
||||
const {
|
||||
mediaStream,
|
||||
setButtonDownInStream,
|
||||
didDragInStream,
|
||||
setDidDragInStream,
|
||||
streamDimensions,
|
||||
} = useStore((s) => ({
|
||||
mediaStream: s.mediaStream,
|
||||
setButtonDownInStream: s.setButtonDownInStream,
|
||||
didDragInStream: s.didDragInStream,
|
||||
setDidDragInStream: s.setDidDragInStream,
|
||||
streamDimensions: s.streamDimensions,
|
||||
}))
|
||||
const { settings } = useSettingsAuthContext()
|
||||
const { state } = useModelingContext()
|
||||
const { state, send, context } = useModelingContext()
|
||||
const { overallState } = useNetworkContext()
|
||||
|
||||
const isNetworkOkay =
|
||||
@ -67,6 +55,10 @@ export const Stream = ({ className = '' }: { className?: string }) => {
|
||||
})
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
setIsFirstRender(kclManager.isFirstRender)
|
||||
}, [kclManager.isFirstRender])
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
typeof window === 'undefined' ||
|
||||
@ -74,9 +66,16 @@ export const Stream = ({ className = '' }: { className?: string }) => {
|
||||
)
|
||||
return
|
||||
if (!videoRef.current) return
|
||||
if (!mediaStream) return
|
||||
videoRef.current.srcObject = mediaStream
|
||||
}, [mediaStream])
|
||||
if (!context.store?.mediaStream) return
|
||||
videoRef.current.srcObject = context.store.mediaStream
|
||||
|
||||
send({
|
||||
type: 'Set context',
|
||||
data: {
|
||||
videoElement: videoRef.current,
|
||||
},
|
||||
})
|
||||
}, [context.store?.mediaStream])
|
||||
|
||||
const handleMouseDown: MouseEventHandler<HTMLDivElement> = (e) => {
|
||||
if (!isNetworkOkay) return
|
||||
@ -88,25 +87,44 @@ export const Stream = ({ className = '' }: { className?: string }) => {
|
||||
clientX: e.clientX,
|
||||
clientY: e.clientY,
|
||||
el: videoRef.current,
|
||||
...streamDimensions,
|
||||
...context.store?.streamDimensions,
|
||||
})
|
||||
|
||||
setButtonDownInStream(e.button)
|
||||
send({
|
||||
type: 'Set context',
|
||||
data: {
|
||||
buttonDownInStream: e.button,
|
||||
},
|
||||
})
|
||||
setClickCoords({ x, y })
|
||||
}
|
||||
|
||||
const handleMouseUp: MouseEventHandler<HTMLDivElement> = (e) => {
|
||||
if (!isNetworkOkay) return
|
||||
if (!videoRef.current) return
|
||||
setButtonDownInStream(undefined)
|
||||
send({
|
||||
type: 'Set context',
|
||||
data: {
|
||||
buttonDownInStream: undefined,
|
||||
},
|
||||
})
|
||||
if (state.matches('Sketch')) return
|
||||
if (state.matches('Sketch no face')) return
|
||||
|
||||
if (!didDragInStream && butName(e).left) {
|
||||
sendSelectEventToEngine(e, videoRef.current, streamDimensions)
|
||||
if (!context.store?.didDragInStream && butName(e).left) {
|
||||
sendSelectEventToEngine(
|
||||
e,
|
||||
videoRef.current,
|
||||
context.store?.streamDimensions
|
||||
)
|
||||
}
|
||||
|
||||
setDidDragInStream(false)
|
||||
send({
|
||||
type: 'Set context',
|
||||
data: {
|
||||
didDragInStream: false,
|
||||
},
|
||||
})
|
||||
setClickCoords(undefined)
|
||||
}
|
||||
|
||||
@ -120,8 +138,13 @@ export const Stream = ({ className = '' }: { className?: string }) => {
|
||||
((clickCoords.x - e.clientX) ** 2 + (clickCoords.y - e.clientY) ** 2) **
|
||||
0.5
|
||||
|
||||
if (delta > 5 && !didDragInStream) {
|
||||
setDidDragInStream(true)
|
||||
if (delta > 5 && !context.store?.didDragInStream) {
|
||||
send({
|
||||
type: 'Set context',
|
||||
data: {
|
||||
didDragInStream: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -156,10 +179,14 @@ export const Stream = ({ className = '' }: { className?: string }) => {
|
||||
</Loading>
|
||||
</div>
|
||||
)}
|
||||
{isLoading && (
|
||||
<div className="text-center absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2">
|
||||
{(isLoading || isFirstRender) && (
|
||||
<div className="text-center absolute inset-0">
|
||||
<Loading>
|
||||
<span data-testid="loading-stream">Loading stream...</span>
|
||||
{!isLoading && isFirstRender ? (
|
||||
<span data-testid="loading-stream">Building scene...</span>
|
||||
) : (
|
||||
<span data-testid="loading-stream">Loading stream...</span>
|
||||
)}
|
||||
</Loading>
|
||||
</div>
|
||||
)}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { toolTips } from '../../useStore'
|
||||
import { toolTips } from 'lang/langHelpers'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { Program, Value, VariableDeclarator } from '../../lang/wasm'
|
||||
import {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { toolTips } from '../../useStore'
|
||||
import { toolTips } from 'lang/langHelpers'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { Program, Value, VariableDeclarator } from '../../lang/wasm'
|
||||
import {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { toolTips } from '../../useStore'
|
||||
import { toolTips } from 'lang/langHelpers'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { Program, ProgramMemory, Value } from '../../lang/wasm'
|
||||
import {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { toolTips } from '../../useStore'
|
||||
import { toolTips } from 'lang/langHelpers'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { BinaryPart, Program, Value, VariableDeclarator } from '../../lang/wasm'
|
||||
import {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { toolTips } from '../../useStore'
|
||||
import { toolTips } from 'lang/langHelpers'
|
||||
import { Selection, Selections } from 'lib/selections'
|
||||
import { PathToNode, Program, Value } from '../../lang/wasm'
|
||||
import {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { toolTips } from '../../useStore'
|
||||
import { toolTips } from 'lang/langHelpers'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { BinaryPart, Program, Value } from '../../lang/wasm'
|
||||
import {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { toolTips } from '../../useStore'
|
||||
import { toolTips } from 'lang/langHelpers'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { BinaryPart, Program, Value, VariableDeclarator } from '../../lang/wasm'
|
||||
import {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { toolTips } from '../../useStore'
|
||||
import { toolTips } from 'lang/langHelpers'
|
||||
import { BinaryPart, Program, Value, VariableDeclarator } from '../../lang/wasm'
|
||||
import {
|
||||
getNodePathFromSourceRange,
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { toolTips } from '../../useStore'
|
||||
import { toolTips } from 'lang/langHelpers'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { BinaryPart, Program, Value } from '../../lang/wasm'
|
||||
import {
|
||||
|
@ -27,6 +27,7 @@ function diagnosticIsEqual(d1: Diagnostic, d2: Diagnostic): boolean {
|
||||
|
||||
export default class EditorManager {
|
||||
private _editorView: EditorView | null = null
|
||||
private _copilotEnabled: boolean = true
|
||||
|
||||
private _isShiftDown: boolean = false
|
||||
private _selectionRanges: Selections = {
|
||||
@ -47,6 +48,14 @@ export default class EditorManager {
|
||||
|
||||
private _highlightRange: [number, number] = [0, 0]
|
||||
|
||||
setCopilotEnabled(enabled: boolean) {
|
||||
this._copilotEnabled = enabled
|
||||
}
|
||||
|
||||
get copilotEnabled(): boolean {
|
||||
return this._copilotEnabled
|
||||
}
|
||||
|
||||
setEditorView(editorView: EditorView) {
|
||||
this._editorView = editorView
|
||||
}
|
||||
@ -213,11 +222,7 @@ export default class EditorManager {
|
||||
return
|
||||
}
|
||||
|
||||
const ignoreEvents: ModelingMachineEvent['type'][] = [
|
||||
'Equip Line tool',
|
||||
'Equip tangential arc to',
|
||||
'Equip rectangle tool',
|
||||
]
|
||||
const ignoreEvents: ModelingMachineEvent['type'][] = ['change tool']
|
||||
|
||||
if (!this._modelingEvent) {
|
||||
return
|
||||
|
@ -23,23 +23,19 @@ import {
|
||||
} from '@codemirror/state'
|
||||
import { completionStatus } from '@codemirror/autocomplete'
|
||||
import {
|
||||
TransactionAnnotation,
|
||||
offsetToPos,
|
||||
posToOffset,
|
||||
LanguageServerOptions,
|
||||
LanguageServerClient,
|
||||
docPathFacet,
|
||||
languageId,
|
||||
TransactionInfo,
|
||||
updateInfo,
|
||||
RelevantUpdate,
|
||||
lspPlugin,
|
||||
} from '@kittycad/codemirror-lsp-client'
|
||||
import { deferExecution } from 'lib/utils'
|
||||
import { CopilotLspCompletionParams } from 'wasm-lib/kcl/bindings/CopilotLspCompletionParams'
|
||||
import { CopilotCompletionResponse } from 'wasm-lib/kcl/bindings/CopilotCompletionResponse'
|
||||
import { CopilotAcceptCompletionParams } from 'wasm-lib/kcl/bindings/CopilotAcceptCompletionParams'
|
||||
import { CopilotRejectCompletionParams } from 'wasm-lib/kcl/bindings/CopilotRejectCompletionParams'
|
||||
import { editorManager } from 'lib/singletons'
|
||||
|
||||
const copilotPluginAnnotation = Annotation.define<null>()
|
||||
export const copilotPluginEvent = copilotPluginAnnotation.of(null)
|
||||
@ -97,11 +93,6 @@ const completionDecoration = StateField.define<CompletionState>({
|
||||
return state
|
||||
}
|
||||
|
||||
// We only care about transactions with effects.
|
||||
if (!transaction.effects) {
|
||||
return state
|
||||
}
|
||||
|
||||
for (const effect of transaction.effects) {
|
||||
if (effect.is(addSuggestion)) {
|
||||
// When adding a suggestion, we set th ghostText
|
||||
@ -197,90 +188,68 @@ const completionDecoration = StateField.define<CompletionState>({
|
||||
),
|
||||
})
|
||||
|
||||
export const relevantUpdate = (update: ViewUpdate): RelevantUpdate => {
|
||||
const infos = updateInfo(update)
|
||||
|
||||
// Make sure we are not in a snippet
|
||||
if (infos.some((info: TransactionInfo) => info.inSnippet)) {
|
||||
return {
|
||||
overall: false,
|
||||
userSelect: false,
|
||||
time: null,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
overall: infos.some(
|
||||
(info: TransactionInfo) =>
|
||||
info.transaction.annotation(copilotPluginEvent.type) !== undefined ||
|
||||
info.annotations.includes(TransactionAnnotation.UserSelect) ||
|
||||
info.annotations.includes(TransactionAnnotation.UserInput) ||
|
||||
info.annotations.includes(TransactionAnnotation.UserDelete) ||
|
||||
info.annotations.includes(TransactionAnnotation.UserUndo) ||
|
||||
info.annotations.includes(TransactionAnnotation.UserRedo) ||
|
||||
info.annotations.includes(TransactionAnnotation.UserMove)
|
||||
),
|
||||
userSelect: infos.some((info: TransactionInfo) =>
|
||||
info.annotations.includes(TransactionAnnotation.UserSelect)
|
||||
),
|
||||
time: infos.length ? infos[0].time : null,
|
||||
}
|
||||
}
|
||||
|
||||
// A view plugin that requests completions from the server after a delay
|
||||
export class CompletionRequester implements PluginValue {
|
||||
private client: LanguageServerClient
|
||||
private lastPos: number = 0
|
||||
private viewUpdate: ViewUpdate | null = null
|
||||
|
||||
private queuedUids: string[] = []
|
||||
|
||||
private _deffererCodeUpdate = deferExecution(() => {
|
||||
if (this.viewUpdate === null) {
|
||||
return
|
||||
}
|
||||
|
||||
this.requestCompletions()
|
||||
}, changesDelay)
|
||||
|
||||
private _deffererUserSelect = deferExecution(() => {
|
||||
if (this.viewUpdate === null) {
|
||||
return
|
||||
}
|
||||
|
||||
this.rejectSuggestionCommand()
|
||||
}, changesDelay)
|
||||
|
||||
constructor(client: LanguageServerClient) {
|
||||
constructor(readonly view: EditorView, client: LanguageServerClient) {
|
||||
this.client = client
|
||||
}
|
||||
|
||||
update(viewUpdate: ViewUpdate) {
|
||||
this.viewUpdate = viewUpdate
|
||||
|
||||
const isRelevant = relevantUpdate(viewUpdate)
|
||||
if (!isRelevant.overall) {
|
||||
// Make sure we are in a state where we can request completions.
|
||||
if (!editorManager.copilotEnabled) {
|
||||
return
|
||||
}
|
||||
|
||||
let isUserSelect = false
|
||||
let isRelevant = false
|
||||
for (const tr of viewUpdate.transactions) {
|
||||
if (tr.isUserEvent('select')) {
|
||||
isUserSelect = true
|
||||
break
|
||||
} else if (tr.isUserEvent('input')) {
|
||||
isRelevant = true
|
||||
} else if (tr.isUserEvent('delete')) {
|
||||
isRelevant = true
|
||||
} else if (tr.isUserEvent('undo')) {
|
||||
isRelevant = true
|
||||
} else if (tr.isUserEvent('redo')) {
|
||||
isRelevant = true
|
||||
} else if (tr.isUserEvent('move')) {
|
||||
isRelevant = true
|
||||
} else if (tr.annotation(copilotPluginEvent.type) !== undefined) {
|
||||
isRelevant = true
|
||||
}
|
||||
}
|
||||
|
||||
// If we have a user select event, we want to clear the ghost text.
|
||||
if (isRelevant.userSelect) {
|
||||
if (isUserSelect) {
|
||||
this._deffererUserSelect(true)
|
||||
return
|
||||
}
|
||||
|
||||
this.lastPos = this.viewUpdate.state.selection.main.head
|
||||
this._deffererCodeUpdate(true)
|
||||
if (!isRelevant) {
|
||||
return
|
||||
}
|
||||
|
||||
this.lastPos = this.view.state.selection.main.head
|
||||
if (viewUpdate.docChanged) this._deffererCodeUpdate(true)
|
||||
}
|
||||
|
||||
ghostText(): GhostText | null {
|
||||
if (!this.viewUpdate) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
this.viewUpdate.view.state.field(completionDecoration)?.ghostText || null
|
||||
)
|
||||
return this.view.state.field(completionDecoration)?.ghostText || null
|
||||
}
|
||||
|
||||
containsGhostText(): boolean {
|
||||
@ -288,33 +257,23 @@ export class CompletionRequester implements PluginValue {
|
||||
}
|
||||
|
||||
autocompleting(): boolean {
|
||||
if (!this.viewUpdate) {
|
||||
return false
|
||||
}
|
||||
|
||||
return completionStatus(this.viewUpdate.state) === 'active'
|
||||
return completionStatus(this.view.state) === 'active'
|
||||
}
|
||||
|
||||
notFocused(): boolean {
|
||||
if (!this.viewUpdate) {
|
||||
return true
|
||||
}
|
||||
|
||||
return !this.viewUpdate.view.hasFocus
|
||||
return !this.view.hasFocus
|
||||
}
|
||||
|
||||
async requestCompletions(): Promise<void> {
|
||||
if (
|
||||
this.viewUpdate === null ||
|
||||
this.containsGhostText() ||
|
||||
this.autocompleting() ||
|
||||
this.notFocused() ||
|
||||
!this.viewUpdate.docChanged
|
||||
this.notFocused()
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
const pos = this.viewUpdate.state.selection.main.head
|
||||
const pos = this.view.state.selection.main.head
|
||||
|
||||
// Check if the position has changed
|
||||
if (pos !== this.lastPos) {
|
||||
@ -322,7 +281,7 @@ export class CompletionRequester implements PluginValue {
|
||||
}
|
||||
|
||||
// Get the current position and source
|
||||
const state = this.viewUpdate.state
|
||||
const state = this.view.state
|
||||
const dUri = state.facet(docPathFacet)
|
||||
|
||||
// Request completion from the server
|
||||
@ -390,14 +349,14 @@ export class CompletionRequester implements PluginValue {
|
||||
|
||||
// Dispatch an effect to add the suggestion
|
||||
// If the completion starts before the end of the line, check the end of the line with the end of the completion.
|
||||
const line = this.viewUpdate.view.state.doc.lineAt(pos)
|
||||
const line = this.view.state.doc.lineAt(pos)
|
||||
if (line.to !== pos) {
|
||||
const ending = this.viewUpdate.view.state.doc.sliceString(pos, line.to)
|
||||
const ending = this.view.state.doc.sliceString(pos, line.to)
|
||||
if (displayText.endsWith(ending)) {
|
||||
displayText = displayText.slice(0, displayText.length - ending.length)
|
||||
} else if (displayText.includes(ending)) {
|
||||
// Remove the ending
|
||||
this.viewUpdate.view.dispatch({
|
||||
this.view.dispatch({
|
||||
changes: {
|
||||
from: pos,
|
||||
to: line.to,
|
||||
@ -410,7 +369,7 @@ export class CompletionRequester implements PluginValue {
|
||||
}
|
||||
}
|
||||
|
||||
this.viewUpdate.view.dispatch({
|
||||
this.view.dispatch({
|
||||
changes: {
|
||||
from: pos,
|
||||
to: pos,
|
||||
@ -436,10 +395,6 @@ export class CompletionRequester implements PluginValue {
|
||||
}
|
||||
|
||||
acceptSuggestionCommand(): boolean {
|
||||
if (!this.viewUpdate) {
|
||||
return false
|
||||
}
|
||||
|
||||
const ghostText = this.ghostText()
|
||||
if (!ghostText) {
|
||||
return false
|
||||
@ -457,7 +412,7 @@ export class CompletionRequester implements PluginValue {
|
||||
|
||||
const suggestion = ghostText.text
|
||||
|
||||
this.viewUpdate.view.dispatch({
|
||||
this.view.dispatch({
|
||||
changes: {
|
||||
from: ghostTextStart,
|
||||
to: ghostTextEnd,
|
||||
@ -469,7 +424,7 @@ export class CompletionRequester implements PluginValue {
|
||||
|
||||
const tmpTextEnd = replacementEnd - (ghostTextEnd - ghostTextStart)
|
||||
|
||||
this.viewUpdate.view.dispatch({
|
||||
this.view.dispatch({
|
||||
changes: {
|
||||
from: actualTextStart,
|
||||
to: tmpTextEnd,
|
||||
@ -484,10 +439,6 @@ export class CompletionRequester implements PluginValue {
|
||||
}
|
||||
|
||||
rejectSuggestionCommand(): boolean {
|
||||
if (!this.viewUpdate) {
|
||||
return false
|
||||
}
|
||||
|
||||
const ghostText = this.ghostText()
|
||||
if (!ghostText) {
|
||||
return false
|
||||
@ -497,7 +448,7 @@ export class CompletionRequester implements PluginValue {
|
||||
const ghostTextStart = ghostText.displayPos
|
||||
const ghostTextEnd = ghostText.endGhostText
|
||||
|
||||
this.viewUpdate.view.dispatch({
|
||||
this.view.dispatch({
|
||||
changes: {
|
||||
from: ghostTextStart,
|
||||
to: ghostTextEnd,
|
||||
@ -515,10 +466,6 @@ export class CompletionRequester implements PluginValue {
|
||||
}
|
||||
|
||||
sameKeyCommand(key: string) {
|
||||
if (!this.viewUpdate) {
|
||||
return false
|
||||
}
|
||||
|
||||
const ghostText = this.ghostText()
|
||||
if (!ghostText) {
|
||||
return false
|
||||
@ -528,10 +475,10 @@ export class CompletionRequester implements PluginValue {
|
||||
|
||||
// When we type a key that is the same as the first letter of the suggestion, we delete the first letter of the suggestion and carry through with the original keypress
|
||||
const ghostTextStart = ghostText.displayPos
|
||||
const indent = this.viewUpdate.view.state.facet(indentUnit)
|
||||
const indent = this.view.state.facet(indentUnit)
|
||||
|
||||
if (key === tabKey && ghostText.displayText.startsWith(indent)) {
|
||||
this.viewUpdate.view.dispatch({
|
||||
this.view.dispatch({
|
||||
selection: { anchor: ghostTextStart + indent.length },
|
||||
effects: typeFirst.of(indent.length),
|
||||
annotations: [copilotPluginEvent, Transaction.addToHistory.of(false)],
|
||||
@ -545,7 +492,7 @@ export class CompletionRequester implements PluginValue {
|
||||
return this.acceptSuggestionCommand()
|
||||
} else {
|
||||
// Use this to delete the first letter of the suggestion
|
||||
this.viewUpdate.view.dispatch({
|
||||
this.view.dispatch({
|
||||
selection: { anchor: ghostTextStart + 1 },
|
||||
effects: typeFirst.of(1),
|
||||
annotations: [copilotPluginEvent, Transaction.addToHistory.of(false)],
|
||||
@ -592,7 +539,7 @@ export class CompletionRequester implements PluginValue {
|
||||
export const copilotPlugin = (options: LanguageServerOptions): Extension => {
|
||||
let plugin: CompletionRequester | null = null
|
||||
const completionPlugin = ViewPlugin.define(
|
||||
(view) => (plugin = new CompletionRequester(options.client))
|
||||
(view) => (plugin = new CompletionRequester(view, options.client))
|
||||
)
|
||||
|
||||
const domHandlers = EditorView.domEventHandlers({
|
||||
@ -618,6 +565,14 @@ export const copilotPlugin = (options: LanguageServerOptions): Extension => {
|
||||
},
|
||||
})
|
||||
|
||||
const rejectSuggestionCommand = (view: EditorView): boolean => {
|
||||
// Get the current plugin from the map.
|
||||
const p = view.plugin(completionPlugin)
|
||||
if (p === null) return false
|
||||
|
||||
return p.rejectSuggestionCommand()
|
||||
}
|
||||
|
||||
const copilotAutocompleteKeymap: readonly KeyBinding[] = [
|
||||
{
|
||||
key: 'Tab',
|
||||
@ -633,27 +588,30 @@ export const copilotPlugin = (options: LanguageServerOptions): Extension => {
|
||||
},
|
||||
{
|
||||
key: 'Backspace',
|
||||
run: (view: EditorView): boolean => {
|
||||
if (view.plugin === null) return false
|
||||
|
||||
// Get the current plugin from the map.
|
||||
const p = view.plugin(completionPlugin)
|
||||
if (p === null) return false
|
||||
|
||||
return p.rejectSuggestionCommand()
|
||||
},
|
||||
run: rejectSuggestionCommand,
|
||||
},
|
||||
{
|
||||
key: 'Delete',
|
||||
run: (view: EditorView): boolean => {
|
||||
if (view.plugin === null) return false
|
||||
|
||||
// Get the current plugin from the map.
|
||||
const p = view.plugin(completionPlugin)
|
||||
if (p === null) return false
|
||||
|
||||
return p.rejectSuggestionCommand()
|
||||
},
|
||||
run: rejectSuggestionCommand,
|
||||
},
|
||||
{ key: 'Mod-z', run: rejectSuggestionCommand, preventDefault: true },
|
||||
{
|
||||
key: 'Mod-y',
|
||||
mac: 'Mod-Shift-z',
|
||||
run: rejectSuggestionCommand,
|
||||
preventDefault: true,
|
||||
},
|
||||
{
|
||||
linux: 'Ctrl-Shift-z',
|
||||
run: rejectSuggestionCommand,
|
||||
preventDefault: true,
|
||||
},
|
||||
{ key: 'Mod-u', run: rejectSuggestionCommand, preventDefault: true },
|
||||
{
|
||||
key: 'Alt-u',
|
||||
mac: 'Mod-Shift-u',
|
||||
run: rejectSuggestionCommand,
|
||||
preventDefault: true,
|
||||
},
|
||||
]
|
||||
|
||||
@ -662,7 +620,6 @@ export const copilotPlugin = (options: LanguageServerOptions): Extension => {
|
||||
)
|
||||
|
||||
return [
|
||||
lspPlugin(options),
|
||||
completionPlugin,
|
||||
copilotAutocompleteKeymapExt,
|
||||
domHandlers,
|
||||
|
@ -2,12 +2,9 @@ import { Extension } from '@codemirror/state'
|
||||
import { ViewPlugin, PluginValue, ViewUpdate } from '@codemirror/view'
|
||||
import {
|
||||
LanguageServerOptions,
|
||||
updateInfo,
|
||||
TransactionInfo,
|
||||
RelevantUpdate,
|
||||
TransactionAnnotation,
|
||||
LanguageServerClient,
|
||||
lspPlugin,
|
||||
lspFormatCodeEvent,
|
||||
} from '@kittycad/codemirror-lsp-client'
|
||||
import { deferExecution } from 'lib/utils'
|
||||
import { codeManager, editorManager, kclManager } from 'lib/singletons'
|
||||
@ -18,34 +15,6 @@ import { UpdateCanExecuteResponse } from 'wasm-lib/kcl/bindings/UpdateCanExecute
|
||||
|
||||
const changesDelay = 600
|
||||
|
||||
export const relevantUpdate = (update: ViewUpdate): RelevantUpdate => {
|
||||
const infos = updateInfo(update)
|
||||
// Make sure we are not in a snippet
|
||||
if (infos.some((info: TransactionInfo) => info.inSnippet)) {
|
||||
return {
|
||||
overall: false,
|
||||
userSelect: false,
|
||||
time: null,
|
||||
}
|
||||
}
|
||||
return {
|
||||
overall: infos.some(
|
||||
(info: TransactionInfo) =>
|
||||
info.annotations.includes(TransactionAnnotation.UserSelect) ||
|
||||
info.annotations.includes(TransactionAnnotation.UserInput) ||
|
||||
info.annotations.includes(TransactionAnnotation.UserDelete) ||
|
||||
info.annotations.includes(TransactionAnnotation.UserUndo) ||
|
||||
info.annotations.includes(TransactionAnnotation.UserRedo) ||
|
||||
info.annotations.includes(TransactionAnnotation.UserMove) ||
|
||||
info.annotations.includes(TransactionAnnotation.FormatCode)
|
||||
),
|
||||
userSelect: infos.some((info: TransactionInfo) =>
|
||||
info.annotations.includes(TransactionAnnotation.UserSelect)
|
||||
),
|
||||
time: infos.length ? infos[0].time : null,
|
||||
}
|
||||
}
|
||||
|
||||
// A view plugin that requests completions from the server after a delay
|
||||
export class KclPlugin implements PluginValue {
|
||||
private viewUpdate: ViewUpdate | null = null
|
||||
@ -75,18 +44,38 @@ export class KclPlugin implements PluginValue {
|
||||
this.viewUpdate = viewUpdate
|
||||
editorManager.setEditorView(viewUpdate.view)
|
||||
|
||||
const isRelevant = relevantUpdate(viewUpdate)
|
||||
if (!isRelevant.overall) {
|
||||
return
|
||||
let isUserSelect = false
|
||||
let isRelevant = false
|
||||
for (const tr of viewUpdate.transactions) {
|
||||
if (tr.isUserEvent('select')) {
|
||||
isUserSelect = true
|
||||
break
|
||||
} else if (tr.isUserEvent('input')) {
|
||||
isRelevant = true
|
||||
} else if (tr.isUserEvent('delete')) {
|
||||
isRelevant = true
|
||||
} else if (tr.isUserEvent('undo')) {
|
||||
isRelevant = true
|
||||
} else if (tr.isUserEvent('redo')) {
|
||||
isRelevant = true
|
||||
} else if (tr.isUserEvent('move')) {
|
||||
isRelevant = true
|
||||
} else if (tr.annotation(lspFormatCodeEvent.type)) {
|
||||
isRelevant = true
|
||||
}
|
||||
}
|
||||
|
||||
// If we have a user select event, we want to update what parts are
|
||||
// highlighted.
|
||||
if (isRelevant.userSelect) {
|
||||
if (isUserSelect) {
|
||||
this._deffererUserSelect(true)
|
||||
return
|
||||
}
|
||||
|
||||
if (!isRelevant) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!viewUpdate.docChanged) {
|
||||
return
|
||||
}
|
||||
|
@ -46,15 +46,7 @@ class KclLanguage extends Language {
|
||||
|
||||
const parser = new KclParser()
|
||||
|
||||
super(
|
||||
data,
|
||||
// For now let's use the javascript parser.
|
||||
// It works really well and has good syntax highlighting.
|
||||
// We can use our lsp for the rest.
|
||||
parser,
|
||||
[plugin],
|
||||
'kcl'
|
||||
)
|
||||
super(data, parser, [plugin], 'kcl')
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,10 @@
|
||||
import { useLayoutEffect, useEffect, useRef } from 'react'
|
||||
import { useStore } from '../useStore'
|
||||
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'
|
||||
|
||||
export function useSetupEngineManager(
|
||||
streamRef: React.RefObject<HTMLDivElement>,
|
||||
@ -13,26 +14,20 @@ export function useSetupEngineManager(
|
||||
theme: Themes.System,
|
||||
highlightEdges: true,
|
||||
enableSSAO: true,
|
||||
modelingSend: (() => {}) as any,
|
||||
modelingContext: {} as any,
|
||||
showScaleGrid: false,
|
||||
} as {
|
||||
pool: string | null
|
||||
theme: Themes
|
||||
highlightEdges: boolean
|
||||
enableSSAO: boolean
|
||||
modelingSend: ReturnType<typeof useModelingContext>['send']
|
||||
modelingContext: ReturnType<typeof useModelingContext>['context']
|
||||
showScaleGrid: boolean
|
||||
}
|
||||
) {
|
||||
const {
|
||||
setMediaStream,
|
||||
setIsStreamReady,
|
||||
setStreamDimensions,
|
||||
streamDimensions,
|
||||
} = useStore((s) => ({
|
||||
setMediaStream: s.setMediaStream,
|
||||
setIsStreamReady: s.setIsStreamReady,
|
||||
setStreamDimensions: s.setStreamDimensions,
|
||||
streamDimensions: s.streamDimensions,
|
||||
}))
|
||||
const { setAppState } = useAppState()
|
||||
|
||||
const streamWidth = streamRef?.current?.offsetWidth
|
||||
const streamHeight = streamRef?.current?.offsetHeight
|
||||
@ -52,16 +47,28 @@ export function useSetupEngineManager(
|
||||
streamWidth,
|
||||
streamHeight
|
||||
)
|
||||
if (!hasSetNonZeroDimensions.current && quadHeight && quadWidth) {
|
||||
if (
|
||||
!hasSetNonZeroDimensions.current &&
|
||||
quadHeight &&
|
||||
quadWidth &&
|
||||
settings.modelingSend
|
||||
) {
|
||||
engineCommandManager.start({
|
||||
setMediaStream,
|
||||
setIsStreamReady,
|
||||
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.
|
||||
return kclManager.executeCode(true, true)
|
||||
kclManager.isFirstRender = true
|
||||
return kclManager.executeCode(true, true).then(() => {
|
||||
kclManager.isFirstRender = false
|
||||
})
|
||||
},
|
||||
token,
|
||||
settings,
|
||||
@ -72,9 +79,14 @@ export function useSetupEngineManager(
|
||||
return modifyGrid(kclManager.engineCommandManager, hidden)
|
||||
},
|
||||
})
|
||||
setStreamDimensions({
|
||||
streamWidth: quadWidth,
|
||||
streamHeight: quadHeight,
|
||||
settings.modelingSend({
|
||||
type: 'Set context',
|
||||
data: {
|
||||
streamDimensions: {
|
||||
streamWidth: quadWidth,
|
||||
streamHeight: quadHeight,
|
||||
},
|
||||
},
|
||||
})
|
||||
hasSetNonZeroDimensions.current = true
|
||||
}
|
||||
@ -83,6 +95,7 @@ export function useSetupEngineManager(
|
||||
useLayoutEffect(startEngineInstance, [
|
||||
streamRef?.current?.offsetWidth,
|
||||
streamRef?.current?.offsetHeight,
|
||||
settings.modelingSend,
|
||||
])
|
||||
|
||||
useEffect(() => {
|
||||
@ -92,16 +105,21 @@ export function useSetupEngineManager(
|
||||
streamRef?.current?.offsetHeight
|
||||
)
|
||||
if (
|
||||
streamDimensions.streamWidth !== width ||
|
||||
streamDimensions.streamHeight !== height
|
||||
settings.modelingContext.store.streamDimensions.streamWidth !== width ||
|
||||
settings.modelingContext.store.streamDimensions.streamHeight !== height
|
||||
) {
|
||||
engineCommandManager.handleResize({
|
||||
streamWidth: width,
|
||||
streamHeight: height,
|
||||
})
|
||||
setStreamDimensions({
|
||||
streamWidth: width,
|
||||
streamHeight: height,
|
||||
settings.modelingSend({
|
||||
type: 'Set context',
|
||||
data: {
|
||||
streamDimensions: {
|
||||
streamWidth: width,
|
||||
streamHeight: height,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}, 500)
|
||||
|
@ -8,9 +8,9 @@ import { settingsMachine } from 'machines/settingsMachine'
|
||||
import { homeMachine } from 'machines/homeMachine'
|
||||
import { Command, CommandSetConfig, CommandSetSchema } from 'lib/commandTypes'
|
||||
import { useKclContext } from 'lang/KclProvider'
|
||||
import { useStore } from 'useStore'
|
||||
import { useNetworkContext } from 'hooks/useNetworkContext'
|
||||
import { NetworkHealthState } from 'hooks/useNetworkStatus'
|
||||
import { useAppState } from 'AppState'
|
||||
|
||||
// This might not be necessary, AnyStateMachine from xstate is working
|
||||
export type AllMachines =
|
||||
@ -47,9 +47,7 @@ export default function useStateMachineCommands<
|
||||
const { commandBarSend } = useCommandsContext()
|
||||
const { overallState } = useNetworkContext()
|
||||
const { isExecuting } = useKclContext()
|
||||
const { isStreamReady } = useStore((s) => ({
|
||||
isStreamReady: s.isStreamReady,
|
||||
}))
|
||||
const { isStreamReady } = useAppState()
|
||||
|
||||
useEffect(() => {
|
||||
const disableAllButtons =
|
||||
|
@ -260,3 +260,8 @@ code {
|
||||
@apply bg-chalkboard-20 dark:bg-chalkboard-90;
|
||||
}
|
||||
}
|
||||
|
||||
#code-mirror-override .cm-scroller,
|
||||
#code-mirror-override .cm-editor {
|
||||
height: 100% !important;
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { executeAst, lintAst } from 'useStore'
|
||||
import { executeAst, lintAst } from 'lang/langHelpers'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { KCLError, kclErrorsToDiagnostics } from './errors'
|
||||
import { uuidv4 } from 'lib/utils'
|
||||
@ -15,6 +15,7 @@ import {
|
||||
ProgramMemory,
|
||||
recast,
|
||||
SketchGroup,
|
||||
SourceRange,
|
||||
ExtrudeGroup,
|
||||
} from 'lang/wasm'
|
||||
import { getNodeFromPath } from './queryAst'
|
||||
@ -65,6 +66,8 @@ export class KclManager {
|
||||
private _wasmInitFailedCallback: (arg: boolean) => void = () => {}
|
||||
private _executeCallback: () => void = () => {}
|
||||
|
||||
isFirstRender = true
|
||||
|
||||
get ast() {
|
||||
return this._ast
|
||||
}
|
||||
@ -194,7 +197,11 @@ export class KclManager {
|
||||
async executeAst(
|
||||
ast: Program = this._ast,
|
||||
zoomToFit?: boolean,
|
||||
executionId?: number
|
||||
executionId?: number,
|
||||
zoomOnRangeAndType?: {
|
||||
range: SourceRange
|
||||
type: string
|
||||
}
|
||||
): Promise<void> {
|
||||
await this?.engineCommandManager?.waitForReady
|
||||
const currentExecutionId = executionId || Date.now()
|
||||
@ -218,12 +225,20 @@ export class KclManager {
|
||||
defaultSelectionFilter(programMemory, this.engineCommandManager)
|
||||
|
||||
if (zoomToFit) {
|
||||
let zoomObjectId: string | undefined = ''
|
||||
if (zoomOnRangeAndType) {
|
||||
zoomObjectId = this.engineCommandManager?.mapRangeToObjectId(
|
||||
zoomOnRangeAndType.range,
|
||||
zoomOnRangeAndType.type
|
||||
)
|
||||
}
|
||||
|
||||
await this.engineCommandManager.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'zoom_to_fit',
|
||||
object_ids: [], // leave empty to zoom to all objects
|
||||
object_ids: zoomObjectId ? [zoomObjectId] : [], // leave empty to zoom to all objects
|
||||
padding: 0.1, // padding around the objects
|
||||
},
|
||||
})
|
||||
@ -357,6 +372,11 @@ export class KclManager {
|
||||
execute: boolean,
|
||||
optionalParams?: {
|
||||
focusPath?: PathToNode
|
||||
zoomToFit?: boolean
|
||||
zoomOnRangeAndType?: {
|
||||
range: SourceRange
|
||||
type: string
|
||||
}
|
||||
}
|
||||
): Promise<{
|
||||
newAst: Program
|
||||
@ -400,7 +420,12 @@ export class KclManager {
|
||||
codeManager.updateCodeEditor(newCode)
|
||||
// Write the file to disk.
|
||||
await codeManager.writeToFile()
|
||||
await this.executeAst(astWithUpdatedSource)
|
||||
await this.executeAst(
|
||||
astWithUpdatedSource,
|
||||
optionalParams?.zoomToFit,
|
||||
undefined,
|
||||
optionalParams?.zoomOnRangeAndType
|
||||
)
|
||||
} else {
|
||||
// When we don't re-execute, we still want to update the program
|
||||
// memory with the new ast. So we will hit the mock executor
|
||||
|
@ -6,9 +6,10 @@ import { isTauri } from 'lib/isTauri'
|
||||
import { writeTextFile } from '@tauri-apps/plugin-fs'
|
||||
import toast from 'react-hot-toast'
|
||||
import { editorManager } from 'lib/singletons'
|
||||
import { Annotation, KeyBinding, Transaction } from '@uiw/react-codemirror'
|
||||
import { Annotation, Transaction } from '@codemirror/state'
|
||||
import { KeyBinding } from '@codemirror/view'
|
||||
|
||||
const PERSIST_CODE_TOKEN = 'persistCode'
|
||||
const PERSIST_CODE_KEY = 'persistCode'
|
||||
|
||||
const codeManagerUpdateAnnotation = Annotation.define<null>()
|
||||
export const codeManagerUpdateEvent = codeManagerUpdateAnnotation.of(null)
|
||||
@ -25,7 +26,7 @@ export default class CodeManager {
|
||||
return
|
||||
}
|
||||
|
||||
const storedCode = safeLSGetItem(PERSIST_CODE_TOKEN)
|
||||
const storedCode = safeLSGetItem(PERSIST_CODE_KEY)
|
||||
// TODO #819 remove zustand persistence logic in a few months
|
||||
// short term migration, shouldn't make a difference for tauri app users
|
||||
// anyway since that's filesystem based.
|
||||
@ -125,7 +126,7 @@ export default class CodeManager {
|
||||
})
|
||||
})
|
||||
} else {
|
||||
safeLSSetItem(PERSIST_CODE_TOKEN, this.code)
|
||||
safeLSSetItem(PERSIST_CODE_KEY, this.code)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
120
src/lang/langHelpers.ts
Normal file
@ -0,0 +1,120 @@
|
||||
import {
|
||||
Program,
|
||||
_executor,
|
||||
ProgramMemory,
|
||||
programMemoryInit,
|
||||
kclLint,
|
||||
} from 'lang/wasm'
|
||||
import { enginelessExecutor } from 'lib/testHelpers'
|
||||
import { EngineCommandManager } from 'lang/std/engineConnection'
|
||||
import { KCLError } from 'lang/errors'
|
||||
import { Diagnostic } from '@codemirror/lint'
|
||||
|
||||
export type ToolTip =
|
||||
| 'lineTo'
|
||||
| 'line'
|
||||
| 'angledLine'
|
||||
| 'angledLineOfXLength'
|
||||
| 'angledLineOfYLength'
|
||||
| 'angledLineToX'
|
||||
| 'angledLineToY'
|
||||
| 'xLine'
|
||||
| 'yLine'
|
||||
| 'xLineTo'
|
||||
| 'yLineTo'
|
||||
| 'angledLineThatIntersects'
|
||||
| 'tangentialArcTo'
|
||||
|
||||
export const toolTips = [
|
||||
'sketch_line',
|
||||
'move',
|
||||
// original tooltips
|
||||
'line',
|
||||
'lineTo',
|
||||
'angledLine',
|
||||
'angledLineOfXLength',
|
||||
'angledLineOfYLength',
|
||||
'angledLineToX',
|
||||
'angledLineToY',
|
||||
'xLine',
|
||||
'yLine',
|
||||
'xLineTo',
|
||||
'yLineTo',
|
||||
'angledLineThatIntersects',
|
||||
'tangentialArcTo',
|
||||
] as any as ToolTip[]
|
||||
|
||||
export async function executeAst({
|
||||
ast,
|
||||
engineCommandManager,
|
||||
useFakeExecutor = false,
|
||||
programMemoryOverride,
|
||||
}: {
|
||||
ast: Program
|
||||
engineCommandManager: EngineCommandManager
|
||||
useFakeExecutor?: boolean
|
||||
programMemoryOverride?: ProgramMemory
|
||||
}): Promise<{
|
||||
logs: string[]
|
||||
errors: KCLError[]
|
||||
programMemory: ProgramMemory
|
||||
}> {
|
||||
try {
|
||||
if (!useFakeExecutor) {
|
||||
engineCommandManager.endSession()
|
||||
engineCommandManager.startNewSession()
|
||||
}
|
||||
const programMemory = await (useFakeExecutor
|
||||
? enginelessExecutor(ast, programMemoryOverride || programMemoryInit())
|
||||
: _executor(ast, programMemoryInit(), engineCommandManager, false))
|
||||
|
||||
await engineCommandManager.waitForAllCommands()
|
||||
return {
|
||||
logs: [],
|
||||
errors: [],
|
||||
programMemory,
|
||||
}
|
||||
} catch (e: any) {
|
||||
if (e instanceof KCLError) {
|
||||
return {
|
||||
errors: [e],
|
||||
logs: [],
|
||||
programMemory: {
|
||||
root: {},
|
||||
return: null,
|
||||
},
|
||||
}
|
||||
} else {
|
||||
console.log(e)
|
||||
return {
|
||||
logs: [e],
|
||||
errors: [],
|
||||
programMemory: {
|
||||
root: {},
|
||||
return: null,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function lintAst({
|
||||
ast,
|
||||
}: {
|
||||
ast: Program
|
||||
}): Promise<Array<Diagnostic>> {
|
||||
try {
|
||||
const discovered_findings = await kclLint(ast)
|
||||
return discovered_findings.map((lint) => {
|
||||
return {
|
||||
message: lint.finding.title,
|
||||
severity: 'info',
|
||||
from: lint.pos[0],
|
||||
to: lint.pos[1],
|
||||
}
|
||||
})
|
||||
} catch (e: any) {
|
||||
console.log(e)
|
||||
return []
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import { ToolTip } from '../useStore'
|
||||
import { ToolTip } from 'lang/langHelpers'
|
||||
import { Selection, Selections } from 'lib/selections'
|
||||
import {
|
||||
ArrayExpression,
|
||||
|
@ -2084,4 +2084,25 @@ export class EngineCommandManager extends EventTarget {
|
||||
setScaleGridVisibility(visible: boolean) {
|
||||
this.modifyGrid(!visible)
|
||||
}
|
||||
|
||||
// Some "objects" have the same source range, such as sketch_mode_start and start_path.
|
||||
// So when passing a range, we need to also specify the command type
|
||||
mapRangeToObjectId(
|
||||
range: SourceRange,
|
||||
commandTypeToTarget: string
|
||||
): string | undefined {
|
||||
const values = Object.entries(this.artifactMap)
|
||||
for (const [id, data] of values) {
|
||||
if (data.type !== 'result') continue
|
||||
|
||||
// Our range selection seems to just select the cursor position, so either
|
||||
// of these can be right...
|
||||
if (
|
||||
(data.range[0] === range[0] || data.range[1] === range[1]) &&
|
||||
data.commandType === commandTypeToTarget
|
||||
)
|
||||
return id
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ import {
|
||||
isLiteralArrayOrStatic,
|
||||
isNotLiteralArrayOrStatic,
|
||||
} from 'lang/std/sketchcombos'
|
||||
import { toolTips, ToolTip } from '../../useStore'
|
||||
import { toolTips, ToolTip } from 'lang/langHelpers'
|
||||
import { createPipeExpression, splitPathAtPipeExpression } from '../modifyAst'
|
||||
|
||||
import {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { getNodeFromPath } from 'lang/queryAst'
|
||||
import { ToolTip, toolTips } from '../../useStore'
|
||||
import { ToolTip, toolTips } from 'lang/langHelpers'
|
||||
import {
|
||||
Program,
|
||||
VariableDeclarator,
|
||||
|
@ -8,7 +8,7 @@ import {
|
||||
ConstraintLevel,
|
||||
getConstraintLevelFromSourceRange,
|
||||
} from './sketchcombos'
|
||||
import { ToolTip } from '../../useStore'
|
||||
import { ToolTip } from 'lang/langHelpers'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { err } from 'lib/trap'
|
||||
import { enginelessExecutor } from '../../lib/testHelpers'
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { TransformCallback, VarValues } from './stdTypes'
|
||||
import { toolTips, ToolTip } from '../../useStore'
|
||||
import { ToolTip, toolTips } from 'lang/langHelpers'
|
||||
import { Selections, Selection } from 'lib/selections'
|
||||
import { cleanErrs, err } from 'lib/trap'
|
||||
import {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ToolTip } from 'useStore'
|
||||
import { ToolTip } from 'lang/langHelpers'
|
||||
import {
|
||||
ProgramMemory,
|
||||
Path,
|
||||
|
@ -37,21 +37,22 @@ export const modelingMachineConfig: CommandSetConfig<
|
||||
description: 'Enter sketch mode.',
|
||||
icon: 'sketch',
|
||||
},
|
||||
'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',
|
||||
},
|
||||
// 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',
|
||||
// },
|
||||
Export: {
|
||||
description: 'Export the current model.',
|
||||
icon: 'exportFile',
|
||||
|
@ -10,7 +10,6 @@ import {
|
||||
import { APP_VERSION } from 'routes/Settings'
|
||||
import { UAParser } from 'ua-parser-js'
|
||||
import screenshot from 'lib/screenshot'
|
||||
import React from 'react'
|
||||
import { VITE_KC_API_BASE_URL } from 'env'
|
||||
|
||||
/* eslint-disable suggest-no-throw/suggest-no-throw --
|
||||
@ -33,17 +32,14 @@ import { VITE_KC_API_BASE_URL } from 'env'
|
||||
// TODO: Throw more
|
||||
export class CoreDumpManager {
|
||||
engineCommandManager: EngineCommandManager
|
||||
htmlRef: React.RefObject<HTMLDivElement> | null
|
||||
token: string | undefined
|
||||
baseUrl: string = VITE_KC_API_BASE_URL
|
||||
|
||||
constructor(
|
||||
engineCommandManager: EngineCommandManager,
|
||||
htmlRef: React.RefObject<HTMLDivElement> | null,
|
||||
token: string | undefined
|
||||
) {
|
||||
this.engineCommandManager = engineCommandManager
|
||||
this.htmlRef = htmlRef
|
||||
this.token = token
|
||||
}
|
||||
|
||||
@ -73,31 +69,14 @@ export class CoreDumpManager {
|
||||
// Get the os information.
|
||||
getOsInfo(): Promise<string> {
|
||||
if (this.isTauri()) {
|
||||
return tauriArch()
|
||||
.catch((error: any) => {
|
||||
throw new Error(`Error getting arch: ${error}`)
|
||||
})
|
||||
.then((arch: string) => {
|
||||
return tauriPlatform()
|
||||
.catch((error: any) => {
|
||||
throw new Error(`Error getting platform: ${error}`)
|
||||
})
|
||||
.then((platform: string) => {
|
||||
return tauriKernelVersion()
|
||||
.catch((error: any) => {
|
||||
throw new Error(`Error getting kernel version: ${error}`)
|
||||
})
|
||||
.then((kernelVersion: string) => {
|
||||
const osinfo: OsInfo = {
|
||||
platform,
|
||||
arch,
|
||||
browser: 'tauri',
|
||||
version: kernelVersion,
|
||||
}
|
||||
return JSON.stringify(osinfo)
|
||||
})
|
||||
})
|
||||
})
|
||||
const osinfo: OsInfo = {
|
||||
platform: tauriPlatform(),
|
||||
arch: tauriArch(),
|
||||
browser: 'tauri',
|
||||
version: tauriKernelVersion(),
|
||||
}
|
||||
return new Promise((resolve) => resolve(JSON.stringify(osinfo)))
|
||||
// TODO: get rid of promises now that the tauri api doesn't require them anymore
|
||||
}
|
||||
|
||||
const userAgent = window.navigator.userAgent || 'unknown browser'
|
||||
@ -449,12 +428,11 @@ export class CoreDumpManager {
|
||||
|
||||
// Return a data URL (png format) of the screenshot of the current page.
|
||||
screenshot(): Promise<string> {
|
||||
return screenshot(this.htmlRef)
|
||||
.then((screenshot: string) => {
|
||||
return screenshot
|
||||
})
|
||||
.catch((error: any) => {
|
||||
throw new Error(`Error getting screenshot: ${error}`)
|
||||
})
|
||||
return (
|
||||
screenshot()
|
||||
.then((screenshotStr: string) => screenshotStr)
|
||||
// maybe rust should handle an error, but an empty string at least doesn't cause the core dump to fail entirely
|
||||
.catch((error: any) => ``)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +1,15 @@
|
||||
import React from 'react'
|
||||
import html2canvas from 'html2canvas-pro'
|
||||
|
||||
// Return a data URL (png format) of the screenshot of the current page.
|
||||
export default async function screenshot(
|
||||
htmlRef: React.RefObject<HTMLDivElement> | null
|
||||
): Promise<string> {
|
||||
if (htmlRef === null) {
|
||||
return Promise.reject(new Error('htmlRef is null'))
|
||||
export default async function screenshot(): Promise<string> {
|
||||
if (typeof window === 'undefined') {
|
||||
return Promise.reject(
|
||||
new Error(
|
||||
"element isn't defined because there's no window, are you running in Node?"
|
||||
)
|
||||
)
|
||||
}
|
||||
if (htmlRef.current === null) {
|
||||
return Promise.reject(new Error('htmlRef is null'))
|
||||
}
|
||||
return html2canvas(htmlRef.current)
|
||||
return html2canvas(document.documentElement)
|
||||
.then((canvas) => {
|
||||
return canvas.toDataURL()
|
||||
})
|
||||
|
@ -8,8 +8,7 @@ import {
|
||||
import { CallExpression, SourceRange, Value, parse, recast } from 'lang/wasm'
|
||||
import { ModelingMachineEvent } from 'machines/modelingMachine'
|
||||
import { uuidv4 } from 'lib/utils'
|
||||
import { EditorSelection } from '@codemirror/state'
|
||||
import { SelectionRange } from '@uiw/react-codemirror'
|
||||
import { EditorSelection, SelectionRange } from '@codemirror/state'
|
||||
import { getNormalisedCoordinates, isOverlap } from 'lib/utils'
|
||||
import { isCursorInSketchCommandRange } from 'lang/util'
|
||||
import { Program } from 'lang/wasm'
|
||||
|
@ -11,6 +11,8 @@ export const engineCommandManager = new EngineCommandManager()
|
||||
|
||||
// This needs to be after codeManager is created.
|
||||
export const kclManager = new KclManager(engineCommandManager)
|
||||
kclManager.isFirstRender = true
|
||||
|
||||
engineCommandManager.getAstCb = () => kclManager.ast
|
||||
|
||||
export const sceneInfra = new SceneInfra(engineCommandManager)
|
||||
|
@ -5,7 +5,7 @@ import { findUniqueName } from 'lang/modifyAst'
|
||||
import { PrevVariable, findAllPreviousVariables } from 'lang/queryAst'
|
||||
import { Value, parse } from 'lang/wasm'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { executeAst } from 'useStore'
|
||||
import { executeAst } from 'lang/langHelpers'
|
||||
import { trap } from 'lib/trap'
|
||||
|
||||
const isValidVariableName = (name: string) =>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { FormEvent, useEffect } from 'react'
|
||||
import { FormEvent, useEffect, useRef } from 'react'
|
||||
import { remove } from '@tauri-apps/plugin-fs'
|
||||
import {
|
||||
getNextProjectIndex,
|
||||
@ -64,6 +64,7 @@ const Home = () => {
|
||||
splitKey: '|',
|
||||
}
|
||||
)
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
|
||||
const [state, send, actor] = useMachine(homeMachine, {
|
||||
context: {
|
||||
@ -198,7 +199,7 @@ const Home = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative flex flex-col h-screen overflow-hidden">
|
||||
<div className="relative flex flex-col h-screen overflow-hidden" ref={ref}>
|
||||
<AppHeader showToolbar={false} />
|
||||
<div className="w-full flex flex-col overflow-hidden max-w-5xl px-4 mx-auto mt-24 lg:px-2">
|
||||
<section>
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { OnboardingButtons, useDismiss, useNextClick } from '.'
|
||||
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||
import { useStore } from '../../useStore'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import {
|
||||
CameraSystem,
|
||||
@ -8,11 +7,10 @@ import {
|
||||
cameraSystems,
|
||||
} from 'lib/cameraControls'
|
||||
import { SettingsSection } from 'components/Settings/SettingsSection'
|
||||
import { useModelingContext } from 'hooks/useModelingContext'
|
||||
|
||||
export default function Units() {
|
||||
const { buttonDownInStream } = useStore((s) => ({
|
||||
buttonDownInStream: s.buttonDownInStream,
|
||||
}))
|
||||
const { context } = useModelingContext()
|
||||
const dismiss = useDismiss()
|
||||
const next = useNextClick(onboardingPaths.STREAMING)
|
||||
const {
|
||||
@ -31,7 +29,7 @@ export default function Units() {
|
||||
<div
|
||||
className={
|
||||
'max-w-2xl border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
|
||||
(buttonDownInStream ? '' : ' pointer-events-auto')
|
||||
(context.store?.buttonDownInStream ? '' : ' pointer-events-auto')
|
||||
}
|
||||
>
|
||||
<SettingsSection
|
||||
|
@ -1,12 +1,10 @@
|
||||
import usePlatform from 'hooks/usePlatform'
|
||||
import { OnboardingButtons, kbdClasses, useDismiss, useNextClick } from '.'
|
||||
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||
import { useStore } from '../../useStore'
|
||||
import { useModelingContext } from 'hooks/useModelingContext'
|
||||
|
||||
export default function CmdK() {
|
||||
const { buttonDownInStream } = useStore((s) => ({
|
||||
buttonDownInStream: s.buttonDownInStream,
|
||||
}))
|
||||
const { context } = useModelingContext()
|
||||
const dismiss = useDismiss()
|
||||
const next = useNextClick(onboardingPaths.USER_MENU)
|
||||
const platformName = usePlatform()
|
||||
@ -16,7 +14,7 @@ export default function CmdK() {
|
||||
<div
|
||||
className={
|
||||
'max-w-full xl:max-w-4xl border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
|
||||
(buttonDownInStream ? '' : ' pointer-events-auto')
|
||||
(context.store?.buttonDownInStream ? '' : ' pointer-events-auto')
|
||||
}
|
||||
>
|
||||
<h2 className="text-2xl font-bold">Command Bar</h2>
|
||||
|
@ -1,12 +1,10 @@
|
||||
import { useModelingContext } from 'hooks/useModelingContext'
|
||||
import { OnboardingButtons, useDemoCode, useDismiss, useNextClick } from '.'
|
||||
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||
import { useStore } from '../../useStore'
|
||||
|
||||
export default function OnboardingCodeEditor() {
|
||||
useDemoCode()
|
||||
const { buttonDownInStream } = useStore((s) => ({
|
||||
buttonDownInStream: s.buttonDownInStream,
|
||||
}))
|
||||
const { context } = useModelingContext()
|
||||
const dismiss = useDismiss()
|
||||
const next = useNextClick(onboardingPaths.PARAMETRIC_MODELING)
|
||||
|
||||
@ -15,7 +13,7 @@ export default function OnboardingCodeEditor() {
|
||||
<div
|
||||
className={
|
||||
'z-10 max-w-xl border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg h-[75vh] flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
|
||||
(buttonDownInStream ? '' : ' pointer-events-auto')
|
||||
(context.store?.buttonDownInStream ? '' : ' pointer-events-auto')
|
||||
}
|
||||
>
|
||||
<section className="flex-1 overflow-y-auto">
|
||||
|
@ -1,12 +1,10 @@
|
||||
import { APP_NAME } from 'lib/constants'
|
||||
import { OnboardingButtons, useDismiss, useNextClick } from '.'
|
||||
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||
import { useStore } from '../../useStore'
|
||||
import { useModelingContext } from 'hooks/useModelingContext'
|
||||
|
||||
export default function Export() {
|
||||
const { buttonDownInStream } = useStore((s) => ({
|
||||
buttonDownInStream: s.buttonDownInStream,
|
||||
}))
|
||||
const { context } = useModelingContext()
|
||||
const dismiss = useDismiss()
|
||||
const next = useNextClick(onboardingPaths.SKETCHING)
|
||||
|
||||
@ -15,7 +13,7 @@ export default function Export() {
|
||||
<div
|
||||
className={
|
||||
'max-w-full xl:max-w-2xl border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
|
||||
(buttonDownInStream ? '' : ' pointer-events-auto')
|
||||
(context.store?.buttonDownInStream ? '' : ' pointer-events-auto')
|
||||
}
|
||||
>
|
||||
<section className="flex-1">
|
||||
|
@ -6,14 +6,12 @@ import {
|
||||
useNextClick,
|
||||
} from '.'
|
||||
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||
import { useStore } from '../../useStore'
|
||||
import { bracketWidthConstantLine } from 'lib/exampleKcl'
|
||||
import { useModelingContext } from 'hooks/useModelingContext'
|
||||
|
||||
export default function OnboardingInteractiveNumbers() {
|
||||
useDemoCode()
|
||||
const { buttonDownInStream } = useStore((s) => ({
|
||||
buttonDownInStream: s.buttonDownInStream,
|
||||
}))
|
||||
const { context } = useModelingContext()
|
||||
const dismiss = useDismiss()
|
||||
const next = useNextClick(onboardingPaths.COMMAND_K)
|
||||
|
||||
@ -22,7 +20,7 @@ export default function OnboardingInteractiveNumbers() {
|
||||
<div
|
||||
className={
|
||||
'z-10 max-w-xl border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg h-[75vh] flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
|
||||
(buttonDownInStream ? '' : ' pointer-events-auto')
|
||||
(context.store?.buttonDownInStream ? '' : ' pointer-events-auto')
|
||||
}
|
||||
>
|
||||
<section className="flex-1 overflow-y-auto mb-6">
|
||||
|
@ -1,15 +1,13 @@
|
||||
import { OnboardingButtons, useDemoCode, useDismiss, useNextClick } from '.'
|
||||
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||
import { useStore } from '../../useStore'
|
||||
import { Themes, getSystemTheme } from 'lib/theme'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import { bracketThicknessCalculationLine } from 'lib/exampleKcl'
|
||||
import { useModelingContext } from 'hooks/useModelingContext'
|
||||
|
||||
export default function OnboardingParametricModeling() {
|
||||
useDemoCode()
|
||||
const { buttonDownInStream } = useStore((s) => ({
|
||||
buttonDownInStream: s.buttonDownInStream,
|
||||
}))
|
||||
const { context } = useModelingContext()
|
||||
const {
|
||||
settings: {
|
||||
context: {
|
||||
@ -32,7 +30,7 @@ export default function OnboardingParametricModeling() {
|
||||
<div
|
||||
className={
|
||||
'z-10 max-w-xl border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg h-[75vh] flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
|
||||
(buttonDownInStream ? '' : ' pointer-events-auto')
|
||||
(context.store?.buttonDownInStream ? '' : ' pointer-events-auto')
|
||||
}
|
||||
>
|
||||
<section className="flex-1 overflow-y-auto mb-6">
|
||||
|
@ -1,12 +1,10 @@
|
||||
import { OnboardingButtons, useDismiss, useNextClick } from '.'
|
||||
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||
import { useStore } from '../../useStore'
|
||||
import { isTauri } from 'lib/isTauri'
|
||||
import { useModelingContext } from 'hooks/useModelingContext'
|
||||
|
||||
export default function ProjectMenu() {
|
||||
const { buttonDownInStream } = useStore((s) => ({
|
||||
buttonDownInStream: s.buttonDownInStream,
|
||||
}))
|
||||
const { context } = useModelingContext()
|
||||
const dismiss = useDismiss()
|
||||
const next = useNextClick(onboardingPaths.EXPORT)
|
||||
const tauri = isTauri()
|
||||
@ -16,7 +14,7 @@ export default function ProjectMenu() {
|
||||
<div
|
||||
className={
|
||||
'max-w-xl flex flex-col border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
|
||||
(buttonDownInStream ? '' : ' pointer-events-auto')
|
||||
(context.store?.buttonDownInStream ? '' : ' pointer-events-auto')
|
||||
}
|
||||
>
|
||||
<section className="flex-1">
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { OnboardingButtons, useDismiss, useNextClick } from '.'
|
||||
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||
import { useStore } from 'useStore'
|
||||
import { useEffect } from 'react'
|
||||
import { codeManager, kclManager } from 'lib/singletons'
|
||||
import { useModelingContext } from 'hooks/useModelingContext'
|
||||
|
||||
export default function Sketching() {
|
||||
const buttonDownInStream = useStore((s) => s.buttonDownInStream)
|
||||
const { context } = useModelingContext()
|
||||
const dismiss = useDismiss()
|
||||
const next = useNextClick(onboardingPaths.FUTURE_WORK)
|
||||
|
||||
@ -23,7 +23,7 @@ export default function Sketching() {
|
||||
<div
|
||||
className={
|
||||
'max-w-full xl:max-w-2xl border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
|
||||
(buttonDownInStream ? '' : ' pointer-events-auto')
|
||||
(context.store?.buttonDownInStream ? '' : ' pointer-events-auto')
|
||||
}
|
||||
>
|
||||
<h1 className="text-2xl font-bold">Sketching</h1>
|
||||
|
@ -1,11 +1,9 @@
|
||||
import { useModelingContext } from 'hooks/useModelingContext'
|
||||
import { OnboardingButtons, useDismiss, useNextClick } from '.'
|
||||
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||
import { useStore } from '../../useStore'
|
||||
|
||||
export default function Streaming() {
|
||||
const { buttonDownInStream } = useStore((s) => ({
|
||||
buttonDownInStream: s.buttonDownInStream,
|
||||
}))
|
||||
const { context } = useModelingContext()
|
||||
const dismiss = useDismiss()
|
||||
const next = useNextClick(onboardingPaths.EDITOR)
|
||||
|
||||
@ -14,7 +12,7 @@ export default function Streaming() {
|
||||
<div
|
||||
className={
|
||||
'max-w-xl border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg h-[75vh] flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
|
||||
(buttonDownInStream ? '' : ' pointer-events-auto')
|
||||
(context.store?.buttonDownInStream ? '' : ' pointer-events-auto')
|
||||
}
|
||||
>
|
||||
<section className="flex-1 overflow-y-auto">
|
||||
|
@ -1,12 +1,10 @@
|
||||
import { OnboardingButtons, useDismiss, useNextClick } from '.'
|
||||
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||
import { useStore } from '../../useStore'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useModelingContext } from 'hooks/useModelingContext'
|
||||
|
||||
export default function UserMenu() {
|
||||
const { buttonDownInStream } = useStore((s) => ({
|
||||
buttonDownInStream: s.buttonDownInStream,
|
||||
}))
|
||||
const { context } = useModelingContext()
|
||||
const dismiss = useDismiss()
|
||||
const next = useNextClick(onboardingPaths.PROJECT_MENU)
|
||||
const [avatarErrored, setAvatarErrored] = useState(false)
|
||||
@ -33,7 +31,7 @@ export default function UserMenu() {
|
||||
<div
|
||||
className={
|
||||
'max-w-xl flex flex-col border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
|
||||
(buttonDownInStream ? '' : ' pointer-events-auto')
|
||||
(context.store?.buttonDownInStream ? '' : ' pointer-events-auto')
|
||||
}
|
||||
>
|
||||
<section className="flex-1">
|
||||
|
@ -80,8 +80,10 @@ export const onboardingRoutes = [
|
||||
export function useDemoCode() {
|
||||
useEffect(() => {
|
||||
if (!editorManager.editorView) return
|
||||
setTimeout(() => codeManager.updateCodeStateEditor(bracket))
|
||||
}, [editorManager.editorView, codeManager.updateCodeStateEditor])
|
||||
setTimeout(() => {
|
||||
codeManager.updateCodeStateEditor(bracket)
|
||||
})
|
||||
}, [editorManager.editorView])
|
||||
}
|
||||
|
||||
export function useNextClick(newStatus: string) {
|
||||
|
@ -12,8 +12,11 @@ import { SettingsSectionsList } from 'components/Settings/SettingsSectionsList'
|
||||
import { AllSettingsFields } from 'components/Settings/AllSettingsFields'
|
||||
import { AllKeybindingsFields } from 'components/Settings/AllKeybindingsFields'
|
||||
import { KeybindingsSectionsList } from 'components/Settings/KeybindingsSectionsList'
|
||||
import { isTauri } from 'lib/isTauri'
|
||||
|
||||
export const APP_VERSION = import.meta.env.PACKAGE_VERSION || 'unknown'
|
||||
export const APP_VERSION = isTauri()
|
||||
? import.meta.env.PACKAGE_VERSION || 'unknown'
|
||||
: 'main'
|
||||
|
||||
export const Settings = () => {
|
||||
const navigate = useNavigate()
|
||||
|
212
src/useStore.ts
@ -1,212 +0,0 @@
|
||||
import { create } from 'zustand'
|
||||
import { persist } from 'zustand/middleware'
|
||||
import {
|
||||
Program,
|
||||
_executor,
|
||||
ProgramMemory,
|
||||
programMemoryInit,
|
||||
kclLint,
|
||||
} from './lang/wasm'
|
||||
import { enginelessExecutor } from './lib/testHelpers'
|
||||
import { EngineCommandManager } from './lang/std/engineConnection'
|
||||
import { KCLError } from './lang/errors'
|
||||
import { SidebarType } from 'components/ModelingSidebar/ModelingPanes'
|
||||
import { Diagnostic } from '@codemirror/lint'
|
||||
|
||||
export type ToolTip =
|
||||
| 'lineTo'
|
||||
| 'line'
|
||||
| 'angledLine'
|
||||
| 'angledLineOfXLength'
|
||||
| 'angledLineOfYLength'
|
||||
| 'angledLineToX'
|
||||
| 'angledLineToY'
|
||||
| 'xLine'
|
||||
| 'yLine'
|
||||
| 'xLineTo'
|
||||
| 'yLineTo'
|
||||
| 'angledLineThatIntersects'
|
||||
| 'tangentialArcTo'
|
||||
|
||||
export const toolTips = [
|
||||
'sketch_line',
|
||||
'move',
|
||||
// original tooltips
|
||||
'line',
|
||||
'lineTo',
|
||||
'angledLine',
|
||||
'angledLineOfXLength',
|
||||
'angledLineOfYLength',
|
||||
'angledLineToX',
|
||||
'angledLineToY',
|
||||
'xLine',
|
||||
'yLine',
|
||||
'xLineTo',
|
||||
'yLineTo',
|
||||
'angledLineThatIntersects',
|
||||
'tangentialArcTo',
|
||||
] as any as ToolTip[]
|
||||
|
||||
export interface StoreState {
|
||||
mediaStream?: MediaStream
|
||||
setMediaStream: (mediaStream: MediaStream) => void
|
||||
isStreamReady: boolean
|
||||
setIsStreamReady: (isStreamReady: boolean) => void
|
||||
isKclLspServerReady: boolean
|
||||
isCopilotLspServerReady: boolean
|
||||
setIsKclLspServerReady: (isKclLspServerReady: boolean) => void
|
||||
setIsCopilotLspServerReady: (isCopilotLspServerReady: boolean) => void
|
||||
buttonDownInStream: number | undefined
|
||||
setButtonDownInStream: (buttonDownInStream: number | undefined) => void
|
||||
didDragInStream: boolean
|
||||
setDidDragInStream: (didDragInStream: boolean) => void
|
||||
fileId: string
|
||||
setFileId: (fileId: string) => void
|
||||
streamDimensions: { streamWidth: number; streamHeight: number }
|
||||
setStreamDimensions: (dimensions: {
|
||||
streamWidth: number
|
||||
streamHeight: number
|
||||
}) => void
|
||||
setHtmlRef: (ref: React.RefObject<HTMLDivElement>) => void
|
||||
htmlRef: React.RefObject<HTMLDivElement> | null
|
||||
|
||||
showHomeMenu: boolean
|
||||
setHomeShowMenu: (showMenu: boolean) => void
|
||||
openPanes: SidebarType[]
|
||||
setOpenPanes: (panes: SidebarType[]) => void
|
||||
homeMenuItems: {
|
||||
name: string
|
||||
path: string
|
||||
}[]
|
||||
setHomeMenuItems: (items: { name: string; path: string }[]) => void
|
||||
}
|
||||
|
||||
export const useStore = create<StoreState>()(
|
||||
persist(
|
||||
(set, get) => {
|
||||
return {
|
||||
setMediaStream: (mediaStream) => set({ mediaStream }),
|
||||
isStreamReady: false,
|
||||
setIsStreamReady: (isStreamReady) => set({ isStreamReady }),
|
||||
isKclLspServerReady: false,
|
||||
isCopilotLspServerReady: false,
|
||||
setIsKclLspServerReady: (isKclLspServerReady) =>
|
||||
set({ isKclLspServerReady }),
|
||||
setIsCopilotLspServerReady: (isCopilotLspServerReady) =>
|
||||
set({ isCopilotLspServerReady }),
|
||||
buttonDownInStream: undefined,
|
||||
setButtonDownInStream: (buttonDownInStream) => {
|
||||
set({ buttonDownInStream })
|
||||
},
|
||||
setHtmlRef: (htmlRef) => {
|
||||
set({ htmlRef })
|
||||
},
|
||||
htmlRef: null,
|
||||
didDragInStream: false,
|
||||
setDidDragInStream: (didDragInStream) => {
|
||||
set({ didDragInStream })
|
||||
},
|
||||
// For stream event handling
|
||||
fileId: '',
|
||||
setFileId: (fileId) => set({ fileId }),
|
||||
streamDimensions: { streamWidth: 1280, streamHeight: 720 },
|
||||
setStreamDimensions: (streamDimensions) => {
|
||||
set({ streamDimensions })
|
||||
},
|
||||
|
||||
// tauri specific app settings
|
||||
defaultDir: {
|
||||
dir: '',
|
||||
},
|
||||
openPanes: ['code'],
|
||||
setOpenPanes: (openPanes) => set({ openPanes }),
|
||||
showHomeMenu: true,
|
||||
setHomeShowMenu: (showHomeMenu) => set({ showHomeMenu }),
|
||||
homeMenuItems: [],
|
||||
setHomeMenuItems: (homeMenuItems) => set({ homeMenuItems }),
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'store',
|
||||
partialize: (state) =>
|
||||
Object.fromEntries(
|
||||
Object.entries(state).filter(([key]) => ['openPanes'].includes(key))
|
||||
),
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
export async function executeAst({
|
||||
ast,
|
||||
engineCommandManager,
|
||||
useFakeExecutor = false,
|
||||
programMemoryOverride,
|
||||
}: {
|
||||
ast: Program
|
||||
engineCommandManager: EngineCommandManager
|
||||
useFakeExecutor?: boolean
|
||||
programMemoryOverride?: ProgramMemory
|
||||
}): Promise<{
|
||||
logs: string[]
|
||||
errors: KCLError[]
|
||||
programMemory: ProgramMemory
|
||||
}> {
|
||||
try {
|
||||
if (!useFakeExecutor) {
|
||||
engineCommandManager.endSession()
|
||||
engineCommandManager.startNewSession()
|
||||
}
|
||||
const programMemory = await (useFakeExecutor
|
||||
? enginelessExecutor(ast, programMemoryOverride || programMemoryInit())
|
||||
: _executor(ast, programMemoryInit(), engineCommandManager, false))
|
||||
|
||||
await engineCommandManager.waitForAllCommands()
|
||||
return {
|
||||
logs: [],
|
||||
errors: [],
|
||||
programMemory,
|
||||
}
|
||||
} catch (e: any) {
|
||||
if (e instanceof KCLError) {
|
||||
return {
|
||||
errors: [e],
|
||||
logs: [],
|
||||
programMemory: {
|
||||
root: {},
|
||||
return: null,
|
||||
},
|
||||
}
|
||||
} else {
|
||||
console.log(e)
|
||||
return {
|
||||
logs: [e],
|
||||
errors: [],
|
||||
programMemory: {
|
||||
root: {},
|
||||
return: null,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function lintAst({
|
||||
ast,
|
||||
}: {
|
||||
ast: Program
|
||||
}): Promise<Array<Diagnostic>> {
|
||||
try {
|
||||
const discovered_findings = await kclLint(ast)
|
||||
return discovered_findings.map((lint) => {
|
||||
return {
|
||||
message: lint.finding.title,
|
||||
severity: 'info',
|
||||
from: lint.pos[0],
|
||||
to: lint.pos[1],
|
||||
}
|
||||
})
|
||||
} catch (e: any) {
|
||||
console.log(e)
|
||||
return []
|
||||
}
|
||||
}
|
4
src/wasm-lib/Cargo.lock
generated
@ -2606,9 +2606,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.119"
|
||||
version = "1.0.120"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8eddb61f0697cc3989c5d64b452f5488e2b8a60fd7d5076a3045076ffef8cb0"
|
||||
checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5"
|
||||
dependencies = [
|
||||
"indexmap 2.2.5",
|
||||
"itoa",
|
||||
|
@ -15,7 +15,7 @@ clap = "4.5.7"
|
||||
gloo-utils = "0.2.0"
|
||||
kcl-lib = { path = "kcl" }
|
||||
kittycad.workspace = true
|
||||
serde_json = "1.0.119"
|
||||
serde_json = "1.0.120"
|
||||
tokio = { version = "1.38.0", features = ["sync"] }
|
||||
toml = "0.8.14"
|
||||
uuid = { version = "1.9.1", features = ["v4", "js", "serde"] }
|
||||
|
@ -11,5 +11,5 @@ hyper = { version = "0.14.29", features = ["server"] }
|
||||
kcl-lib = { version = "0.1.62", path = "../kcl" }
|
||||
pico-args = "0.5.0"
|
||||
serde = { version = "1.0.203", features = ["derive"] }
|
||||
serde_json = "1.0.119"
|
||||
serde_json = "1.0.120"
|
||||
tokio = { version = "1.38.0", features = ["macros", "rt-multi-thread"] }
|
||||
|
@ -33,7 +33,7 @@ reqwest = { version = "0.11.26", default-features = false, features = ["stream",
|
||||
ropey = "1.6.1"
|
||||
schemars = { version = "0.8.17", features = ["impl_json_schema", "url", "uuid1"] }
|
||||
serde = { version = "1.0.203", features = ["derive"] }
|
||||
serde_json = "1.0.119"
|
||||
serde_json = "1.0.120"
|
||||
sha2 = "0.10.8"
|
||||
thiserror = "1.0.61"
|
||||
toml = "0.8.14"
|
||||
|
@ -69,6 +69,7 @@ impl StdLibFnArg {
|
||||
|| self.type_ == "ExtrudeGroup"
|
||||
|| self.type_ == "ExtrudeGroupSet"
|
||||
|| self.type_ == "SketchSurface"
|
||||
|| self.type_ == "SketchSurfaceOrGroup"
|
||||
{
|
||||
return Ok(Some((index, format!("${{{}:{}}}", index, "%"))));
|
||||
} else if self.type_ == "TagDeclarator" && self.required {
|
||||
@ -894,4 +895,11 @@ mod tests {
|
||||
}, ${2:%})${}"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_autocomplete_snippet_circle() {
|
||||
let circle_fn: Box<dyn StdLibFn> = Box::new(crate::std::shapes::Circle);
|
||||
let snippet = circle_fn.to_autocomplete_snippet().unwrap();
|
||||
assert_eq!(snippet, r#"circle([${0:3.14}, ${1:3.14}], ${2:3.14}, ${3:%})${}"#);
|
||||
}
|
||||
}
|
||||
|
202
yarn.lock
@ -1124,7 +1124,7 @@
|
||||
resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310"
|
||||
integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==
|
||||
|
||||
"@babel/runtime@^7.12.5", "@babel/runtime@^7.16.3", "@babel/runtime@^7.18.6", "@babel/runtime@^7.20.13", "@babel/runtime@^7.23.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
|
||||
"@babel/runtime@^7.12.5", "@babel/runtime@^7.16.3", "@babel/runtime@^7.20.13", "@babel/runtime@^7.23.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
|
||||
version "7.24.7"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.7.tgz#f4f0d5530e8dbdf59b3451b9b3e594b6ba082e12"
|
||||
integrity sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==
|
||||
@ -1175,17 +1175,17 @@
|
||||
"@codemirror/view" "^6.17.0"
|
||||
"@lezer/common" "^1.0.0"
|
||||
|
||||
"@codemirror/autocomplete@^6.16.3":
|
||||
version "6.16.3"
|
||||
resolved "https://registry.yarnpkg.com/@codemirror/autocomplete/-/autocomplete-6.16.3.tgz#04d5a4e4e44ccae1ba525d47db53a5479bf46338"
|
||||
integrity sha512-Vl/tIeRVVUCRDuOG48lttBasNQu8usGgXQawBXI7WJAiUDSFOfzflmEsZFZo48mAvAaa4FZ/4/yLLxFtdJaKYA==
|
||||
"@codemirror/autocomplete@^6.17.0":
|
||||
version "6.17.0"
|
||||
resolved "https://registry.yarnpkg.com/@codemirror/autocomplete/-/autocomplete-6.17.0.tgz#24ff5fc37fd91f6439df6f4ff9c8e910cde1b053"
|
||||
integrity sha512-fdfj6e6ZxZf8yrkMHUSJJir7OJkHkZKaOZGzLWIYp2PZ3jd+d+UjG8zVPqJF6d3bKxkhvXTPan/UZ1t7Bqm0gA==
|
||||
dependencies:
|
||||
"@codemirror/language" "^6.0.0"
|
||||
"@codemirror/state" "^6.0.0"
|
||||
"@codemirror/view" "^6.17.0"
|
||||
"@lezer/common" "^1.0.0"
|
||||
|
||||
"@codemirror/commands@^6.0.0", "@codemirror/commands@^6.1.0", "@codemirror/commands@^6.6.0":
|
||||
"@codemirror/commands@^6.0.0", "@codemirror/commands@^6.6.0":
|
||||
version "6.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@codemirror/commands/-/commands-6.6.0.tgz#d308f143fe1b8896ca25fdb855f66acdaf019dd4"
|
||||
integrity sha512-qnY+b7j1UNcTS31Eenuc/5YJB6gQOzkUoNmJQc0rznwqSRpeaWWpjkWy2C/MPTcePpsKJEM26hXrOXl1+nceXg==
|
||||
@ -1234,12 +1234,12 @@
|
||||
"@codemirror/view" "^6.0.0"
|
||||
crelt "^1.0.5"
|
||||
|
||||
"@codemirror/state@^6.0.0", "@codemirror/state@^6.1.1", "@codemirror/state@^6.2.1", "@codemirror/state@^6.4.0", "@codemirror/state@^6.4.1":
|
||||
"@codemirror/state@^6.0.0", "@codemirror/state@^6.2.1", "@codemirror/state@^6.4.0", "@codemirror/state@^6.4.1":
|
||||
version "6.4.1"
|
||||
resolved "https://registry.yarnpkg.com/@codemirror/state/-/state-6.4.1.tgz#da57143695c056d9a3c38705ed34136e2b68171b"
|
||||
integrity sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A==
|
||||
|
||||
"@codemirror/theme-one-dark@^6.0.0":
|
||||
"@codemirror/theme-one-dark@^6.1.2":
|
||||
version "6.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@codemirror/theme-one-dark/-/theme-one-dark-6.1.2.tgz#fcef9f9cfc17a07836cb7da17c9f6d7231064df8"
|
||||
integrity sha512-F+sH0X16j/qFLMAfbciKTxVOwkdAS336b7AXTKOZhy8BR3eH/RelsnLgLFINrpST63mmN2OuwUt0W2ndUgYwUA==
|
||||
@ -1697,12 +1697,12 @@
|
||||
resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33"
|
||||
integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==
|
||||
|
||||
"@playwright/test@^1.44.1":
|
||||
version "1.44.1"
|
||||
resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.44.1.tgz#cc874ec31342479ad99838040e99b5f604299bcb"
|
||||
integrity sha512-1hZ4TNvD5z9VuhNJ/walIjvMVvYkZKf71axoF/uiAqpntQJXpG64dlXhoDXE3OczPuTuvjf/M5KWFg5VAVUS3Q==
|
||||
"@playwright/test@^1.45.1":
|
||||
version "1.45.1"
|
||||
resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.45.1.tgz#819b90fa43d17000fce5ebd127043fd661938b7a"
|
||||
integrity sha512-Wo1bWTzQvGA7LyKGIZc8nFSTFf2TkthGIFBR+QVNilvwouGzFd4PYukZe3rvf5PSqjHi1+1NyKSDZKcQWETzaA==
|
||||
dependencies:
|
||||
playwright "1.44.1"
|
||||
playwright "1.45.1"
|
||||
|
||||
"@promptbook/utils@0.50.0-10":
|
||||
version "0.50.0-10"
|
||||
@ -1909,15 +1909,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@tanstack/virtual-core/-/virtual-core-3.5.1.tgz#f519149bce9156d0e7954b9531df15f446f2fc12"
|
||||
integrity sha512-046+AUSiDru/V9pajE1du8WayvBKeCvJ2NmKPy/mR8/SbKKrqmSbj7LJBfXE+nSq4f5TBXvnCzu0kcYebI9WdQ==
|
||||
|
||||
"@tauri-apps/api@2.0.0-beta.12":
|
||||
version "2.0.0-beta.12"
|
||||
resolved "https://registry.yarnpkg.com/@tauri-apps/api/-/api-2.0.0-beta.12.tgz#0b552086e6382cfd5798537b304d00cbf42db7a1"
|
||||
integrity sha512-77OvAnsExtiprnjQcvmDyZGfnIvMF/zVL5+8Vkl1R8o8E3iDtvEJZpbbH1F4dPtNa3gr4byp/5dm8hAa1+r3AA==
|
||||
|
||||
"@tauri-apps/api@2.0.0-beta.13":
|
||||
version "2.0.0-beta.13"
|
||||
resolved "https://registry.yarnpkg.com/@tauri-apps/api/-/api-2.0.0-beta.13.tgz#53ec5117d042d560615afec2d38a6d38ee20ff22"
|
||||
integrity sha512-Np1opKANzRMF3lgJ9gDquBCB9SxlE2lRmNpVx1+L6RyzAmigkuh0ZulT5jMnDA3JLsuSDU135r/s4t/Pmx4atg==
|
||||
"@tauri-apps/api@2.0.0-beta.14", "@tauri-apps/api@^2.0.0-beta.14":
|
||||
version "2.0.0-beta.14"
|
||||
resolved "https://registry.yarnpkg.com/@tauri-apps/api/-/api-2.0.0-beta.14.tgz#8c1c65c07559cd29c5103a99e0abe5331cc2246f"
|
||||
integrity sha512-YLYgHqdwWswr4Y70+hRzaLD6kLIUgHhE3shLXNquPiTaQ9+cX3Q2dB0AFfqsua6NXYFNe7LfkmMzaqEzqv3yQg==
|
||||
|
||||
"@tauri-apps/cli-darwin-arm64@2.0.0-beta.13":
|
||||
version "2.0.0-beta.13"
|
||||
@ -1985,54 +1980,54 @@
|
||||
"@tauri-apps/cli-win32-ia32-msvc" "2.0.0-beta.13"
|
||||
"@tauri-apps/cli-win32-x64-msvc" "2.0.0-beta.13"
|
||||
|
||||
"@tauri-apps/plugin-dialog@^2.0.0-beta.2":
|
||||
version "2.0.0-beta.5"
|
||||
resolved "https://registry.yarnpkg.com/@tauri-apps/plugin-dialog/-/plugin-dialog-2.0.0-beta.5.tgz#1ad9592d554bd5a4b4d41a9f4aa6e01aa33a9f6a"
|
||||
integrity sha512-jkaBCsx2v6WB6sB77fTMCeijuvT3FlzwschiHnPlD7aU6CHvQgRlpCv/FttPdTq4ih2t6MIlM4oX85hNYgfs6w==
|
||||
dependencies:
|
||||
"@tauri-apps/api" "2.0.0-beta.13"
|
||||
|
||||
"@tauri-apps/plugin-fs@^2.0.0-beta.5":
|
||||
version "2.0.0-beta.5"
|
||||
resolved "https://registry.yarnpkg.com/@tauri-apps/plugin-fs/-/plugin-fs-2.0.0-beta.5.tgz#eea9161d33dafc592005e3ee1e74db6298b20398"
|
||||
integrity sha512-uTqCDFA1z8KDtTe5fKlbRrKV4zxh63UVUvD/PR8GnyNLV+qxj/fEuJuGvMdfMbVKrTljGqSpun5wnP5jqD5fMg==
|
||||
dependencies:
|
||||
"@tauri-apps/api" "2.0.0-beta.13"
|
||||
|
||||
"@tauri-apps/plugin-http@^2.0.0-beta.2":
|
||||
"@tauri-apps/plugin-dialog@^2.0.0-beta.6":
|
||||
version "2.0.0-beta.6"
|
||||
resolved "https://registry.yarnpkg.com/@tauri-apps/plugin-http/-/plugin-http-2.0.0-beta.6.tgz#b126b6ca3ab22ccaeb9861fa4f5e88066e20539e"
|
||||
integrity sha512-/5cDY9LwrZkPBTqxx2xwvzA3fzYS+Y1UD0rK9NVxjKkNXoA9NmGxEetug05u0KPbOtciyFiTyq31koszlPy6KA==
|
||||
resolved "https://registry.yarnpkg.com/@tauri-apps/plugin-dialog/-/plugin-dialog-2.0.0-beta.6.tgz#e42b80278d914318992cfc0534bc3c6977ed52ac"
|
||||
integrity sha512-Rw8C8t/0y3QExEinp+cAOZi/BDt0c9jifv0bS3WDCwQt9ANdmfCWKamsIhqwemt3MjepkU2RV8bvphzoWlbOGw==
|
||||
dependencies:
|
||||
"@tauri-apps/api" "2.0.0-beta.13"
|
||||
"@tauri-apps/api" "2.0.0-beta.14"
|
||||
|
||||
"@tauri-apps/plugin-os@^2.0.0-beta.3":
|
||||
version "2.0.0-beta.5"
|
||||
resolved "https://registry.yarnpkg.com/@tauri-apps/plugin-os/-/plugin-os-2.0.0-beta.5.tgz#7b3b066796e37f073925afb6cb0ddb9cdab0f7d9"
|
||||
integrity sha512-Qfs/clZ9R05J+OVOGkko+9OaYaL+xJaGICeQ1G5CnLFpUdTfMV10D+1nBBauxDdiLU4ay5I0iprJ5aG5GJBunQ==
|
||||
dependencies:
|
||||
"@tauri-apps/api" "2.0.0-beta.13"
|
||||
|
||||
"@tauri-apps/plugin-process@^2.0.0-beta.2":
|
||||
version "2.0.0-beta.5"
|
||||
resolved "https://registry.yarnpkg.com/@tauri-apps/plugin-process/-/plugin-process-2.0.0-beta.5.tgz#aa97b98ab4dcdd438de162e98f6f54e0230ff6e3"
|
||||
integrity sha512-UMiBm6TtNYfxRb6GwzA4PalkZGwalHdclI/t0MVG33fNXgX1PaWONR/NZW/k7JE+WpvRAnN/Kf9ur8aEzjVVSQ==
|
||||
dependencies:
|
||||
"@tauri-apps/api" "2.0.0-beta.13"
|
||||
|
||||
"@tauri-apps/plugin-shell@^2.0.0-beta.2":
|
||||
"@tauri-apps/plugin-fs@^2.0.0-beta.6":
|
||||
version "2.0.0-beta.6"
|
||||
resolved "https://registry.yarnpkg.com/@tauri-apps/plugin-shell/-/plugin-shell-2.0.0-beta.6.tgz#10378e753a220bdab638cbacb6b9d0039ddbe986"
|
||||
integrity sha512-g3nM9cQQGl7Iv4MvyFuco/aPTiwOI/MixcoKso3VQIg5Aqd64NqR0r+GfsB0qx52txItqzSXwmeaj1eZjO9Q6Q==
|
||||
resolved "https://registry.yarnpkg.com/@tauri-apps/plugin-fs/-/plugin-fs-2.0.0-beta.6.tgz#ac0e0b171e5c8320e26ca763e780d91a1d1e4e4a"
|
||||
integrity sha512-R7M5wggENzJhiA0HwP63AzdF6uzdXRYe0z+ETSue9gvJmqKtqVp+qx9JLsWSfwENHy8RDKmrnuDL18kx/Q2aFA==
|
||||
dependencies:
|
||||
"@tauri-apps/api" "2.0.0-beta.13"
|
||||
"@tauri-apps/api" "2.0.0-beta.14"
|
||||
|
||||
"@tauri-apps/plugin-updater@^2.0.0-beta.3":
|
||||
version "2.0.0-beta.5"
|
||||
resolved "https://registry.yarnpkg.com/@tauri-apps/plugin-updater/-/plugin-updater-2.0.0-beta.5.tgz#8be3277da409ffc596a870b625cb4bb4ef17f199"
|
||||
integrity sha512-h8uNFQDtXaZPFyQcNAB5SxiSIvPbYRlM1fXf/lCcW8bAaMTanyszbHvR2vAYtVa/wIzKAOYriC/MpsJaKLjyhg==
|
||||
"@tauri-apps/plugin-http@^2.0.0-beta.7":
|
||||
version "2.0.0-beta.7"
|
||||
resolved "https://registry.yarnpkg.com/@tauri-apps/plugin-http/-/plugin-http-2.0.0-beta.7.tgz#0472b6b71c9df5d889c8a81e136ce3dd824aeeb6"
|
||||
integrity sha512-mxdhcpPPT2oHm0FWe6HS2UbQW2LFTbAv2vExrTYAPJSuXOp2kisgOjazZtswYqpmftpcSZ4dFpmzNlQp188e/g==
|
||||
dependencies:
|
||||
"@tauri-apps/api" "2.0.0-beta.13"
|
||||
"@tauri-apps/api" "2.0.0-beta.14"
|
||||
|
||||
"@tauri-apps/plugin-os@^2.0.0-beta.6":
|
||||
version "2.0.0-beta.6"
|
||||
resolved "https://registry.yarnpkg.com/@tauri-apps/plugin-os/-/plugin-os-2.0.0-beta.6.tgz#2ce79d3edfa81401da4f4ff61549700f37766b61"
|
||||
integrity sha512-28Ts286o4YH3vZ+swptVblRMuMa1MLjLbgPpnR1wuPNzzR4p7J6+Hr3Euge71RIsFJhjAeP1XkNbHgpAFj4Mpg==
|
||||
dependencies:
|
||||
"@tauri-apps/api" "2.0.0-beta.14"
|
||||
|
||||
"@tauri-apps/plugin-process@^2.0.0-beta.6":
|
||||
version "2.0.0-beta.6"
|
||||
resolved "https://registry.yarnpkg.com/@tauri-apps/plugin-process/-/plugin-process-2.0.0-beta.6.tgz#3835d4d61a9565a8451bfc3e47afe827bba8f050"
|
||||
integrity sha512-Rem3r8lGe6ZSvncqIV9xpq2hOey7krMoPh5nu7WxbR73LOSkRBUDaYMvZjXu1DrJ3LEyXxo48sp76+9MW2Rp/w==
|
||||
dependencies:
|
||||
"@tauri-apps/api" "2.0.0-beta.14"
|
||||
|
||||
"@tauri-apps/plugin-shell@^2.0.0-beta.7":
|
||||
version "2.0.0-beta.7"
|
||||
resolved "https://registry.yarnpkg.com/@tauri-apps/plugin-shell/-/plugin-shell-2.0.0-beta.7.tgz#43159959ff8ef83435df6d64be381606f6e02130"
|
||||
integrity sha512-oJxWbEiNRcoMM0PrePjJnjPHEAN1sbYuWaQ1QMtLPdjHsl83RLk+RpFzkL5WvtGknfiKY7T2qEthOID4br+mvg==
|
||||
dependencies:
|
||||
"@tauri-apps/api" "2.0.0-beta.14"
|
||||
|
||||
"@tauri-apps/plugin-updater@^2.0.0-beta.6":
|
||||
version "2.0.0-beta.6"
|
||||
resolved "https://registry.yarnpkg.com/@tauri-apps/plugin-updater/-/plugin-updater-2.0.0-beta.6.tgz#2b889638ea65ec0cfafa8e8adc530e10dfd40278"
|
||||
integrity sha512-CKgVNsbpGPp8yIUWitszdQ2DVglRZ8jescxacJ43HkaI7oz26EQPL3VwPjjwIQ1RX166JQcNJuM8grcZd8QjBg==
|
||||
dependencies:
|
||||
"@tauri-apps/api" "2.0.0-beta.14"
|
||||
|
||||
"@testing-library/dom@^10.0.0":
|
||||
version "10.1.0"
|
||||
@ -2454,31 +2449,6 @@
|
||||
"@typescript-eslint/types" "5.62.0"
|
||||
eslint-visitor-keys "^3.3.0"
|
||||
|
||||
"@uiw/codemirror-extensions-basic-setup@4.22.2":
|
||||
version "4.22.2"
|
||||
resolved "https://registry.yarnpkg.com/@uiw/codemirror-extensions-basic-setup/-/codemirror-extensions-basic-setup-4.22.2.tgz#a114dc9ebad6de41a441c8aca655d9c34934a7d9"
|
||||
integrity sha512-zcHGkldLFN3cGoI5XdOGAkeW24yaAgrDEYoyPyWHODmPiNwybQQoZGnH3qUdzZwUaXtAcLWoAeOPzfNRW2yGww==
|
||||
dependencies:
|
||||
"@codemirror/autocomplete" "^6.0.0"
|
||||
"@codemirror/commands" "^6.0.0"
|
||||
"@codemirror/language" "^6.0.0"
|
||||
"@codemirror/lint" "^6.0.0"
|
||||
"@codemirror/search" "^6.0.0"
|
||||
"@codemirror/state" "^6.0.0"
|
||||
"@codemirror/view" "^6.0.0"
|
||||
|
||||
"@uiw/react-codemirror@^4.21.25":
|
||||
version "4.22.2"
|
||||
resolved "https://registry.yarnpkg.com/@uiw/react-codemirror/-/react-codemirror-4.22.2.tgz#18dcb79e31cf34e0704366f3041da93ff3c64109"
|
||||
integrity sha512-okCSl+WJG63gRx8Fdz7v0C6RakBQnbb3pHhuzIgDB+fwhipgFodSnu2n9oOsQesJ5YQ7mSOcKMgX0JEsu4nnfQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.18.6"
|
||||
"@codemirror/commands" "^6.1.0"
|
||||
"@codemirror/state" "^6.1.1"
|
||||
"@codemirror/theme-one-dark" "^6.0.0"
|
||||
"@uiw/codemirror-extensions-basic-setup" "4.22.2"
|
||||
codemirror "^6.0.0"
|
||||
|
||||
"@ungap/structured-clone@^1.2.0":
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406"
|
||||
@ -3530,7 +3500,7 @@ clone@^1.0.2:
|
||||
resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e"
|
||||
integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==
|
||||
|
||||
codemirror@^6.0.0, codemirror@^6.0.1:
|
||||
codemirror@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-6.0.1.tgz#62b91142d45904547ee3e0e0e4c1a79158035a29"
|
||||
integrity sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==
|
||||
@ -5145,10 +5115,10 @@ html-encoding-sniffer@^3.0.0:
|
||||
dependencies:
|
||||
whatwg-encoding "^2.0.0"
|
||||
|
||||
html2canvas-pro@^1.5.0:
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/html2canvas-pro/-/html2canvas-pro-1.5.0.tgz#18925def43505bad352a394b95fffb45d6d46a8f"
|
||||
integrity sha512-izxSphcINRwfEVV6eamsPVdhsxSYqX8n/hxzK+niVWdB+onM+aYRoVO+xgS9iMmZoUleZTWg1tJwryikib2hXg==
|
||||
html2canvas-pro@^1.5.2:
|
||||
version "1.5.2"
|
||||
resolved "https://registry.yarnpkg.com/html2canvas-pro/-/html2canvas-pro-1.5.2.tgz#fb9841feabd477d02e54ea60b4a45ef499f8350a"
|
||||
integrity sha512-VYZifzRbLl+ssNDbivIAQftu+qRsxF3YdNpCo1NvqHAZ/0O3aoV0j1yIyIEKcDxTtuQ0buE3pe74IhmyRk/QdQ==
|
||||
dependencies:
|
||||
css-line-break "^2.1.0"
|
||||
text-segmentation "^1.0.3"
|
||||
@ -6632,17 +6602,17 @@ pkg-types@^1.0.3, pkg-types@^1.1.1:
|
||||
mlly "^1.7.0"
|
||||
pathe "^1.1.2"
|
||||
|
||||
playwright-core@1.44.1:
|
||||
version "1.44.1"
|
||||
resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.44.1.tgz#53ec975503b763af6fc1a7aa995f34bc09ff447c"
|
||||
integrity sha512-wh0JWtYTrhv1+OSsLPgFzGzt67Y7BE/ZS3jEqgGBlp2ppp1ZDj8c+9IARNW4dwf1poq5MgHreEM2KV/GuR4cFA==
|
||||
playwright-core@1.45.1:
|
||||
version "1.45.1"
|
||||
resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.45.1.tgz#549a2701556b58245cc75263f9fc2795c1158dc1"
|
||||
integrity sha512-LF4CUUtrUu2TCpDw4mcrAIuYrEjVDfT1cHbJMfwnE2+1b8PZcFzPNgvZCvq2JfQ4aTjRCCHw5EJ2tmr2NSzdPg==
|
||||
|
||||
playwright@1.44.1:
|
||||
version "1.44.1"
|
||||
resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.44.1.tgz#5634369d777111c1eea9180430b7a184028e7892"
|
||||
integrity sha512-qr/0UJ5CFAtloI3avF95Y0L1xQo6r3LQArLIg/z/PoGJ6xa+EwzrwO5lpNr/09STxdHuUoP2mvuELJS+hLdtgg==
|
||||
playwright@1.45.1:
|
||||
version "1.45.1"
|
||||
resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.45.1.tgz#aaa6b0d6db14796b599d80c6679e63444e942534"
|
||||
integrity sha512-Hjrgae4kpSQBr98nhCj3IScxVeVUixqj+5oyif8TdIn2opTCPEzqAqNMeK42i3cWDCVu9MI+ZsGWw+gVR4ISBg==
|
||||
dependencies:
|
||||
playwright-core "1.44.1"
|
||||
playwright-core "1.45.1"
|
||||
optionalDependencies:
|
||||
fsevents "2.3.2"
|
||||
|
||||
@ -7848,10 +7818,10 @@ thenify-all@^1.0.0:
|
||||
dependencies:
|
||||
any-promise "^1.0.0"
|
||||
|
||||
three@^0.164.1:
|
||||
version "0.164.1"
|
||||
resolved "https://registry.yarnpkg.com/three/-/three-0.164.1.tgz#b742f76bd8dfd3736ba0d86a12dfddb73c5cdcc0"
|
||||
integrity sha512-iC/hUBbl1vzFny7f5GtqzVXYjMJKaTPxiCxXfrvVdBi1Sf+jhd1CAkitiFwC7mIBFCo3MrDLJG97yisoaWig0w==
|
||||
three@^0.166.1:
|
||||
version "0.166.1"
|
||||
resolved "https://registry.yarnpkg.com/three/-/three-0.166.1.tgz#322cfc48fff4e751cd47d61fd1558c387d098d7c"
|
||||
integrity sha512-LtuafkKHHzm61AQA1be2MAYIw1IjmhOUxhBa0prrLpEMWbV7ijvxCRHjSgHPGp2493wLBzwKV46tA9nivLEgKg==
|
||||
|
||||
through@^2.3.8:
|
||||
version "2.3.8"
|
||||
@ -8159,11 +8129,6 @@ use-latest@^1.2.1:
|
||||
dependencies:
|
||||
use-isomorphic-layout-effect "^1.1.1"
|
||||
|
||||
use-sync-external-store@1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"
|
||||
integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==
|
||||
|
||||
use-sync-external-store@^1.0.0:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz#c3b6390f3a30eba13200d2302dcdf1e7b57b2ef9"
|
||||
@ -8343,10 +8308,10 @@ warning@^4.0.3:
|
||||
dependencies:
|
||||
loose-envify "^1.0.0"
|
||||
|
||||
wasm-pack@^0.12.1:
|
||||
version "0.12.1"
|
||||
resolved "https://registry.yarnpkg.com/wasm-pack/-/wasm-pack-0.12.1.tgz#974c1fbbf5b65c9e135e0d1fba3a97de1a21a489"
|
||||
integrity sha512-dIyKWUumPFsGohdndZjDXRFaokUT/kQS+SavbbiXVAvA/eN4riX5QNdB6AhXQx37zNxluxQkuixZUgJ8adKjOg==
|
||||
wasm-pack@^0.13.0:
|
||||
version "0.13.0"
|
||||
resolved "https://registry.yarnpkg.com/wasm-pack/-/wasm-pack-0.13.0.tgz#c2637e0129e1854735f3daead45d92165d54709d"
|
||||
integrity sha512-AmboGZEnZoIcVCzSlkLEmNFEqJN+IwgshJ5S7pi30uNUTce4LvWkifQzsQRxnWj47G8gkqZxlyGlyQplsnIS7w==
|
||||
dependencies:
|
||||
binary-install "^1.0.1"
|
||||
|
||||
@ -8718,10 +8683,3 @@ zip-stream@^6.0.1:
|
||||
archiver-utils "^5.0.0"
|
||||
compress-commons "^6.0.2"
|
||||
readable-stream "^4.0.0"
|
||||
|
||||
zustand@^4.5.2:
|
||||
version "4.5.2"
|
||||
resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.5.2.tgz#fddbe7cac1e71d45413b3682cdb47b48034c3848"
|
||||
integrity sha512-2cN1tPkDVkwCy5ickKrI7vijSjPksFRfqS6237NzT0vqSsztTNnQdHw9mmN7uBdk3gceVXU0a+21jFzFzAc9+g==
|
||||
dependencies:
|
||||
use-sync-external-store "1.2.0"
|
||||
|