Compare commits
14 Commits
v0.19.0
...
url-scheme
Author | SHA1 | Date | |
---|---|---|---|
a21c2c50b3 | |||
aa52407fda | |||
e45be831d0 | |||
005944f3a3 | |||
755ef8ce7f | |||
005d1f0ca7 | |||
e158f6f513 | |||
879d7ec4f4 | |||
f6838b9b14 | |||
cb75c47631 | |||
9b95ec1083 | |||
a3eeff65c8 | |||
fab3d2b130 | |||
0a96dc6fd2 |
2
.github/workflows/build-and-store-wasm.yml
vendored
2
.github/workflows/build-and-store-wasm.yml
vendored
@ -16,8 +16,6 @@ jobs:
|
|||||||
cache: 'yarn'
|
cache: 'yarn'
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: yarn
|
run: yarn
|
||||||
- name: Install Playwright Browsers
|
|
||||||
run: yarn playwright install --with-deps
|
|
||||||
- name: Setup Rust
|
- name: Setup Rust
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
- name: Cache wasm
|
- name: Cache wasm
|
||||||
|
80
.github/workflows/playwright.yml
vendored
80
.github/workflows/playwright.yml
vendored
@ -12,11 +12,31 @@ concurrency:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
|
actions: read
|
||||||
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
|
check-rust-changes:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
rust-changed: ${{ steps.filter.outputs.rust }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- id: filter
|
||||||
|
name: Check for Rust changes
|
||||||
|
uses: dorny/paths-filter@v2
|
||||||
|
with:
|
||||||
|
filters: |
|
||||||
|
rust:
|
||||||
|
- 'src/wasm-lib/**'
|
||||||
|
|
||||||
playwright-ubuntu:
|
playwright-ubuntu:
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
runs-on: ubuntu-latest-8-cores
|
runs-on: ubuntu-latest-8-cores
|
||||||
|
needs: check-rust-changes
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
@ -28,13 +48,39 @@ jobs:
|
|||||||
run: yarn
|
run: yarn
|
||||||
- name: Install Playwright Browsers
|
- name: Install Playwright Browsers
|
||||||
run: yarn playwright install --with-deps
|
run: yarn playwright install --with-deps
|
||||||
|
- name: download wasm
|
||||||
|
id: download-wasm
|
||||||
|
if: needs.check-rust-changes.outputs.rust-changed == 'false'
|
||||||
|
uses: actions/download-artifact@v2
|
||||||
|
continue-on-error: true
|
||||||
|
with:
|
||||||
|
name: wasm-bundle
|
||||||
|
path: src/wasm-lib/pkg
|
||||||
|
- name: copy wasm blob
|
||||||
|
if: needs.check-rust-changes.outputs.rust-changed == 'false'
|
||||||
|
run: cp src/wasm-lib/pkg/wasm_lib_bg.wasm public
|
||||||
|
continue-on-error: true
|
||||||
- name: Setup Rust
|
- name: Setup Rust
|
||||||
|
if: needs.check-rust-changes.outputs.rust-changed == 'true'
|
||||||
|
uses: dtolnay/rust-toolchain@stable
|
||||||
|
- name: Setup Rust
|
||||||
|
if: steps.download-wasm.outcome == 'failure'
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
- name: Cache wasm
|
- name: Cache wasm
|
||||||
|
if: needs.check-rust-changes.outputs.rust-changed == 'true'
|
||||||
|
uses: Swatinem/rust-cache@v2
|
||||||
|
with:
|
||||||
|
workspaces: './src/wasm-lib'
|
||||||
|
- name: Cache wasm
|
||||||
|
if: steps.download-wasm.outcome == 'failure'
|
||||||
uses: Swatinem/rust-cache@v2
|
uses: Swatinem/rust-cache@v2
|
||||||
with:
|
with:
|
||||||
workspaces: './src/wasm-lib'
|
workspaces: './src/wasm-lib'
|
||||||
- name: build wasm
|
- name: build wasm
|
||||||
|
if: needs.check-rust-changes.outputs.rust-changed == 'true'
|
||||||
|
run: yarn build:wasm
|
||||||
|
- name: build wasm
|
||||||
|
if: steps.download-wasm.outcome == 'failure'
|
||||||
run: yarn build:wasm
|
run: yarn build:wasm
|
||||||
- name: build web
|
- name: build web
|
||||||
run: yarn build:local
|
run: yarn build:local
|
||||||
@ -85,10 +131,16 @@ jobs:
|
|||||||
name: playwright-report
|
name: playwright-report
|
||||||
path: playwright-report/
|
path: playwright-report/
|
||||||
retention-days: 30
|
retention-days: 30
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
if: github.ref == 'refs/heads/main'
|
||||||
|
with:
|
||||||
|
name: wasm-bundle
|
||||||
|
path: src/wasm-lib/pkg
|
||||||
|
|
||||||
playwright-macos:
|
playwright-macos:
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
runs-on: macos-14
|
runs-on: macos-14
|
||||||
|
needs: check-rust-changes
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
@ -99,13 +151,39 @@ jobs:
|
|||||||
run: yarn
|
run: yarn
|
||||||
- name: Install Playwright Browsers
|
- name: Install Playwright Browsers
|
||||||
run: yarn playwright install --with-deps
|
run: yarn playwright install --with-deps
|
||||||
|
- name: download wasm
|
||||||
|
id: download-wasm
|
||||||
|
if: needs.check-rust-changes.outputs.rust-changed == 'false'
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: wasm-bundle
|
||||||
|
path: src/wasm-lib/pkg
|
||||||
|
continue-on-error: true
|
||||||
|
- name: copy wasm blob
|
||||||
|
if: needs.check-rust-changes.outputs.rust-changed == 'false'
|
||||||
|
run: cp src/wasm-lib/pkg/wasm_lib_bg.wasm public
|
||||||
|
continue-on-error: true
|
||||||
- name: Setup Rust
|
- name: Setup Rust
|
||||||
|
if: needs.check-rust-changes.outputs.rust-changed == 'true'
|
||||||
|
uses: dtolnay/rust-toolchain@stable
|
||||||
|
- name: Setup Rust
|
||||||
|
if: steps.download-wasm.outcome == 'failure'
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
- name: Cache wasm
|
- name: Cache wasm
|
||||||
|
if: needs.check-rust-changes.outputs.rust-changed == 'true'
|
||||||
|
uses: Swatinem/rust-cache@v2
|
||||||
|
with:
|
||||||
|
workspaces: './src/wasm-lib'
|
||||||
|
- name: Cache wasm
|
||||||
|
if: steps.download-wasm.outcome == 'failure'
|
||||||
uses: Swatinem/rust-cache@v2
|
uses: Swatinem/rust-cache@v2
|
||||||
with:
|
with:
|
||||||
workspaces: './src/wasm-lib'
|
workspaces: './src/wasm-lib'
|
||||||
- name: build wasm
|
- name: build wasm
|
||||||
|
if: needs.check-rust-changes.outputs.rust-changed == 'true'
|
||||||
|
run: yarn build:wasm
|
||||||
|
- name: build wasm
|
||||||
|
if: steps.download-wasm.outcome == 'failure'
|
||||||
run: yarn build:wasm
|
run: yarn build:wasm
|
||||||
- name: build web
|
- name: build web
|
||||||
run: yarn build:local
|
run: yarn build:local
|
||||||
@ -122,7 +200,7 @@ jobs:
|
|||||||
name: playwright-report
|
name: playwright-report
|
||||||
path: playwright-report/
|
path: playwright-report/
|
||||||
retention-days: 30
|
retention-days: 30
|
||||||
- uses: actions/upload-artifact@v2
|
- uses: actions/upload-artifact@v4
|
||||||
if: github.ref == 'refs/heads/main'
|
if: github.ref == 'refs/heads/main'
|
||||||
with:
|
with:
|
||||||
name: wasm-bundle
|
name: wasm-bundle
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "untitled-app",
|
"name": "untitled-app",
|
||||||
"version": "0.19.0",
|
"version": "0.19.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/autocomplete": "^6.16.0",
|
"@codemirror/autocomplete": "^6.16.0",
|
||||||
|
136
src-tauri/Cargo.lock
generated
136
src-tauri/Cargo.lock
generated
@ -90,6 +90,54 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstream"
|
||||||
|
version = "0.6.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"anstyle-parse",
|
||||||
|
"anstyle-query",
|
||||||
|
"anstyle-wincon",
|
||||||
|
"colorchoice",
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle"
|
||||||
|
version = "1.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-parse"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
|
||||||
|
dependencies = [
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-query"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-wincon"
|
||||||
|
version = "3.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"windows-sys 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.82"
|
version = "1.0.82"
|
||||||
@ -110,6 +158,7 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"tauri",
|
"tauri",
|
||||||
"tauri-build",
|
"tauri-build",
|
||||||
|
"tauri-plugin-cli",
|
||||||
"tauri-plugin-dialog",
|
"tauri-plugin-dialog",
|
||||||
"tauri-plugin-fs",
|
"tauri-plugin-fs",
|
||||||
"tauri-plugin-http",
|
"tauri-plugin-http",
|
||||||
@ -695,6 +744,48 @@ dependencies = [
|
|||||||
"inout",
|
"inout",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap"
|
||||||
|
version = "4.5.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0"
|
||||||
|
dependencies = [
|
||||||
|
"clap_builder",
|
||||||
|
"clap_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_builder"
|
||||||
|
version = "4.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4"
|
||||||
|
dependencies = [
|
||||||
|
"anstream",
|
||||||
|
"anstyle",
|
||||||
|
"clap_lex",
|
||||||
|
"strsim 0.11.1",
|
||||||
|
"unicase",
|
||||||
|
"unicode-width",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_derive"
|
||||||
|
version = "4.5.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64"
|
||||||
|
dependencies = [
|
||||||
|
"heck 0.5.0",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.60",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_lex"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cocoa"
|
name = "cocoa"
|
||||||
version = "0.25.0"
|
version = "0.25.0"
|
||||||
@ -725,6 +816,12 @@ dependencies = [
|
|||||||
"objc",
|
"objc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorchoice"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "colored"
|
name = "colored"
|
||||||
version = "2.1.0"
|
version = "2.1.0"
|
||||||
@ -915,7 +1012,7 @@ dependencies = [
|
|||||||
"ident_case",
|
"ident_case",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"strsim",
|
"strsim 0.10.0",
|
||||||
"syn 2.0.60",
|
"syn 2.0.60",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -2321,7 +2418,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kcl-lib"
|
name = "kcl-lib"
|
||||||
version = "0.1.51"
|
version = "0.1.53"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"approx",
|
"approx",
|
||||||
@ -2330,6 +2427,7 @@ dependencies = [
|
|||||||
"base64 0.22.0",
|
"base64 0.22.0",
|
||||||
"bson",
|
"bson",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"clap",
|
||||||
"dashmap",
|
"dashmap",
|
||||||
"databake",
|
"databake",
|
||||||
"derive-docs",
|
"derive-docs",
|
||||||
@ -2389,6 +2487,7 @@ dependencies = [
|
|||||||
"bigdecimal",
|
"bigdecimal",
|
||||||
"bytes",
|
"bytes",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"clap",
|
||||||
"data-encoding",
|
"data-encoding",
|
||||||
"format_serde_error",
|
"format_serde_error",
|
||||||
"futures",
|
"futures",
|
||||||
@ -4580,6 +4679,12 @@ version = "0.10.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strsim"
|
||||||
|
version = "0.11.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "structmeta"
|
name = "structmeta"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@ -4958,6 +5063,21 @@ dependencies = [
|
|||||||
"walkdir",
|
"walkdir",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tauri-plugin-cli"
|
||||||
|
version = "2.0.0-beta.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0b079f01e923f7d3bf175e8d31b18861e6580f4b57ce0fdc16fbf69f9acd158c"
|
||||||
|
dependencies = [
|
||||||
|
"clap",
|
||||||
|
"log",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"tauri",
|
||||||
|
"tauri-plugin",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-dialog"
|
name = "tauri-plugin-dialog"
|
||||||
version = "2.0.0-beta.6"
|
version = "2.0.0-beta.6"
|
||||||
@ -5783,6 +5903,12 @@ version = "1.11.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
|
checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-width"
|
||||||
|
version = "0.1.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "untrusted"
|
name = "untrusted"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
@ -5820,6 +5946,12 @@ version = "0.7.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
|
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utf8parse"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "1.8.0"
|
version = "1.8.0"
|
||||||
|
@ -15,11 +15,12 @@ tauri-build = { version = "2.0.0-beta.13", features = [] }
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
kcl-lib = { version = "0.1.51", path = "../src/wasm-lib/kcl" }
|
kcl-lib = { version = "0.1.53", path = "../src/wasm-lib/kcl" }
|
||||||
kittycad = "0.3.0"
|
kittycad = "0.3.0"
|
||||||
oauth2 = "4.4.2"
|
oauth2 = "4.4.2"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
tauri = { version = "2.0.0-beta.15", features = [ "devtools", "unstable"] }
|
tauri = { version = "2.0.0-beta.15", features = [ "devtools", "unstable"] }
|
||||||
|
tauri-plugin-cli = { version = "2.0.0-beta.3" }
|
||||||
tauri-plugin-dialog = { version = "2.0.0-beta.6" }
|
tauri-plugin-dialog = { version = "2.0.0-beta.6" }
|
||||||
tauri-plugin-fs = { version = "2.0.0-beta.6" }
|
tauri-plugin-fs = { version = "2.0.0-beta.6" }
|
||||||
tauri-plugin-http = { version = "2.0.0-beta.6" }
|
tauri-plugin-http = { version = "2.0.0-beta.6" }
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
"main"
|
"main"
|
||||||
],
|
],
|
||||||
"permissions": [
|
"permissions": [
|
||||||
|
"cli:default",
|
||||||
"path:default",
|
"path:default",
|
||||||
"event:default",
|
"event:default",
|
||||||
"window:default",
|
"window:default",
|
||||||
@ -23,7 +24,6 @@
|
|||||||
"fs:allow-copy-file",
|
"fs:allow-copy-file",
|
||||||
"fs:allow-mkdir",
|
"fs:allow-mkdir",
|
||||||
"fs:allow-remove",
|
"fs:allow-remove",
|
||||||
"fs:allow-remove",
|
|
||||||
"fs:allow-rename",
|
"fs:allow-rename",
|
||||||
"fs:allow-exists",
|
"fs:allow-exists",
|
||||||
"fs:allow-stat",
|
"fs:allow-stat",
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
||||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||||
|
|
||||||
|
pub(crate) mod state;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
env,
|
env,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
@ -9,12 +11,13 @@ use std::{
|
|||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use kcl_lib::settings::types::{
|
use kcl_lib::settings::types::{
|
||||||
file::{FileEntry, Project},
|
file::{FileEntry, Project, ProjectRoute, ProjectState},
|
||||||
project::ProjectConfiguration,
|
project::ProjectConfiguration,
|
||||||
Configuration,
|
Configuration, DEFAULT_PROJECT_KCL_FILE,
|
||||||
};
|
};
|
||||||
use oauth2::TokenResponse;
|
use oauth2::TokenResponse;
|
||||||
use tauri::{ipc::InvokeError, Manager};
|
use tauri::{ipc::InvokeError, Manager};
|
||||||
|
use tauri_plugin_cli::CliExt;
|
||||||
use tauri_plugin_shell::ShellExt;
|
use tauri_plugin_shell::ShellExt;
|
||||||
|
|
||||||
const DEFAULT_HOST: &str = "https://api.zoo.dev";
|
const DEFAULT_HOST: &str = "https://api.zoo.dev";
|
||||||
@ -36,14 +39,35 @@ fn get_initial_default_dir(app: tauri::AppHandle) -> Result<PathBuf, InvokeError
|
|||||||
Ok(dir.join(PROJECT_FOLDER))
|
Ok(dir.join(PROJECT_FOLDER))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_app_settings_file_path(app: &tauri::AppHandle) -> Result<PathBuf, InvokeError> {
|
#[tauri::command]
|
||||||
|
async fn get_state(app: tauri::AppHandle) -> Result<Option<ProjectState>, InvokeError> {
|
||||||
|
let store = app.state::<state::Store>();
|
||||||
|
Ok(store.get().await)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn set_state(app: tauri::AppHandle, state: Option<ProjectState>) -> Result<(), InvokeError> {
|
||||||
|
let store = app.state::<state::Store>();
|
||||||
|
store.set(state).await;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_app_settings_file_path(app: &tauri::AppHandle) -> Result<PathBuf, InvokeError> {
|
||||||
let app_config_dir = app.path().app_config_dir()?;
|
let app_config_dir = app.path().app_config_dir()?;
|
||||||
|
|
||||||
|
// Ensure this directory exists.
|
||||||
|
if !app_config_dir.exists() {
|
||||||
|
tokio::fs::create_dir_all(&app_config_dir)
|
||||||
|
.await
|
||||||
|
.map_err(|e| InvokeError::from_anyhow(e.into()))?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(app_config_dir.join(SETTINGS_FILE_NAME))
|
Ok(app_config_dir.join(SETTINGS_FILE_NAME))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn read_app_settings_file(app: tauri::AppHandle) -> Result<Configuration, InvokeError> {
|
async fn read_app_settings_file(app: tauri::AppHandle) -> Result<Configuration, InvokeError> {
|
||||||
let mut settings_path = get_app_settings_file_path(&app)?;
|
let mut settings_path = get_app_settings_file_path(&app).await?;
|
||||||
let mut needs_migration = false;
|
let mut needs_migration = false;
|
||||||
|
|
||||||
// Check if this file exists.
|
// Check if this file exists.
|
||||||
@ -88,7 +112,7 @@ async fn read_app_settings_file(app: tauri::AppHandle) -> Result<Configuration,
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn write_app_settings_file(app: tauri::AppHandle, configuration: Configuration) -> Result<(), InvokeError> {
|
async fn write_app_settings_file(app: tauri::AppHandle, configuration: Configuration) -> Result<(), InvokeError> {
|
||||||
let settings_path = get_app_settings_file_path(&app)?;
|
let settings_path = get_app_settings_file_path(&app).await?;
|
||||||
let contents = toml::to_string_pretty(&configuration).map_err(|e| InvokeError::from_anyhow(e.into()))?;
|
let contents = toml::to_string_pretty(&configuration).map_err(|e| InvokeError::from_anyhow(e.into()))?;
|
||||||
tokio::fs::write(settings_path, contents.as_bytes())
|
tokio::fs::write(settings_path, contents.as_bytes())
|
||||||
.await
|
.await
|
||||||
@ -97,13 +121,19 @@ async fn write_app_settings_file(app: tauri::AppHandle, configuration: Configura
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_project_settings_file_path(app_settings: Configuration, project_name: &str) -> Result<PathBuf, InvokeError> {
|
async fn get_project_settings_file_path(
|
||||||
Ok(app_settings
|
app_settings: Configuration,
|
||||||
.settings
|
project_name: &str,
|
||||||
.project
|
) -> Result<PathBuf, InvokeError> {
|
||||||
.directory
|
let project_dir = app_settings.settings.project.directory.join(project_name);
|
||||||
.join(project_name)
|
|
||||||
.join(PROJECT_SETTINGS_FILE_NAME))
|
if !project_dir.exists() {
|
||||||
|
tokio::fs::create_dir_all(&project_dir)
|
||||||
|
.await
|
||||||
|
.map_err(|e| InvokeError::from_anyhow(e.into()))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(project_dir.join(PROJECT_SETTINGS_FILE_NAME))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@ -111,7 +141,7 @@ async fn read_project_settings_file(
|
|||||||
app_settings: Configuration,
|
app_settings: Configuration,
|
||||||
project_name: &str,
|
project_name: &str,
|
||||||
) -> Result<ProjectConfiguration, InvokeError> {
|
) -> Result<ProjectConfiguration, InvokeError> {
|
||||||
let settings_path = get_project_settings_file_path(app_settings, project_name)?;
|
let settings_path = get_project_settings_file_path(app_settings, project_name).await?;
|
||||||
|
|
||||||
// Check if this file exists.
|
// Check if this file exists.
|
||||||
if !settings_path.exists() {
|
if !settings_path.exists() {
|
||||||
@ -133,7 +163,7 @@ async fn write_project_settings_file(
|
|||||||
project_name: &str,
|
project_name: &str,
|
||||||
configuration: ProjectConfiguration,
|
configuration: ProjectConfiguration,
|
||||||
) -> Result<(), InvokeError> {
|
) -> Result<(), InvokeError> {
|
||||||
let settings_path = get_project_settings_file_path(app_settings, project_name)?;
|
let settings_path = get_project_settings_file_path(app_settings, project_name).await?;
|
||||||
let contents = toml::to_string_pretty(&configuration).map_err(|e| InvokeError::from_anyhow(e.into()))?;
|
let contents = toml::to_string_pretty(&configuration).map_err(|e| InvokeError::from_anyhow(e.into()))?;
|
||||||
tokio::fs::write(settings_path, contents.as_bytes())
|
tokio::fs::write(settings_path, contents.as_bytes())
|
||||||
.await
|
.await
|
||||||
@ -172,13 +202,19 @@ async fn list_projects(configuration: Configuration) -> Result<Vec<Project>, Inv
|
|||||||
|
|
||||||
/// Get information about a project.
|
/// Get information about a project.
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn get_project_info(configuration: Configuration, project_name: &str) -> Result<Project, InvokeError> {
|
async fn get_project_info(configuration: Configuration, project_path: &str) -> Result<Project, InvokeError> {
|
||||||
configuration
|
configuration
|
||||||
.get_project_info(project_name)
|
.get_project_info(project_path)
|
||||||
.await
|
.await
|
||||||
.map_err(InvokeError::from_anyhow)
|
.map_err(InvokeError::from_anyhow)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse the project route.
|
||||||
|
#[tauri::command]
|
||||||
|
async fn parse_project_route(configuration: Configuration, route: &str) -> Result<ProjectRoute, InvokeError> {
|
||||||
|
ProjectRoute::from_route(&configuration, route).map_err(InvokeError::from_anyhow)
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn read_dir_recursive(path: &str) -> Result<FileEntry, InvokeError> {
|
async fn read_dir_recursive(path: &str) -> Result<FileEntry, InvokeError> {
|
||||||
kcl_lib::settings::utils::walk_dir(&Path::new(path).to_path_buf())
|
kcl_lib::settings::utils::walk_dir(&Path::new(path).to_path_buf())
|
||||||
@ -316,23 +352,15 @@ fn show_in_folder(path: &str) -> Result<(), InvokeError> {
|
|||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
.setup(|_app| {
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
{
|
|
||||||
_app.get_webview("main").unwrap().open_devtools();
|
|
||||||
}
|
|
||||||
#[cfg(not(debug_assertions))]
|
|
||||||
{
|
|
||||||
_app.handle().plugin(tauri_plugin_updater::Builder::new().build())?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(tauri::generate_handler![
|
||||||
|
get_state,
|
||||||
|
set_state,
|
||||||
get_initial_default_dir,
|
get_initial_default_dir,
|
||||||
initialize_project_directory,
|
initialize_project_directory,
|
||||||
create_new_project_directory,
|
create_new_project_directory,
|
||||||
list_projects,
|
list_projects,
|
||||||
get_project_info,
|
get_project_info,
|
||||||
|
parse_project_route,
|
||||||
get_user,
|
get_user,
|
||||||
login,
|
login,
|
||||||
read_dir_recursive,
|
read_dir_recursive,
|
||||||
@ -342,6 +370,138 @@ fn main() -> Result<()> {
|
|||||||
read_project_settings_file,
|
read_project_settings_file,
|
||||||
write_project_settings_file,
|
write_project_settings_file,
|
||||||
])
|
])
|
||||||
|
.plugin(tauri_plugin_cli::init())
|
||||||
|
.setup(|app| {
|
||||||
|
// Do update things.
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
{
|
||||||
|
app.get_webview("main").unwrap().open_devtools();
|
||||||
|
}
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
{
|
||||||
|
app.handle().plugin(tauri_plugin_updater::Builder::new().build())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut verbose = false;
|
||||||
|
let mut source_path: Option<PathBuf> = None;
|
||||||
|
match app.cli().matches() {
|
||||||
|
// `matches` here is a Struct with { args, subcommand }.
|
||||||
|
// `args` is `HashMap<String, ArgData>` where `ArgData` is a struct with { value, occurrences }.
|
||||||
|
// `subcommand` is `Option<Box<SubcommandMatches>>` where `SubcommandMatches` is a struct with { name, matches }.
|
||||||
|
Ok(matches) => {
|
||||||
|
if let Some(verbose_flag) = matches.args.get("verbose") {
|
||||||
|
let Some(value) = verbose_flag.value.as_bool() else {
|
||||||
|
return Err(
|
||||||
|
anyhow::anyhow!("Error parsing CLI arguments: verbose flag is not a boolean").into(),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
verbose = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the path we are trying to open.
|
||||||
|
if let Some(source_arg) = matches.args.get("source") {
|
||||||
|
// We don't do an else here because this can be null.
|
||||||
|
if let Some(value) = source_arg.value.as_str() {
|
||||||
|
source_path = Some(Path::new(value).to_path_buf());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
return Err(anyhow::anyhow!("Error parsing CLI arguments: {:?}", err).into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have a source path to open, make sure it exists.
|
||||||
|
let Some(source_path) = source_path else {
|
||||||
|
// The user didn't provide a source path to open.
|
||||||
|
// Run the app as normal.
|
||||||
|
app.manage(state::Store::default());
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
if !source_path.exists() {
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"Error: the path `{}` you are trying to open does not exist",
|
||||||
|
source_path.display()
|
||||||
|
)
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let runner: tauri::async_runtime::JoinHandle<Result<ProjectState>> =
|
||||||
|
tauri::async_runtime::spawn(async move {
|
||||||
|
// Fix for "." path, which is the current directory.
|
||||||
|
let source_path = if source_path == Path::new(".") {
|
||||||
|
std::env::current_dir()
|
||||||
|
.map_err(|e| anyhow::anyhow!("Error getting the current directory: {:?}", e))?
|
||||||
|
} else {
|
||||||
|
source_path
|
||||||
|
};
|
||||||
|
|
||||||
|
// If the path is a directory, let's assume it is a project directory.
|
||||||
|
if source_path.is_dir() {
|
||||||
|
// Load the details about the project from the path.
|
||||||
|
let project = Project::from_path(&source_path).await.map_err(|e| {
|
||||||
|
anyhow::anyhow!("Error loading project from path {}: {:?}", source_path.display(), e)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if verbose {
|
||||||
|
println!("Project loaded from path: {}", source_path.display());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the default file in the project.
|
||||||
|
// Write the initial project file.
|
||||||
|
let project_file = source_path.join(DEFAULT_PROJECT_KCL_FILE);
|
||||||
|
tokio::fs::write(&project_file, vec![]).await?;
|
||||||
|
|
||||||
|
return Ok(ProjectState {
|
||||||
|
project,
|
||||||
|
current_file: Some(project_file.display().to_string()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// We were given a file path, not a directory.
|
||||||
|
// Let's get the parent directory of the file.
|
||||||
|
let parent = source_path.parent().ok_or_else(|| {
|
||||||
|
anyhow::anyhow!(
|
||||||
|
"Error getting the parent directory of the file: {}",
|
||||||
|
source_path.display()
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Load the details about the project from the parent directory.
|
||||||
|
let project = Project::from_path(&parent).await.map_err(|e| {
|
||||||
|
anyhow::anyhow!("Error loading project from path {}: {:?}", source_path.display(), e)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if verbose {
|
||||||
|
println!(
|
||||||
|
"Project loaded from path: {}, current file: {}",
|
||||||
|
parent.display(),
|
||||||
|
source_path.display()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ProjectState {
|
||||||
|
project,
|
||||||
|
current_file: Some(source_path.display().to_string()),
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// Block on the handle.
|
||||||
|
let store = tauri::async_runtime::block_on(runner)??;
|
||||||
|
|
||||||
|
// Create a state object to hold the project.
|
||||||
|
app.manage(state::Store::new(store));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.register_uri_scheme_protocol("zoo-modeling-app", |_app, request| {
|
||||||
|
let path = request.uri().path();
|
||||||
|
dbg!(path);
|
||||||
|
println!("Requesting path: {}", path);
|
||||||
|
|
||||||
|
tauri::http::Response::builder().status(200).body(b"{}").unwrap()
|
||||||
|
})
|
||||||
.plugin(tauri_plugin_dialog::init())
|
.plugin(tauri_plugin_dialog::init())
|
||||||
.plugin(tauri_plugin_fs::init())
|
.plugin(tauri_plugin_fs::init())
|
||||||
.plugin(tauri_plugin_http::init())
|
.plugin(tauri_plugin_http::init())
|
||||||
|
21
src-tauri/src/state.rs
Normal file
21
src-tauri/src/state.rs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
//! State management for the application.
|
||||||
|
|
||||||
|
use kcl_lib::settings::types::file::ProjectState;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct Store(Mutex<Option<ProjectState>>);
|
||||||
|
|
||||||
|
impl Store {
|
||||||
|
pub fn new(p: ProjectState) -> Self {
|
||||||
|
Self(Mutex::new(Some(p)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get(&self) -> Option<ProjectState> {
|
||||||
|
self.0.lock().await.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set(&self, p: Option<ProjectState>) {
|
||||||
|
*self.0.lock().await = p;
|
||||||
|
}
|
||||||
|
}
|
@ -50,10 +50,26 @@
|
|||||||
},
|
},
|
||||||
"identifier": "dev.zoo.modeling-app",
|
"identifier": "dev.zoo.modeling-app",
|
||||||
"plugins": {
|
"plugins": {
|
||||||
|
"cli": {
|
||||||
|
"description": "Zoo Modeling App CLI",
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"short": "v",
|
||||||
|
"name": "verbose",
|
||||||
|
"description": "Verbosity level"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "source",
|
||||||
|
"index": 1,
|
||||||
|
"takesValue": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"subcommands": {}
|
||||||
|
},
|
||||||
"shell": {
|
"shell": {
|
||||||
"open": true
|
"open": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"productName": "Zoo Modeling App",
|
"productName": "Zoo Modeling App",
|
||||||
"version": "0.19.0"
|
"version": "0.19.3"
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@ import SettingsAuthProvider from 'components/SettingsAuthProvider'
|
|||||||
import LspProvider from 'components/LspProvider'
|
import LspProvider from 'components/LspProvider'
|
||||||
import { KclContextProvider } from 'lang/KclProvider'
|
import { KclContextProvider } from 'lang/KclProvider'
|
||||||
import { BROWSER_PROJECT_NAME } from 'lib/constants'
|
import { BROWSER_PROJECT_NAME } from 'lib/constants'
|
||||||
|
import { getState, setState } from 'lib/tauri'
|
||||||
|
|
||||||
const router = createBrowserRouter([
|
const router = createBrowserRouter([
|
||||||
{
|
{
|
||||||
@ -52,10 +53,29 @@ const router = createBrowserRouter([
|
|||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: paths.INDEX,
|
path: paths.INDEX,
|
||||||
loader: () =>
|
loader: async () => {
|
||||||
isTauri()
|
const inTauri = isTauri()
|
||||||
|
if (inTauri) {
|
||||||
|
const appState = await getState()
|
||||||
|
|
||||||
|
if (appState) {
|
||||||
|
// Reset the state.
|
||||||
|
// We do this so that we load the initial state from the cli but everything
|
||||||
|
// else we can ignore.
|
||||||
|
await setState(undefined)
|
||||||
|
// Redirect to the file if we have a file path.
|
||||||
|
if (appState.current_file) {
|
||||||
|
return redirect(
|
||||||
|
paths.FILE + '/' + encodeURIComponent(appState.current_file)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return inTauri
|
||||||
? redirect(paths.HOME)
|
? redirect(paths.HOME)
|
||||||
: redirect(paths.FILE + '/%2F' + BROWSER_PROJECT_NAME),
|
: redirect(paths.FILE + '/%2F' + BROWSER_PROJECT_NAME)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
loader: fileLoader,
|
loader: fileLoader,
|
||||||
|
@ -62,7 +62,7 @@ export const FileMachineProvider = ({
|
|||||||
services: {
|
services: {
|
||||||
readFiles: async (context: ContextFrom<typeof fileMachine>) => {
|
readFiles: async (context: ContextFrom<typeof fileMachine>) => {
|
||||||
const newFiles = isTauri()
|
const newFiles = isTauri()
|
||||||
? (await getProjectInfo(context.project.name)).children
|
? (await getProjectInfo(context.project.path)).children
|
||||||
: []
|
: []
|
||||||
return {
|
return {
|
||||||
...context.project,
|
...context.project,
|
||||||
|
@ -3,7 +3,7 @@ import { paths } from 'lib/paths'
|
|||||||
import { ActionButton } from './ActionButton'
|
import { ActionButton } from './ActionButton'
|
||||||
import Tooltip from './Tooltip'
|
import Tooltip from './Tooltip'
|
||||||
import { Dispatch, useEffect, useRef, useState } from 'react'
|
import { Dispatch, useEffect, useRef, useState } from 'react'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate, useRouteLoaderData } from 'react-router-dom'
|
||||||
import { Dialog, Disclosure } from '@headlessui/react'
|
import { Dialog, Disclosure } from '@headlessui/react'
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
import { faChevronRight, faTrashAlt } from '@fortawesome/free-solid-svg-icons'
|
import { faChevronRight, faTrashAlt } from '@fortawesome/free-solid-svg-icons'
|
||||||
@ -133,18 +133,13 @@ const FileTreeItem = ({
|
|||||||
project,
|
project,
|
||||||
currentFile,
|
currentFile,
|
||||||
fileOrDir,
|
fileOrDir,
|
||||||
closePanel,
|
onDoubleClick,
|
||||||
level = 0,
|
level = 0,
|
||||||
}: {
|
}: {
|
||||||
project?: IndexLoaderData['project']
|
project?: IndexLoaderData['project']
|
||||||
currentFile?: IndexLoaderData['file']
|
currentFile?: IndexLoaderData['file']
|
||||||
fileOrDir: FileEntry
|
fileOrDir: FileEntry
|
||||||
closePanel: (
|
onDoubleClick?: () => void
|
||||||
focusableElement?:
|
|
||||||
| HTMLElement
|
|
||||||
| React.MutableRefObject<HTMLElement | null>
|
|
||||||
| undefined
|
|
||||||
) => void
|
|
||||||
level?: number
|
level?: number
|
||||||
}) => {
|
}) => {
|
||||||
const { send, context } = useFileContext()
|
const { send, context } = useFileContext()
|
||||||
@ -186,7 +181,7 @@ const FileTreeItem = ({
|
|||||||
// Open kcl files
|
// Open kcl files
|
||||||
navigate(`${paths.FILE}/${encodeURIComponent(fileOrDir.path)}`)
|
navigate(`${paths.FILE}/${encodeURIComponent(fileOrDir.path)}`)
|
||||||
}
|
}
|
||||||
closePanel()
|
onDoubleClick?.()
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -194,8 +189,10 @@ const FileTreeItem = ({
|
|||||||
{fileOrDir.children === undefined ? (
|
{fileOrDir.children === undefined ? (
|
||||||
<li
|
<li
|
||||||
className={
|
className={
|
||||||
'group m-0 p-0 border-solid border-0 hover:text-primary hover:bg-primary/5 focus-within:bg-primary/5 ' +
|
'group m-0 p-0 border-solid border-0 hover:bg-primary/5 focus-within:bg-primary/5 dark:hover:bg-primary/20 dark:focus-within:bg-primary/20 ' +
|
||||||
(isCurrentFile ? '!bg-primary/10 !text-primary' : '')
|
(isCurrentFile
|
||||||
|
? '!bg-primary/10 !text-primary dark:!bg-primary/20 dark:!text-inherit'
|
||||||
|
: '')
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{!isRenaming ? (
|
{!isRenaming ? (
|
||||||
@ -227,9 +224,9 @@ const FileTreeItem = ({
|
|||||||
{!isRenaming ? (
|
{!isRenaming ? (
|
||||||
<Disclosure.Button
|
<Disclosure.Button
|
||||||
className={
|
className={
|
||||||
' group border-none text-sm rounded-none p-0 m-0 flex items-center justify-start w-full py-0.5 hover:text-primary hover:bg-primary/5' +
|
' group border-none text-sm rounded-none p-0 m-0 flex items-center justify-start w-full py-0.5 hover:text-primary hover:bg-primary/5 dark:hover:text-inherit dark:hover:bg-primary/10' +
|
||||||
(context.selectedDirectory.path.includes(fileOrDir.path)
|
(context.selectedDirectory.path.includes(fileOrDir.path)
|
||||||
? ' ui-open:text-primary'
|
? ' ui-open:bg-primary/10'
|
||||||
: '')
|
: '')
|
||||||
}
|
}
|
||||||
style={{ paddingInlineStart: getIndentationCSS(level) }}
|
style={{ paddingInlineStart: getIndentationCSS(level) }}
|
||||||
@ -293,7 +290,7 @@ const FileTreeItem = ({
|
|||||||
fileOrDir={child}
|
fileOrDir={child}
|
||||||
project={project}
|
project={project}
|
||||||
currentFile={currentFile}
|
currentFile={currentFile}
|
||||||
closePanel={closePanel}
|
onDoubleClick={onDoubleClick}
|
||||||
level={level + 1}
|
level={level + 1}
|
||||||
key={level + '-' + child.path}
|
key={level + '-' + child.path}
|
||||||
/>
|
/>
|
||||||
@ -325,20 +322,8 @@ interface FileTreeProps {
|
|||||||
) => void
|
) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FileTree = ({
|
export const FileTreeMenu = () => {
|
||||||
className = '',
|
const { send } = useFileContext()
|
||||||
file,
|
|
||||||
closePanel,
|
|
||||||
}: FileTreeProps) => {
|
|
||||||
const { send, context } = useFileContext()
|
|
||||||
const docuemntHasFocus = useDocumentHasFocus()
|
|
||||||
useHotkeys('meta + n', createFile)
|
|
||||||
useHotkeys('meta + shift + n', createFolder)
|
|
||||||
|
|
||||||
// Refresh the file tree when the document gets focus
|
|
||||||
useEffect(() => {
|
|
||||||
send({ type: 'Refresh' })
|
|
||||||
}, [docuemntHasFocus])
|
|
||||||
|
|
||||||
async function createFile() {
|
async function createFile() {
|
||||||
send({ type: 'Create file', data: { name: '', makeDir: false } })
|
send({ type: 'Create file', data: { name: '', makeDir: false } })
|
||||||
@ -348,58 +333,88 @@ export const FileTree = ({
|
|||||||
send({ type: 'Create file', data: { name: '', makeDir: true } })
|
send({ type: 'Create file', data: { name: '', makeDir: true } })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useHotkeys('meta + n', createFile)
|
||||||
|
useHotkeys('meta + shift + n', createFolder)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
icon={{
|
||||||
|
icon: 'filePlus',
|
||||||
|
iconClassName: '!text-current',
|
||||||
|
bgClassName: 'bg-transparent',
|
||||||
|
}}
|
||||||
|
className="!p-0 !bg-transparent hover:text-primary border-transparent hover:border-primary !outline-none"
|
||||||
|
onClick={createFile}
|
||||||
|
>
|
||||||
|
<Tooltip position="bottom-right" delay={750}>
|
||||||
|
Create file
|
||||||
|
</Tooltip>
|
||||||
|
</ActionButton>
|
||||||
|
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
icon={{
|
||||||
|
icon: 'folderPlus',
|
||||||
|
iconClassName: '!text-current',
|
||||||
|
bgClassName: 'bg-transparent',
|
||||||
|
}}
|
||||||
|
className="!p-0 !bg-transparent hover:text-primary border-transparent hover:border-primary !outline-none"
|
||||||
|
onClick={createFolder}
|
||||||
|
>
|
||||||
|
<Tooltip position="bottom-right" delay={750}>
|
||||||
|
Create folder
|
||||||
|
</Tooltip>
|
||||||
|
</ActionButton>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FileTree = ({ className = '', closePanel }: FileTreeProps) => {
|
||||||
return (
|
return (
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
<div className="flex items-center gap-1 px-4 py-1 bg-chalkboard-20/40 dark:bg-chalkboard-80/50 border-b border-b-chalkboard-30 dark:border-b-chalkboard-80">
|
<div className="flex items-center gap-1 px-4 py-1 bg-chalkboard-20/40 dark:bg-chalkboard-80/50 border-b border-b-chalkboard-30 dark:border-b-chalkboard-80">
|
||||||
<h2 className="flex-1 m-0 p-0 text-sm mono">Files</h2>
|
<h2 className="flex-1 m-0 p-0 text-sm mono">Files</h2>
|
||||||
<ActionButton
|
<FileTreeMenu />
|
||||||
Element="button"
|
|
||||||
icon={{
|
|
||||||
icon: 'filePlus',
|
|
||||||
iconClassName: '!text-current',
|
|
||||||
bgClassName: 'bg-transparent',
|
|
||||||
}}
|
|
||||||
className="!p-0 !bg-transparent hover:text-primary border-transparent hover:border-primary !outline-none"
|
|
||||||
onClick={createFile}
|
|
||||||
>
|
|
||||||
<Tooltip position="bottom-right" delay={750}>
|
|
||||||
Create file
|
|
||||||
</Tooltip>
|
|
||||||
</ActionButton>
|
|
||||||
|
|
||||||
<ActionButton
|
|
||||||
Element="button"
|
|
||||||
icon={{
|
|
||||||
icon: 'folderPlus',
|
|
||||||
iconClassName: '!text-current',
|
|
||||||
bgClassName: 'bg-transparent',
|
|
||||||
}}
|
|
||||||
className="!p-0 !bg-transparent hover:text-primary border-transparent hover:border-primary !outline-none"
|
|
||||||
onClick={createFolder}
|
|
||||||
>
|
|
||||||
<Tooltip position="bottom-right" delay={750}>
|
|
||||||
Create folder
|
|
||||||
</Tooltip>
|
|
||||||
</ActionButton>
|
|
||||||
</div>
|
|
||||||
<div className="overflow-auto max-h-full pb-12">
|
|
||||||
<ul
|
|
||||||
className="m-0 p-0 text-sm"
|
|
||||||
onClickCapture={(e) => {
|
|
||||||
send({ type: 'Set selected directory', data: context.project })
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{sortProject(context.project.children || []).map((fileOrDir) => (
|
|
||||||
<FileTreeItem
|
|
||||||
project={context.project}
|
|
||||||
currentFile={file}
|
|
||||||
fileOrDir={fileOrDir}
|
|
||||||
closePanel={closePanel}
|
|
||||||
key={fileOrDir.path}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
|
<FileTreeInner onDoubleClick={closePanel} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FileTreeInner = ({
|
||||||
|
onDoubleClick,
|
||||||
|
}: {
|
||||||
|
onDoubleClick?: () => void
|
||||||
|
}) => {
|
||||||
|
const loaderData = useRouteLoaderData(paths.FILE) as IndexLoaderData
|
||||||
|
const { send, context } = useFileContext()
|
||||||
|
const documentHasFocus = useDocumentHasFocus()
|
||||||
|
|
||||||
|
// Refresh the file tree when the document gets focus
|
||||||
|
useEffect(() => {
|
||||||
|
send({ type: 'Refresh' })
|
||||||
|
}, [documentHasFocus])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="overflow-auto max-h-full pb-12">
|
||||||
|
<ul
|
||||||
|
className="m-0 p-0 text-sm"
|
||||||
|
onClickCapture={(e) => {
|
||||||
|
send({ type: 'Set selected directory', data: context.project })
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{sortProject(context.project.children || []).map((fileOrDir) => (
|
||||||
|
<FileTreeItem
|
||||||
|
project={context.project}
|
||||||
|
currentFile={loaderData?.file}
|
||||||
|
fileOrDir={fileOrDir}
|
||||||
|
onDoubleClick={onDoubleClick}
|
||||||
|
key={fileOrDir.path}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ import type * as LSP from 'vscode-languageserver-protocol'
|
|||||||
import React, { createContext, useMemo, useEffect, useContext } from 'react'
|
import React, { createContext, useMemo, useEffect, useContext } from 'react'
|
||||||
import { FromServer, IntoServer } from 'editor/plugins/lsp/codec'
|
import { FromServer, IntoServer } from 'editor/plugins/lsp/codec'
|
||||||
import Client from '../editor/plugins/lsp/client'
|
import Client from '../editor/plugins/lsp/client'
|
||||||
import { DEV, TEST } from 'env'
|
import { TEST, VITE_KC_API_BASE_URL } from 'env'
|
||||||
import kclLanguage from 'editor/plugins/lsp/kcl/language'
|
import kclLanguage from 'editor/plugins/lsp/kcl/language'
|
||||||
import { copilotPlugin } from 'editor/plugins/lsp/copilot'
|
import { copilotPlugin } from 'editor/plugins/lsp/copilot'
|
||||||
import { useStore } from 'useStore'
|
import { useStore } from 'useStore'
|
||||||
@ -103,7 +103,7 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
wasmUrl: wasmUrl(),
|
wasmUrl: wasmUrl(),
|
||||||
token: token,
|
token: token,
|
||||||
baseUnit: defaultUnit.current,
|
baseUnit: defaultUnit.current,
|
||||||
devMode: DEV,
|
apiBaseUrl: VITE_KC_API_BASE_URL,
|
||||||
}
|
}
|
||||||
lspWorker.postMessage({
|
lspWorker.postMessage({
|
||||||
worker: LspWorker.Kcl,
|
worker: LspWorker.Kcl,
|
||||||
@ -177,7 +177,7 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
const initEvent: CopilotWorkerOptions = {
|
const initEvent: CopilotWorkerOptions = {
|
||||||
wasmUrl: wasmUrl(),
|
wasmUrl: wasmUrl(),
|
||||||
token: token,
|
token: token,
|
||||||
devMode: DEV,
|
apiBaseUrl: VITE_KC_API_BASE_URL,
|
||||||
}
|
}
|
||||||
lspWorker.postMessage({
|
lspWorker.postMessage({
|
||||||
worker: LspWorker.Copilot,
|
worker: LspWorker.Copilot,
|
||||||
|
@ -56,6 +56,7 @@ import toast from 'react-hot-toast'
|
|||||||
import { EditorSelection } from '@uiw/react-codemirror'
|
import { EditorSelection } from '@uiw/react-codemirror'
|
||||||
import { CoreDumpManager } from 'lib/coredump'
|
import { CoreDumpManager } from 'lib/coredump'
|
||||||
import { useHotkeys } from 'react-hotkeys-hook'
|
import { useHotkeys } from 'react-hotkeys-hook'
|
||||||
|
import { useSearchParams } from 'react-router-dom'
|
||||||
import { letEngineAnimateAndSyncCamAfter } from 'clientSideScene/CameraControls'
|
import { letEngineAnimateAndSyncCamAfter } from 'clientSideScene/CameraControls'
|
||||||
|
|
||||||
type MachineContext<T extends AnyStateMachine> = {
|
type MachineContext<T extends AnyStateMachine> = {
|
||||||
@ -84,7 +85,12 @@ export const ModelingMachineProvider = ({
|
|||||||
} = useSettingsAuthContext()
|
} = useSettingsAuthContext()
|
||||||
const token = auth?.context?.token
|
const token = auth?.context?.token
|
||||||
const streamRef = useRef<HTMLDivElement>(null)
|
const streamRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
|
let [searchParams] = useSearchParams()
|
||||||
|
const pool = searchParams.get('pool')
|
||||||
|
|
||||||
useSetupEngineManager(streamRef, token, {
|
useSetupEngineManager(streamRef, token, {
|
||||||
|
pool: pool,
|
||||||
theme: theme.current,
|
theme: theme.current,
|
||||||
highlightEdges: highlightEdges.current,
|
highlightEdges: highlightEdges.current,
|
||||||
enableSSAO: enableSSAO.current,
|
enableSSAO: enableSSAO.current,
|
||||||
|
@ -10,21 +10,32 @@ import { KclEditorMenu } from 'components/ModelingSidebar/ModelingPanes/KclEdito
|
|||||||
import { CustomIconName } from 'components/CustomIcon'
|
import { CustomIconName } from 'components/CustomIcon'
|
||||||
import { KclEditorPane } from 'components/ModelingSidebar/ModelingPanes/KclEditorPane'
|
import { KclEditorPane } from 'components/ModelingSidebar/ModelingPanes/KclEditorPane'
|
||||||
import { ReactNode } from 'react'
|
import { ReactNode } from 'react'
|
||||||
import type { PaneType } from 'useStore'
|
|
||||||
import { MemoryPane } from './MemoryPane'
|
import { MemoryPane } from './MemoryPane'
|
||||||
import { KclErrorsPane, LogsPane } from './LoggingPanes'
|
import { KclErrorsPane, LogsPane } from './LoggingPanes'
|
||||||
import { DebugPane } from './DebugPane'
|
import { DebugPane } from './DebugPane'
|
||||||
|
import { FileTreeInner, FileTreeMenu } from 'components/FileTree'
|
||||||
|
|
||||||
export type Pane = {
|
export type SidebarType =
|
||||||
id: PaneType
|
| 'code'
|
||||||
|
| 'debug'
|
||||||
|
| 'export'
|
||||||
|
| 'files'
|
||||||
|
| 'kclErrors'
|
||||||
|
| 'logs'
|
||||||
|
| 'lspMessages'
|
||||||
|
| 'variables'
|
||||||
|
|
||||||
|
export type SidebarPane = {
|
||||||
|
id: SidebarType
|
||||||
title: string
|
title: string
|
||||||
icon: CustomIconName | IconDefinition
|
icon: CustomIconName | IconDefinition
|
||||||
|
keybinding: string
|
||||||
Content: ReactNode | React.FC
|
Content: ReactNode | React.FC
|
||||||
Menu?: ReactNode | React.FC
|
Menu?: ReactNode | React.FC
|
||||||
keybinding: string
|
hideOnPlatform?: 'desktop' | 'web'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const topPanes: Pane[] = [
|
export const topPanes: SidebarPane[] = [
|
||||||
{
|
{
|
||||||
id: 'code',
|
id: 'code',
|
||||||
title: 'KCL Code',
|
title: 'KCL Code',
|
||||||
@ -33,9 +44,18 @@ export const topPanes: Pane[] = [
|
|||||||
keybinding: 'shift + c',
|
keybinding: 'shift + c',
|
||||||
Menu: KclEditorMenu,
|
Menu: KclEditorMenu,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'files',
|
||||||
|
title: 'Project Files',
|
||||||
|
icon: 'folder',
|
||||||
|
Content: FileTreeInner,
|
||||||
|
keybinding: 'shift + f',
|
||||||
|
Menu: FileTreeMenu,
|
||||||
|
hideOnPlatform: 'web',
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
export const bottomPanes: Pane[] = [
|
export const bottomPanes: SidebarPane[] = [
|
||||||
{
|
{
|
||||||
id: 'variables',
|
id: 'variables',
|
||||||
title: 'Variables',
|
title: 'Variables',
|
||||||
|
@ -2,13 +2,19 @@ import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
|||||||
import { Resizable } from 're-resizable'
|
import { Resizable } from 're-resizable'
|
||||||
import { useCallback, useEffect, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
import { useHotkeys } from 'react-hotkeys-hook'
|
import { useHotkeys } from 'react-hotkeys-hook'
|
||||||
import { PaneType, useStore } from 'useStore'
|
import { useStore } from 'useStore'
|
||||||
import { Tab } from '@headlessui/react'
|
import { Tab } from '@headlessui/react'
|
||||||
import { Pane, bottomPanes, topPanes } from './ModelingPanes'
|
import {
|
||||||
|
SidebarPane,
|
||||||
|
SidebarType,
|
||||||
|
bottomPanes,
|
||||||
|
topPanes,
|
||||||
|
} from './ModelingPanes'
|
||||||
import Tooltip from 'components/Tooltip'
|
import Tooltip from 'components/Tooltip'
|
||||||
import { ActionIcon } from 'components/ActionIcon'
|
import { ActionIcon } from 'components/ActionIcon'
|
||||||
import styles from './ModelingSidebar.module.css'
|
import styles from './ModelingSidebar.module.css'
|
||||||
import { ModelingPane } from './ModelingPane'
|
import { ModelingPane } from './ModelingPane'
|
||||||
|
import { isTauri } from 'lib/isTauri'
|
||||||
|
|
||||||
interface ModelingSidebarProps {
|
interface ModelingSidebarProps {
|
||||||
paneOpacity: '' | 'opacity-20' | 'opacity-40'
|
paneOpacity: '' | 'opacity-20' | 'opacity-40'
|
||||||
@ -52,7 +58,7 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface ModelingSidebarSectionProps {
|
interface ModelingSidebarSectionProps {
|
||||||
panes: Pane[]
|
panes: SidebarPane[]
|
||||||
alignButtons?: 'start' | 'end'
|
alignButtons?: 'start' | 'end'
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,11 +75,11 @@ function ModelingSidebarSection({
|
|||||||
}))
|
}))
|
||||||
const foundOpenPane = openPanes.find((pane) => paneIds.includes(pane))
|
const foundOpenPane = openPanes.find((pane) => paneIds.includes(pane))
|
||||||
const [currentPane, setCurrentPane] = useState(
|
const [currentPane, setCurrentPane] = useState(
|
||||||
foundOpenPane || ('none' as PaneType | 'none')
|
foundOpenPane || ('none' as SidebarType | 'none')
|
||||||
)
|
)
|
||||||
|
|
||||||
const togglePane = useCallback(
|
const togglePane = useCallback(
|
||||||
(newPane: PaneType | 'none') => {
|
(newPane: SidebarType | 'none') => {
|
||||||
if (newPane === 'none') {
|
if (newPane === 'none') {
|
||||||
setOpenPanes(openPanes.filter((p) => p !== currentPane))
|
setOpenPanes(openPanes.filter((p) => p !== currentPane))
|
||||||
setCurrentPane('none')
|
setCurrentPane('none')
|
||||||
@ -90,9 +96,15 @@ function ModelingSidebarSection({
|
|||||||
|
|
||||||
// Filter out the debug panel if it's not supposed to be shown
|
// Filter out the debug panel if it's not supposed to be shown
|
||||||
// TODO: abstract out for allowing user to configure which panes to show
|
// TODO: abstract out for allowing user to configure which panes to show
|
||||||
const filteredPanes = showDebugPanel.current
|
const filteredPanes = (
|
||||||
? panes
|
showDebugPanel.current ? panes : panes.filter((pane) => pane.id !== 'debug')
|
||||||
: panes.filter((pane) => pane.id !== 'debug')
|
).filter(
|
||||||
|
(pane) =>
|
||||||
|
!pane.hideOnPlatform ||
|
||||||
|
(isTauri()
|
||||||
|
? pane.hideOnPlatform === 'web'
|
||||||
|
: pane.hideOnPlatform === 'desktop')
|
||||||
|
)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
!showDebugPanel.current &&
|
!showDebugPanel.current &&
|
||||||
@ -168,8 +180,8 @@ function ModelingSidebarSection({
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface ModelingPaneButtonProps {
|
interface ModelingPaneButtonProps {
|
||||||
paneConfig: Pane
|
paneConfig: SidebarPane
|
||||||
currentPane: PaneType | 'none'
|
currentPane: SidebarType | 'none'
|
||||||
togglePane: () => void
|
togglePane: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,13 +8,13 @@ export interface KclWorkerOptions {
|
|||||||
wasmUrl: string
|
wasmUrl: string
|
||||||
token: string
|
token: string
|
||||||
baseUnit: UnitLength
|
baseUnit: UnitLength
|
||||||
devMode: boolean
|
apiBaseUrl: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CopilotWorkerOptions {
|
export interface CopilotWorkerOptions {
|
||||||
wasmUrl: string
|
wasmUrl: string
|
||||||
token: string
|
token: string
|
||||||
devMode: boolean
|
apiBaseUrl: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum LspWorkerEventType {
|
export enum LspWorkerEventType {
|
||||||
|
@ -28,11 +28,11 @@ const initialise = async (wasmUrl: string) => {
|
|||||||
export async function copilotLspRun(
|
export async function copilotLspRun(
|
||||||
config: ServerConfig,
|
config: ServerConfig,
|
||||||
token: string,
|
token: string,
|
||||||
devMode: boolean = false
|
baseUrl: string
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
console.log('starting copilot lsp')
|
console.log('starting copilot lsp')
|
||||||
await copilot_lsp_run(config, token, devMode)
|
await copilot_lsp_run(config, token, baseUrl)
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.log('copilot lsp failed', e)
|
console.log('copilot lsp failed', e)
|
||||||
// We can't restart here because a moved value, we should do this another way.
|
// We can't restart here because a moved value, we should do this another way.
|
||||||
@ -44,11 +44,11 @@ export async function kclLspRun(
|
|||||||
engineCommandManager: EngineCommandManager | null,
|
engineCommandManager: EngineCommandManager | null,
|
||||||
token: string,
|
token: string,
|
||||||
baseUnit: string,
|
baseUnit: string,
|
||||||
devMode: boolean = false
|
baseUrl: string
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
console.log('start kcl lsp')
|
console.log('start kcl lsp')
|
||||||
await kcl_lsp_run(config, engineCommandManager, baseUnit, token, devMode)
|
await kcl_lsp_run(config, engineCommandManager, baseUnit, token, baseUrl)
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.log('kcl lsp failed', e)
|
console.log('kcl lsp failed', e)
|
||||||
// We can't restart here because a moved value, we should do this another way.
|
// We can't restart here because a moved value, we should do this another way.
|
||||||
@ -80,12 +80,12 @@ onmessage = function (event) {
|
|||||||
null,
|
null,
|
||||||
kclData.token,
|
kclData.token,
|
||||||
kclData.baseUnit,
|
kclData.baseUnit,
|
||||||
kclData.devMode
|
kclData.apiBaseUrl
|
||||||
)
|
)
|
||||||
break
|
break
|
||||||
case LspWorker.Copilot:
|
case LspWorker.Copilot:
|
||||||
let copilotData = eventData as CopilotWorkerOptions
|
let copilotData = eventData as CopilotWorkerOptions
|
||||||
copilotLspRun(config, copilotData.token, copilotData.devMode)
|
copilotLspRun(config, copilotData.token, copilotData.apiBaseUrl)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -9,10 +9,12 @@ export function useSetupEngineManager(
|
|||||||
streamRef: React.RefObject<HTMLDivElement>,
|
streamRef: React.RefObject<HTMLDivElement>,
|
||||||
token?: string,
|
token?: string,
|
||||||
settings = {
|
settings = {
|
||||||
|
pool: null,
|
||||||
theme: Themes.System,
|
theme: Themes.System,
|
||||||
highlightEdges: true,
|
highlightEdges: true,
|
||||||
enableSSAO: true,
|
enableSSAO: true,
|
||||||
} as {
|
} as {
|
||||||
|
pool: string | null
|
||||||
theme: Themes
|
theme: Themes
|
||||||
highlightEdges: boolean
|
highlightEdges: boolean
|
||||||
enableSSAO: boolean
|
enableSSAO: boolean
|
||||||
@ -35,6 +37,12 @@ export function useSetupEngineManager(
|
|||||||
|
|
||||||
const hasSetNonZeroDimensions = useRef<boolean>(false)
|
const hasSetNonZeroDimensions = useRef<boolean>(false)
|
||||||
|
|
||||||
|
if (settings.pool) {
|
||||||
|
// override the pool param (?pool=) to request a specific engine instance
|
||||||
|
// from a particular pool.
|
||||||
|
engineCommandManager.pool = settings.pool
|
||||||
|
}
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
// Load the engine command manager once with the initial width and height,
|
// Load the engine command manager once with the initial width and height,
|
||||||
// then we do not want to reload it.
|
// then we do not want to reload it.
|
||||||
|
@ -888,6 +888,7 @@ export class EngineCommandManager {
|
|||||||
sceneCommandArtifacts: ArtifactMap = {}
|
sceneCommandArtifacts: ArtifactMap = {}
|
||||||
outSequence = 1
|
outSequence = 1
|
||||||
inSequence = 1
|
inSequence = 1
|
||||||
|
pool?: string
|
||||||
engineConnection?: EngineConnection
|
engineConnection?: EngineConnection
|
||||||
defaultPlanes: DefaultPlanes | null = null
|
defaultPlanes: DefaultPlanes | null = null
|
||||||
commandLogs: CommandLog[] = []
|
commandLogs: CommandLog[] = []
|
||||||
@ -914,8 +915,9 @@ export class EngineCommandManager {
|
|||||||
callbacksEngineStateConnection: ((state: EngineConnectionState) => void)[] =
|
callbacksEngineStateConnection: ((state: EngineConnectionState) => void)[] =
|
||||||
[]
|
[]
|
||||||
|
|
||||||
constructor() {
|
constructor(pool?: string) {
|
||||||
this.engineConnection = undefined
|
this.engineConnection = undefined
|
||||||
|
this.pool = pool
|
||||||
}
|
}
|
||||||
|
|
||||||
private _camControlsCameraChange = () => {}
|
private _camControlsCameraChange = () => {}
|
||||||
@ -972,7 +974,8 @@ export class EngineCommandManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const additionalSettings = settings.enableSSAO ? '&post_effect=ssao' : ''
|
const additionalSettings = settings.enableSSAO ? '&post_effect=ssao' : ''
|
||||||
const url = `${VITE_KC_API_WS_MODELING_URL}?video_res_width=${width}&video_res_height=${height}${additionalSettings}`
|
const pool = this.pool == undefined ? '' : `&pool=${this.pool}`
|
||||||
|
const url = `${VITE_KC_API_WS_MODELING_URL}?video_res_width=${width}&video_res_height=${height}${additionalSettings}${pool}`
|
||||||
this.engineConnection = new EngineConnection({
|
this.engineConnection = new EngineConnection({
|
||||||
engineCommandManager: this,
|
engineCommandManager: this,
|
||||||
url,
|
url,
|
||||||
|
@ -14,6 +14,7 @@ import init, {
|
|||||||
parse_app_settings,
|
parse_app_settings,
|
||||||
parse_project_settings,
|
parse_project_settings,
|
||||||
default_project_settings,
|
default_project_settings,
|
||||||
|
parse_project_route,
|
||||||
} from '../wasm-lib/pkg/wasm_lib'
|
} from '../wasm-lib/pkg/wasm_lib'
|
||||||
import { KCLError } from './errors'
|
import { KCLError } from './errors'
|
||||||
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError'
|
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError'
|
||||||
@ -31,6 +32,7 @@ import { DefaultPlanes } from 'wasm-lib/kcl/bindings/DefaultPlanes'
|
|||||||
import { TEST } from 'env'
|
import { TEST } from 'env'
|
||||||
import { Configuration } from 'wasm-lib/kcl/bindings/Configuration'
|
import { Configuration } from 'wasm-lib/kcl/bindings/Configuration'
|
||||||
import { ProjectConfiguration } from 'wasm-lib/kcl/bindings/ProjectConfiguration'
|
import { ProjectConfiguration } from 'wasm-lib/kcl/bindings/ProjectConfiguration'
|
||||||
|
import { ProjectRoute } from 'wasm-lib/kcl/bindings/ProjectRoute'
|
||||||
|
|
||||||
export type { Program } from '../wasm-lib/kcl/bindings/Program'
|
export type { Program } from '../wasm-lib/kcl/bindings/Program'
|
||||||
export type { Value } from '../wasm-lib/kcl/bindings/Value'
|
export type { Value } from '../wasm-lib/kcl/bindings/Value'
|
||||||
@ -389,3 +391,18 @@ export function parseProjectSettings(toml: string): ProjectConfiguration {
|
|||||||
throw new Error(`Error parsing project settings: ${e}`)
|
throw new Error(`Error parsing project settings: ${e}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function parseProjectRoute(
|
||||||
|
configuration: Configuration,
|
||||||
|
route_str: string
|
||||||
|
): ProjectRoute {
|
||||||
|
try {
|
||||||
|
const route: ProjectRoute = parse_project_route(
|
||||||
|
JSON.stringify(configuration),
|
||||||
|
route_str
|
||||||
|
)
|
||||||
|
return route
|
||||||
|
} catch (e: any) {
|
||||||
|
throw new Error(`Error parsing project route: ${e}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -49,6 +49,11 @@ export class CoreDumpManager {
|
|||||||
return APP_VERSION
|
return APP_VERSION
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the backend pool we've requested.
|
||||||
|
pool(): string {
|
||||||
|
return this.engineCommandManager.pool || ''
|
||||||
|
}
|
||||||
|
|
||||||
// Get the os information.
|
// Get the os information.
|
||||||
getOsInfo(): Promise<string> {
|
getOsInfo(): Promise<string> {
|
||||||
if (this.isTauri()) {
|
if (this.isTauri()) {
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
import { sep } from '@tauri-apps/api/path'
|
|
||||||
import { onboardingPaths } from 'routes/Onboarding/paths'
|
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||||
import { BROWSER_FILE_NAME, BROWSER_PROJECT_NAME, FILE_EXT } from './constants'
|
import { BROWSER_FILE_NAME, BROWSER_PROJECT_NAME, FILE_EXT } from './constants'
|
||||||
import { isTauri } from './isTauri'
|
import { isTauri } from './isTauri'
|
||||||
|
import { Configuration } from 'wasm-lib/kcl/bindings/Configuration'
|
||||||
|
import { ProjectRoute } from 'wasm-lib/kcl/bindings/ProjectRoute'
|
||||||
|
import { parseProjectRoute, readAppSettingsFile } from './tauri'
|
||||||
|
import { parseProjectRoute as parseProjectRouteWasm } from 'lang/wasm'
|
||||||
|
import { readLocalStorageAppSettingsFile } from './settings/settingsUtils'
|
||||||
|
|
||||||
const prependRoutes =
|
const prependRoutes =
|
||||||
(routesObject: Record<string, string>) => (prepend: string) => {
|
(routesObject: Record<string, string>) => (prepend: string) => {
|
||||||
@ -25,28 +29,23 @@ export const paths = {
|
|||||||
} as const
|
} as const
|
||||||
export const BROWSER_PATH = `%2F${BROWSER_PROJECT_NAME}%2F${BROWSER_FILE_NAME}${FILE_EXT}`
|
export const BROWSER_PATH = `%2F${BROWSER_PROJECT_NAME}%2F${BROWSER_FILE_NAME}${FILE_EXT}`
|
||||||
|
|
||||||
export function getProjectMetaByRouteId(id?: string, defaultDir = '') {
|
export async function getProjectMetaByRouteId(
|
||||||
|
id?: string,
|
||||||
|
configuration?: Configuration
|
||||||
|
): Promise<ProjectRoute | undefined> {
|
||||||
if (!id) return undefined
|
if (!id) return undefined
|
||||||
const s = isTauri() ? sep() : '/'
|
|
||||||
|
|
||||||
const decodedId = decodeURIComponent(id).replace(/\/$/, '') // remove trailing slash
|
const inTauri = isTauri()
|
||||||
const projectAndFile =
|
|
||||||
defaultDir === '/'
|
|
||||||
? decodedId.replace(defaultDir, '')
|
|
||||||
: decodedId.replace(defaultDir + s, '')
|
|
||||||
const filePathParts = projectAndFile.split(s)
|
|
||||||
const projectName = filePathParts[0]
|
|
||||||
const projectPath =
|
|
||||||
(defaultDir === '/' ? defaultDir : defaultDir + s) + projectName
|
|
||||||
const lastPathPart = filePathParts[filePathParts.length - 1]
|
|
||||||
const currentFileName =
|
|
||||||
lastPathPart === projectName ? undefined : lastPathPart
|
|
||||||
const currentFilePath = lastPathPart === projectName ? undefined : decodedId
|
|
||||||
|
|
||||||
return {
|
if (!configuration) {
|
||||||
projectName,
|
configuration = inTauri
|
||||||
projectPath,
|
? await readAppSettingsFile()
|
||||||
currentFileName,
|
: readLocalStorageAppSettingsFile()
|
||||||
currentFilePath,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const route = inTauri
|
||||||
|
? await parseProjectRoute(configuration, id)
|
||||||
|
: parseProjectRouteWasm(configuration, id)
|
||||||
|
|
||||||
|
return route
|
||||||
}
|
}
|
||||||
|
@ -25,17 +25,21 @@ import { createSettings } from './settings/initialSettings'
|
|||||||
// occurred during the settings load
|
// occurred during the settings load
|
||||||
export const settingsLoader: LoaderFunction = async ({
|
export const settingsLoader: LoaderFunction = async ({
|
||||||
params,
|
params,
|
||||||
}): Promise<ReturnType<typeof createSettings>> => {
|
}): Promise<
|
||||||
let { settings } = await loadAndValidateSettings()
|
ReturnType<typeof createSettings> | ReturnType<typeof redirect>
|
||||||
|
> => {
|
||||||
|
let { settings, configuration } = await loadAndValidateSettings()
|
||||||
|
|
||||||
// I don't love that we have to read the settings again here,
|
// I don't love that we have to read the settings again here,
|
||||||
// but we need to get the project path to load the project settings
|
// but we need to get the project path to load the project settings
|
||||||
if (params.id) {
|
if (params.id) {
|
||||||
const defaultDir = settings.app.projectDirectory.current || ''
|
const projectPathData = await getProjectMetaByRouteId(
|
||||||
const projectPathData = getProjectMetaByRouteId(params.id, defaultDir)
|
params.id,
|
||||||
|
configuration
|
||||||
|
)
|
||||||
if (projectPathData) {
|
if (projectPathData) {
|
||||||
const { projectName } = projectPathData
|
const { project_name } = projectPathData
|
||||||
const { settings: s } = await loadAndValidateSettings(projectName)
|
const { settings: s } = await loadAndValidateSettings(project_name)
|
||||||
settings = s
|
settings = s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -69,17 +73,19 @@ export const onboardingRedirectLoader: ActionFunction = async (args) => {
|
|||||||
export const fileLoader: LoaderFunction = async ({
|
export const fileLoader: LoaderFunction = async ({
|
||||||
params,
|
params,
|
||||||
}): Promise<FileLoaderData | Response> => {
|
}): Promise<FileLoaderData | Response> => {
|
||||||
let { settings } = await loadAndValidateSettings()
|
let { configuration } = await loadAndValidateSettings()
|
||||||
|
|
||||||
const defaultDir = settings.app.projectDirectory.current || '/'
|
const projectPathData = await getProjectMetaByRouteId(
|
||||||
const projectPathData = getProjectMetaByRouteId(params.id, defaultDir)
|
params.id,
|
||||||
|
configuration
|
||||||
|
)
|
||||||
const isBrowserProject = params.id === decodeURIComponent(BROWSER_PATH)
|
const isBrowserProject = params.id === decodeURIComponent(BROWSER_PATH)
|
||||||
|
|
||||||
if (!isBrowserProject && projectPathData) {
|
if (!isBrowserProject && projectPathData) {
|
||||||
const { projectName, projectPath, currentFileName, currentFilePath } =
|
const { project_name, project_path, current_file_name, current_file_path } =
|
||||||
projectPathData
|
projectPathData
|
||||||
|
|
||||||
if (!currentFileName || !currentFilePath) {
|
if (!current_file_name || !current_file_path || !project_name) {
|
||||||
return redirect(
|
return redirect(
|
||||||
`${paths.FILE}/${encodeURIComponent(
|
`${paths.FILE}/${encodeURIComponent(
|
||||||
`${params.id}${isTauri() ? sep() : '/'}${PROJECT_ENTRYPOINT}`
|
`${params.id}${isTauri() ? sep() : '/'}${PROJECT_ENTRYPOINT}`
|
||||||
@ -89,33 +95,33 @@ export const fileLoader: LoaderFunction = async ({
|
|||||||
|
|
||||||
// TODO: PROJECT_ENTRYPOINT is hardcoded
|
// TODO: PROJECT_ENTRYPOINT is hardcoded
|
||||||
// until we support setting a project's entrypoint file
|
// until we support setting a project's entrypoint file
|
||||||
const code = await readTextFile(currentFilePath)
|
const code = await readTextFile(current_file_path)
|
||||||
|
|
||||||
// Update both the state and the editor's code.
|
// Update both the state and the editor's code.
|
||||||
// We explicitly do not write to the file here since we are loading from
|
// We explicitly do not write to the file here since we are loading from
|
||||||
// the file system and not the editor.
|
// the file system and not the editor.
|
||||||
codeManager.updateCurrentFilePath(currentFilePath)
|
codeManager.updateCurrentFilePath(current_file_path)
|
||||||
codeManager.updateCodeStateEditor(code)
|
codeManager.updateCodeStateEditor(code)
|
||||||
kclManager.executeCode(true)
|
kclManager.executeCode(true)
|
||||||
|
|
||||||
// Set the file system manager to the project path
|
// Set the file system manager to the project path
|
||||||
// So that WASM gets an updated path for operations
|
// So that WASM gets an updated path for operations
|
||||||
fileSystemManager.dir = projectPath
|
fileSystemManager.dir = project_path
|
||||||
|
|
||||||
const projectData: IndexLoaderData = {
|
const projectData: IndexLoaderData = {
|
||||||
code,
|
code,
|
||||||
project: isTauri()
|
project: isTauri()
|
||||||
? await getProjectInfo(projectName)
|
? await getProjectInfo(project_path, configuration)
|
||||||
: {
|
: {
|
||||||
name: projectName,
|
name: project_name,
|
||||||
path: projectPath,
|
path: project_path,
|
||||||
children: [],
|
children: [],
|
||||||
kcl_file_count: 0,
|
kcl_file_count: 0,
|
||||||
directory_count: 0,
|
directory_count: 0,
|
||||||
},
|
},
|
||||||
file: {
|
file: {
|
||||||
name: currentFileName,
|
name: current_file_name,
|
||||||
path: currentFilePath,
|
path: current_file_path,
|
||||||
children: [],
|
children: [],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -101,7 +101,7 @@ function localStorageProjectSettingsPath() {
|
|||||||
return '/' + BROWSER_PROJECT_NAME + '/project.toml'
|
return '/' + BROWSER_PROJECT_NAME + '/project.toml'
|
||||||
}
|
}
|
||||||
|
|
||||||
function readLocalStorageAppSettingsFile(): Configuration {
|
export function readLocalStorageAppSettingsFile(): Configuration {
|
||||||
// TODO: Remove backwards compatibility after a few releases.
|
// TODO: Remove backwards compatibility after a few releases.
|
||||||
let stored =
|
let stored =
|
||||||
localStorage.getItem(localStorageAppSettingsPath()) ??
|
localStorage.getItem(localStorageAppSettingsPath()) ??
|
||||||
|
@ -6,6 +6,18 @@ import { Configuration } from 'wasm-lib/kcl/bindings/Configuration'
|
|||||||
import { ProjectConfiguration } from 'wasm-lib/kcl/bindings/ProjectConfiguration'
|
import { ProjectConfiguration } from 'wasm-lib/kcl/bindings/ProjectConfiguration'
|
||||||
import { Project } from 'wasm-lib/kcl/bindings/Project'
|
import { Project } from 'wasm-lib/kcl/bindings/Project'
|
||||||
import { FileEntry } from 'wasm-lib/kcl/bindings/FileEntry'
|
import { FileEntry } from 'wasm-lib/kcl/bindings/FileEntry'
|
||||||
|
import { ProjectState } from 'wasm-lib/kcl/bindings/ProjectState'
|
||||||
|
import { ProjectRoute } from 'wasm-lib/kcl/bindings/ProjectRoute'
|
||||||
|
|
||||||
|
// Get the app state from tauri.
|
||||||
|
export async function getState(): Promise<ProjectState | undefined> {
|
||||||
|
return await invoke<ProjectState | undefined>('get_state')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the app state in tauri.
|
||||||
|
export async function setState(state: ProjectState | undefined): Promise<void> {
|
||||||
|
return await invoke('set_state', { state })
|
||||||
|
}
|
||||||
|
|
||||||
// Get the initial default dir for holding all projects.
|
// Get the initial default dir for holding all projects.
|
||||||
export async function getInitialDefaultDir(): Promise<string> {
|
export async function getInitialDefaultDir(): Promise<string> {
|
||||||
@ -53,7 +65,7 @@ export async function listProjects(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getProjectInfo(
|
export async function getProjectInfo(
|
||||||
projectName: string,
|
projectPath: string,
|
||||||
configuration?: Configuration
|
configuration?: Configuration
|
||||||
): Promise<Project> {
|
): Promise<Project> {
|
||||||
if (!configuration) {
|
if (!configuration) {
|
||||||
@ -61,7 +73,7 @@ export async function getProjectInfo(
|
|||||||
}
|
}
|
||||||
return await invoke<Project>('get_project_info', {
|
return await invoke<Project>('get_project_info', {
|
||||||
configuration,
|
configuration,
|
||||||
projectName,
|
projectPath,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,6 +81,16 @@ export async function login(host: string): Promise<string> {
|
|||||||
return await invoke('login', { host })
|
return await invoke('login', { host })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function parseProjectRoute(
|
||||||
|
configuration: Configuration,
|
||||||
|
route: string
|
||||||
|
): Promise<ProjectRoute> {
|
||||||
|
return await invoke<ProjectRoute>('parse_project_route', {
|
||||||
|
configuration,
|
||||||
|
route,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export async function getUser(
|
export async function getUser(
|
||||||
token: string | undefined,
|
token: string | undefined,
|
||||||
host: string
|
host: string
|
||||||
|
@ -9,6 +9,7 @@ import {
|
|||||||
import { enginelessExecutor } from './lib/testHelpers'
|
import { enginelessExecutor } from './lib/testHelpers'
|
||||||
import { EngineCommandManager } from './lang/std/engineConnection'
|
import { EngineCommandManager } from './lang/std/engineConnection'
|
||||||
import { KCLError } from './lang/errors'
|
import { KCLError } from './lang/errors'
|
||||||
|
import { SidebarType } from 'components/ModelingSidebar/ModelingPanes'
|
||||||
|
|
||||||
export type ToolTip =
|
export type ToolTip =
|
||||||
| 'lineTo'
|
| 'lineTo'
|
||||||
@ -44,14 +45,6 @@ export const toolTips = [
|
|||||||
'tangentialArcTo',
|
'tangentialArcTo',
|
||||||
] as any as ToolTip[]
|
] as any as ToolTip[]
|
||||||
|
|
||||||
export type PaneType =
|
|
||||||
| 'code'
|
|
||||||
| 'variables'
|
|
||||||
| 'debug'
|
|
||||||
| 'kclErrors'
|
|
||||||
| 'logs'
|
|
||||||
| 'lspMessages'
|
|
||||||
|
|
||||||
export interface StoreState {
|
export interface StoreState {
|
||||||
mediaStream?: MediaStream
|
mediaStream?: MediaStream
|
||||||
setMediaStream: (mediaStream: MediaStream) => void
|
setMediaStream: (mediaStream: MediaStream) => void
|
||||||
@ -77,8 +70,8 @@ export interface StoreState {
|
|||||||
|
|
||||||
showHomeMenu: boolean
|
showHomeMenu: boolean
|
||||||
setHomeShowMenu: (showMenu: boolean) => void
|
setHomeShowMenu: (showMenu: boolean) => void
|
||||||
openPanes: PaneType[]
|
openPanes: SidebarType[]
|
||||||
setOpenPanes: (panes: PaneType[]) => void
|
setOpenPanes: (panes: SidebarType[]) => void
|
||||||
homeMenuItems: {
|
homeMenuItems: {
|
||||||
name: string
|
name: string
|
||||||
path: string
|
path: string
|
||||||
|
4
src/wasm-lib/Cargo.lock
generated
4
src/wasm-lib/Cargo.lock
generated
@ -1895,7 +1895,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kcl-lib"
|
name = "kcl-lib"
|
||||||
version = "0.1.51"
|
version = "0.1.53"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"approx 0.5.1",
|
"approx 0.5.1",
|
||||||
@ -1974,6 +1974,7 @@ dependencies = [
|
|||||||
"bigdecimal",
|
"bigdecimal",
|
||||||
"bytes",
|
"bytes",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"clap",
|
||||||
"data-encoding",
|
"data-encoding",
|
||||||
"format_serde_error",
|
"format_serde_error",
|
||||||
"futures",
|
"futures",
|
||||||
@ -4726,6 +4727,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bson",
|
"bson",
|
||||||
|
"clap",
|
||||||
"console_error_panic_hook",
|
"console_error_panic_hook",
|
||||||
"futures",
|
"futures",
|
||||||
"gloo-utils",
|
"gloo-utils",
|
||||||
|
@ -11,6 +11,7 @@ crate-type = ["cdylib"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bson = { version = "2.10.0", features = ["uuid-1", "chrono"] }
|
bson = { version = "2.10.0", features = ["uuid-1", "chrono"] }
|
||||||
|
clap = "4.5.4"
|
||||||
gloo-utils = "0.2.0"
|
gloo-utils = "0.2.0"
|
||||||
kcl-lib = { path = "kcl" }
|
kcl-lib = { path = "kcl" }
|
||||||
kittycad = { workspace = true }
|
kittycad = { workspace = true }
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "kcl-lib"
|
name = "kcl-lib"
|
||||||
description = "KittyCAD Language implementation and tools"
|
description = "KittyCAD Language implementation and tools"
|
||||||
version = "0.1.51"
|
version = "0.1.53"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://github.com/KittyCAD/modeling-app"
|
repository = "https://github.com/KittyCAD/modeling-app"
|
||||||
@ -16,7 +16,7 @@ async-recursion = "1.1.0"
|
|||||||
async-trait = "0.1.80"
|
async-trait = "0.1.80"
|
||||||
base64 = "0.22.0"
|
base64 = "0.22.0"
|
||||||
chrono = "0.4.38"
|
chrono = "0.4.38"
|
||||||
clap = { version = "4.5.4", features = ["cargo", "derive", "env", "unicode"], optional = true }
|
clap = { version = "4.5.4", default-features = false, optional = true }
|
||||||
dashmap = "5.5.3"
|
dashmap = "5.5.3"
|
||||||
databake = { version = "0.1.7", features = ["derive"] }
|
databake = { version = "0.1.7", features = ["derive"] }
|
||||||
derive-docs = { version = "0.1.17", path = "../derive-docs" }
|
derive-docs = { version = "0.1.17", path = "../derive-docs" }
|
||||||
@ -24,7 +24,7 @@ form_urlencoded = "1.2.1"
|
|||||||
futures = { version = "0.3.30" }
|
futures = { version = "0.3.30" }
|
||||||
git_rev = "0.1.0"
|
git_rev = "0.1.0"
|
||||||
gltf-json = "1.4.0"
|
gltf-json = "1.4.0"
|
||||||
kittycad = { workspace = true }
|
kittycad = { workspace = true, features = ["clap"] }
|
||||||
kittycad-execution-plan-macros = { workspace = true }
|
kittycad-execution-plan-macros = { workspace = true }
|
||||||
kittycad-execution-plan-traits = { workspace = true }
|
kittycad-execution-plan-traits = { workspace = true }
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
@ -61,7 +61,7 @@ tokio-tungstenite = { version = "0.21.0", features = ["rustls-tls-native-roots"]
|
|||||||
tower-lsp = { version = "0.20.0", features = ["proposed"] }
|
tower-lsp = { version = "0.20.0", features = ["proposed"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["engine"]
|
default = ["cli", "engine"]
|
||||||
cli = ["dep:clap"]
|
cli = ["dep:clap"]
|
||||||
engine = []
|
engine = []
|
||||||
|
|
||||||
|
@ -33,6 +33,10 @@ impl CoreDump for CoreDumper {
|
|||||||
Ok(env!("CARGO_PKG_VERSION").to_string())
|
Ok(env!("CARGO_PKG_VERSION").to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn pool(&self) -> Result<String> {
|
||||||
|
Ok("".to_owned())
|
||||||
|
}
|
||||||
|
|
||||||
async fn os(&self) -> Result<crate::coredump::OsInfo> {
|
async fn os(&self) -> Result<crate::coredump::OsInfo> {
|
||||||
Ok(crate::coredump::OsInfo {
|
Ok(crate::coredump::OsInfo {
|
||||||
platform: Some(std::env::consts::OS.to_string()),
|
platform: Some(std::env::consts::OS.to_string()),
|
||||||
|
@ -19,6 +19,8 @@ pub trait CoreDump: Clone {
|
|||||||
|
|
||||||
fn version(&self) -> Result<String>;
|
fn version(&self) -> Result<String>;
|
||||||
|
|
||||||
|
fn pool(&self) -> Result<String>;
|
||||||
|
|
||||||
async fn os(&self) -> Result<OsInfo>;
|
async fn os(&self) -> Result<OsInfo>;
|
||||||
|
|
||||||
fn is_tauri(&self) -> Result<bool>;
|
fn is_tauri(&self) -> Result<bool>;
|
||||||
@ -71,6 +73,7 @@ pub trait CoreDump: Clone {
|
|||||||
os,
|
os,
|
||||||
webrtc_stats,
|
webrtc_stats,
|
||||||
github_issue_url: None,
|
github_issue_url: None,
|
||||||
|
pool: self.pool()?,
|
||||||
};
|
};
|
||||||
app_info.set_github_issue_url(&screenshot_url)?;
|
app_info.set_github_issue_url(&screenshot_url)?;
|
||||||
|
|
||||||
@ -103,6 +106,9 @@ pub struct AppInfo {
|
|||||||
/// This gets prepoulated with all the core dump info.
|
/// This gets prepoulated with all the core dump info.
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub github_issue_url: Option<String>,
|
pub github_issue_url: Option<String>,
|
||||||
|
|
||||||
|
/// Engine pool the client is connected to.
|
||||||
|
pub pool: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppInfo {
|
impl AppInfo {
|
||||||
|
@ -16,6 +16,9 @@ extern "C" {
|
|||||||
#[wasm_bindgen(method, js_name = baseApiUrl, catch)]
|
#[wasm_bindgen(method, js_name = baseApiUrl, catch)]
|
||||||
fn baseApiUrl(this: &CoreDumpManager) -> Result<String, js_sys::Error>;
|
fn baseApiUrl(this: &CoreDumpManager) -> Result<String, js_sys::Error>;
|
||||||
|
|
||||||
|
#[wasm_bindgen(method, js_name = pool, catch)]
|
||||||
|
fn pool(this: &CoreDumpManager) -> Result<String, js_sys::Error>;
|
||||||
|
|
||||||
#[wasm_bindgen(method, js_name = version, catch)]
|
#[wasm_bindgen(method, js_name = version, catch)]
|
||||||
fn version(this: &CoreDumpManager) -> Result<String, js_sys::Error>;
|
fn version(this: &CoreDumpManager) -> Result<String, js_sys::Error>;
|
||||||
|
|
||||||
@ -66,6 +69,12 @@ impl CoreDump for CoreDumper {
|
|||||||
.map_err(|e| anyhow::anyhow!("Failed to get response from version: {:?}", e))
|
.map_err(|e| anyhow::anyhow!("Failed to get response from version: {:?}", e))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn pool(&self) -> Result<String> {
|
||||||
|
self.manager
|
||||||
|
.pool()
|
||||||
|
.map_err(|e| anyhow::anyhow!("Failed to get response from pool: {:?}", e))
|
||||||
|
}
|
||||||
|
|
||||||
async fn os(&self) -> Result<crate::coredump::OsInfo> {
|
async fn os(&self) -> Result<crate::coredump::OsInfo> {
|
||||||
let promise = self
|
let promise = self
|
||||||
.manager
|
.manager
|
||||||
|
@ -5,6 +5,111 @@ use parse_display::{Display, FromStr};
|
|||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use super::Configuration;
|
||||||
|
|
||||||
|
/// State management for the application.
|
||||||
|
#[derive(Debug, Default, Clone, Deserialize, Serialize, JsonSchema, ts_rs::TS, PartialEq)]
|
||||||
|
#[ts(export)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub struct ProjectState {
|
||||||
|
pub project: Project,
|
||||||
|
pub current_file: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Project route information.
|
||||||
|
#[derive(Debug, Default, Clone, Deserialize, Serialize, JsonSchema, ts_rs::TS, PartialEq)]
|
||||||
|
#[ts(export)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub struct ProjectRoute {
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub project_name: Option<String>,
|
||||||
|
pub project_path: String,
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub current_file_name: Option<String>,
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub current_file_path: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProjectRoute {
|
||||||
|
/// Get the project state from the url in the route.
|
||||||
|
pub fn from_route(configuration: &Configuration, route: &str) -> Result<Self> {
|
||||||
|
let path = std::path::Path::new(route);
|
||||||
|
// Check if the default project path is in the route.
|
||||||
|
let (project_path, project_name) = if path.starts_with(&configuration.settings.project.directory)
|
||||||
|
&& configuration.settings.project.directory != std::path::PathBuf::default()
|
||||||
|
{
|
||||||
|
// Get the project name.
|
||||||
|
if let Some(project_name) = path
|
||||||
|
.strip_prefix(&configuration.settings.project.directory)
|
||||||
|
.unwrap()
|
||||||
|
.iter()
|
||||||
|
.next()
|
||||||
|
{
|
||||||
|
(
|
||||||
|
configuration
|
||||||
|
.settings
|
||||||
|
.project
|
||||||
|
.directory
|
||||||
|
.join(project_name)
|
||||||
|
.display()
|
||||||
|
.to_string(),
|
||||||
|
Some(project_name.to_string_lossy().to_string()),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(configuration.settings.project.directory.display().to_string(), None)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Assume the project path is the parent directory of the file.
|
||||||
|
let project_dir = if path.display().to_string().ends_with(".kcl") {
|
||||||
|
path.parent()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Parent directory not found: {}", path.display()))?
|
||||||
|
} else {
|
||||||
|
path
|
||||||
|
};
|
||||||
|
|
||||||
|
if project_dir == std::path::Path::new("/") {
|
||||||
|
(
|
||||||
|
path.display().to_string(),
|
||||||
|
Some(
|
||||||
|
path.file_name()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("File name not found: {}", path.display()))?
|
||||||
|
.to_string_lossy()
|
||||||
|
.to_string(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
} else if let Some(project_name) = project_dir.file_name() {
|
||||||
|
(
|
||||||
|
project_dir.display().to_string(),
|
||||||
|
Some(project_name.to_string_lossy().to_string()),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(project_dir.display().to_string(), None)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let (current_file_name, current_file_path) = if path.display().to_string() == project_path {
|
||||||
|
(None, None)
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
Some(
|
||||||
|
path.file_name()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("File name not found: {}", path.display()))?
|
||||||
|
.to_string_lossy()
|
||||||
|
.to_string(),
|
||||||
|
),
|
||||||
|
Some(path.display().to_string()),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
project_name,
|
||||||
|
project_path,
|
||||||
|
current_file_name,
|
||||||
|
current_file_path,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Information about project.
|
/// Information about project.
|
||||||
#[derive(Debug, Default, Clone, Deserialize, Serialize, JsonSchema, ts_rs::TS, PartialEq)]
|
#[derive(Debug, Default, Clone, Deserialize, Serialize, JsonSchema, ts_rs::TS, PartialEq)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
@ -23,6 +128,34 @@ pub struct Project {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Project {
|
impl Project {
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
/// Populate a project from a path.
|
||||||
|
pub async fn from_path<P: AsRef<std::path::Path>>(path: P) -> Result<Self> {
|
||||||
|
// Check if they are using '.' as the path.
|
||||||
|
let path = if path.as_ref() == std::path::Path::new(".") {
|
||||||
|
std::env::current_dir()?
|
||||||
|
} else {
|
||||||
|
path.as_ref().to_path_buf()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Make sure the path exists.
|
||||||
|
if !path.exists() {
|
||||||
|
return Err(anyhow::anyhow!("Path does not exist"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let file = crate::settings::utils::walk_dir(&path).await?;
|
||||||
|
let metadata = std::fs::metadata(path).ok().map(|m| m.into());
|
||||||
|
let mut project = Self {
|
||||||
|
file,
|
||||||
|
metadata,
|
||||||
|
kcl_file_count: 0,
|
||||||
|
directory_count: 0,
|
||||||
|
};
|
||||||
|
project.populate_kcl_file_count()?;
|
||||||
|
project.populate_directory_count()?;
|
||||||
|
Ok(project)
|
||||||
|
}
|
||||||
|
|
||||||
/// Populate the number of KCL files in the project.
|
/// Populate the number of KCL files in the project.
|
||||||
pub fn populate_kcl_file_count(&mut self) -> Result<()> {
|
pub fn populate_kcl_file_count(&mut self) -> Result<()> {
|
||||||
let mut count = 0;
|
let mut count = 0;
|
||||||
@ -196,3 +329,141 @@ impl From<std::fs::Metadata> for FileMetadata {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_project_route_from_route_std_path() {
|
||||||
|
let mut configuration = crate::settings::types::Configuration::default();
|
||||||
|
configuration.settings.project.directory =
|
||||||
|
std::path::PathBuf::from("/Users/macinatormax/Documents/kittycad-modeling-projects");
|
||||||
|
|
||||||
|
let route = "/Users/macinatormax/Documents/kittycad-modeling-projects/assembly/main.kcl";
|
||||||
|
let state = super::ProjectRoute::from_route(&configuration, route).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
state,
|
||||||
|
super::ProjectRoute {
|
||||||
|
project_name: Some("assembly".to_string()),
|
||||||
|
project_path: "/Users/macinatormax/Documents/kittycad-modeling-projects/assembly".to_string(),
|
||||||
|
current_file_name: Some("main.kcl".to_string()),
|
||||||
|
current_file_path: Some(
|
||||||
|
"/Users/macinatormax/Documents/kittycad-modeling-projects/assembly/main.kcl".to_string()
|
||||||
|
),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_project_route_from_route_std_path_dir() {
|
||||||
|
let mut configuration = crate::settings::types::Configuration::default();
|
||||||
|
configuration.settings.project.directory =
|
||||||
|
std::path::PathBuf::from("/Users/macinatormax/Documents/kittycad-modeling-projects");
|
||||||
|
|
||||||
|
let route = "/Users/macinatormax/Documents/kittycad-modeling-projects/assembly";
|
||||||
|
let state = super::ProjectRoute::from_route(&configuration, route).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
state,
|
||||||
|
super::ProjectRoute {
|
||||||
|
project_name: Some("assembly".to_string()),
|
||||||
|
project_path: "/Users/macinatormax/Documents/kittycad-modeling-projects/assembly".to_string(),
|
||||||
|
current_file_name: None,
|
||||||
|
current_file_path: None,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_project_route_from_route_std_path_dir_empty() {
|
||||||
|
let mut configuration = crate::settings::types::Configuration::default();
|
||||||
|
configuration.settings.project.directory =
|
||||||
|
std::path::PathBuf::from("/Users/macinatormax/Documents/kittycad-modeling-projects");
|
||||||
|
|
||||||
|
let route = "/Users/macinatormax/Documents/kittycad-modeling-projects";
|
||||||
|
let state = super::ProjectRoute::from_route(&configuration, route).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
state,
|
||||||
|
super::ProjectRoute {
|
||||||
|
project_name: None,
|
||||||
|
project_path: "/Users/macinatormax/Documents/kittycad-modeling-projects".to_string(),
|
||||||
|
current_file_name: None,
|
||||||
|
current_file_path: None,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_project_route_from_route_outside_std_path() {
|
||||||
|
let mut configuration = crate::settings::types::Configuration::default();
|
||||||
|
configuration.settings.project.directory =
|
||||||
|
std::path::PathBuf::from("/Users/macinatormax/Documents/kittycad-modeling-projects");
|
||||||
|
|
||||||
|
let route = "/Users/macinatormax/kittycad/modeling-app/main.kcl";
|
||||||
|
let state = super::ProjectRoute::from_route(&configuration, route).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
state,
|
||||||
|
super::ProjectRoute {
|
||||||
|
project_name: Some("modeling-app".to_string()),
|
||||||
|
project_path: "/Users/macinatormax/kittycad/modeling-app".to_string(),
|
||||||
|
current_file_name: Some("main.kcl".to_string()),
|
||||||
|
current_file_path: Some("/Users/macinatormax/kittycad/modeling-app/main.kcl".to_string()),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_project_route_from_route_outside_std_path_dir() {
|
||||||
|
let mut configuration = crate::settings::types::Configuration::default();
|
||||||
|
configuration.settings.project.directory =
|
||||||
|
std::path::PathBuf::from("/Users/macinatormax/Documents/kittycad-modeling-projects");
|
||||||
|
|
||||||
|
let route = "/Users/macinatormax/kittycad/modeling-app";
|
||||||
|
let state = super::ProjectRoute::from_route(&configuration, route).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
state,
|
||||||
|
super::ProjectRoute {
|
||||||
|
project_name: Some("modeling-app".to_string()),
|
||||||
|
project_path: "/Users/macinatormax/kittycad/modeling-app".to_string(),
|
||||||
|
current_file_name: None,
|
||||||
|
current_file_path: None,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_project_route_from_route_browser() {
|
||||||
|
let mut configuration = crate::settings::types::Configuration::default();
|
||||||
|
configuration.settings.project.directory = std::path::PathBuf::default();
|
||||||
|
|
||||||
|
let route = "/browser/main.kcl";
|
||||||
|
let state = super::ProjectRoute::from_route(&configuration, route).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
state,
|
||||||
|
super::ProjectRoute {
|
||||||
|
project_name: Some("browser".to_string()),
|
||||||
|
project_path: "/browser".to_string(),
|
||||||
|
current_file_name: Some("main.kcl".to_string()),
|
||||||
|
current_file_path: Some("/browser/main.kcl".to_string()),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_project_route_from_route_browser_no_path() {
|
||||||
|
let mut configuration = crate::settings::types::Configuration::default();
|
||||||
|
configuration.settings.project.directory = std::path::PathBuf::default();
|
||||||
|
|
||||||
|
let route = "/browser";
|
||||||
|
let state = super::ProjectRoute::from_route(&configuration, route).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
state,
|
||||||
|
super::ProjectRoute {
|
||||||
|
project_name: Some("browser".to_string()),
|
||||||
|
project_path: "/browser".to_string(),
|
||||||
|
current_file_name: None,
|
||||||
|
current_file_path: None,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -10,7 +10,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
use validator::{Validate, ValidateRange};
|
use validator::{Validate, ValidateRange};
|
||||||
|
|
||||||
const DEFAULT_THEME_COLOR: f64 = 264.5;
|
const DEFAULT_THEME_COLOR: f64 = 264.5;
|
||||||
const DEFAULT_PROJECT_KCL_FILE: &str = "main.kcl";
|
pub const DEFAULT_PROJECT_KCL_FILE: &str = "main.kcl";
|
||||||
const DEFAULT_PROJECT_NAME_TEMPLATE: &str = "project-$nnn";
|
const DEFAULT_PROJECT_NAME_TEMPLATE: &str = "project-$nnn";
|
||||||
|
|
||||||
/// High level configuration.
|
/// High level configuration.
|
||||||
@ -129,7 +129,7 @@ impl Configuration {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
projects.push(self.get_project_info(&e.file_name().to_string_lossy()).await?);
|
projects.push(self.get_project_info(&e.path().display().to_string()).await?);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(projects)
|
Ok(projects)
|
||||||
@ -137,21 +137,20 @@ impl Configuration {
|
|||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
/// Get information about a project.
|
/// Get information about a project.
|
||||||
pub async fn get_project_info(&self, project_name: &str) -> Result<crate::settings::types::file::Project> {
|
pub async fn get_project_info(&self, project_path: &str) -> Result<crate::settings::types::file::Project> {
|
||||||
let main_dir = &self.ensure_project_directory_exists().await?;
|
// Check the directory.
|
||||||
|
let project_dir = std::path::Path::new(project_path);
|
||||||
if project_name.is_empty() {
|
if !project_dir.exists() {
|
||||||
return Err(anyhow::anyhow!("Project name cannot be empty."));
|
return Err(anyhow::anyhow!("Project directory does not exist: {}", project_path));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check the directory.
|
// Make sure it is a directory.
|
||||||
let project_dir = main_dir.join(project_name);
|
if !project_dir.is_dir() {
|
||||||
if !project_dir.exists() {
|
return Err(anyhow::anyhow!("Project path is not a directory: {}", project_path));
|
||||||
return Err(anyhow::anyhow!("Project directory does not exist."));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut project = crate::settings::types::file::Project {
|
let mut project = crate::settings::types::file::Project {
|
||||||
file: crate::settings::utils::walk_dir(&project_dir).await?,
|
file: crate::settings::utils::walk_dir(project_dir).await?,
|
||||||
metadata: Some(tokio::fs::metadata(&project_dir).await?.into()),
|
metadata: Some(tokio::fs::metadata(&project_dir).await?.into()),
|
||||||
kcl_file_count: 0,
|
kcl_file_count: 0,
|
||||||
directory_count: 0,
|
directory_count: 0,
|
||||||
|
@ -1,31 +1,54 @@
|
|||||||
//! Utility functions for settings.
|
//! Utility functions for settings.
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::Path;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use clap::ValueEnum;
|
||||||
|
|
||||||
use crate::settings::types::file::FileEntry;
|
use crate::settings::types::file::FileEntry;
|
||||||
|
|
||||||
|
lazy_static::lazy_static! {
|
||||||
|
static ref RELEVANT_EXTENSIONS: Vec<String> = {
|
||||||
|
let mut relevant_extensions = vec!["kcl".to_string(), "stp".to_string(), "glb".to_string()];
|
||||||
|
let named_extensions = kittycad::types::FileImportFormat::value_variants()
|
||||||
|
.iter()
|
||||||
|
.map(|x| format!("{}", x))
|
||||||
|
.collect::<Vec<String>>();
|
||||||
|
// Add all the default import formats.
|
||||||
|
relevant_extensions.extend_from_slice(&named_extensions);
|
||||||
|
relevant_extensions
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// Walk a directory recursively and return a list of all files.
|
/// Walk a directory recursively and return a list of all files.
|
||||||
#[async_recursion::async_recursion]
|
#[async_recursion::async_recursion]
|
||||||
pub async fn walk_dir(dir: &PathBuf) -> Result<FileEntry> {
|
pub async fn walk_dir<P: AsRef<Path> + Send>(dir: P) -> Result<FileEntry> {
|
||||||
let mut entry = FileEntry {
|
let mut entry = FileEntry {
|
||||||
name: dir
|
name: dir
|
||||||
|
.as_ref()
|
||||||
.file_name()
|
.file_name()
|
||||||
.ok_or_else(|| anyhow::anyhow!("No file name"))?
|
.ok_or_else(|| anyhow::anyhow!("No file name"))?
|
||||||
.to_string_lossy()
|
.to_string_lossy()
|
||||||
.to_string(),
|
.to_string(),
|
||||||
path: dir.display().to_string(),
|
path: dir.as_ref().display().to_string(),
|
||||||
children: None,
|
children: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut children = vec![];
|
let mut children = vec![];
|
||||||
|
|
||||||
let mut entries = tokio::fs::read_dir(&dir).await?;
|
let mut entries = tokio::fs::read_dir(&dir.as_ref()).await?;
|
||||||
while let Some(e) = entries.next_entry().await? {
|
while let Some(e) = entries.next_entry().await? {
|
||||||
|
// ignore hidden files and directories (starting with a dot)
|
||||||
|
if e.file_name().to_string_lossy().starts_with('.') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if e.file_type().await?.is_dir() {
|
if e.file_type().await?.is_dir() {
|
||||||
children.push(walk_dir(&e.path()).await?);
|
children.push(walk_dir(&e.path()).await?);
|
||||||
} else {
|
} else {
|
||||||
|
if !is_relevant_file(&e.path())? {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
children.push(FileEntry {
|
children.push(FileEntry {
|
||||||
name: e.file_name().to_string_lossy().to_string(),
|
name: e.file_name().to_string_lossy().to_string(),
|
||||||
path: e.path().display().to_string(),
|
path: e.path().display().to_string(),
|
||||||
@ -34,9 +57,17 @@ pub async fn walk_dir(dir: &PathBuf) -> Result<FileEntry> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !children.is_empty() {
|
// We don't set this to none if there are no children, because it's a directory.
|
||||||
entry.children = Some(children);
|
entry.children = Some(children);
|
||||||
}
|
|
||||||
|
|
||||||
Ok(entry)
|
Ok(entry)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check if a file is relevant for the application.
|
||||||
|
fn is_relevant_file<P: AsRef<Path>>(path: P) -> Result<bool> {
|
||||||
|
if let Some(ext) = path.as_ref().extension() {
|
||||||
|
Ok(RELEVANT_EXTENSIONS.contains(&ext.to_string_lossy().to_string()))
|
||||||
|
} else {
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -198,7 +198,7 @@ pub async fn kcl_lsp_run(
|
|||||||
engine_manager: Option<kcl_lib::engine::conn_wasm::EngineCommandManager>,
|
engine_manager: Option<kcl_lib::engine::conn_wasm::EngineCommandManager>,
|
||||||
units: &str,
|
units: &str,
|
||||||
token: String,
|
token: String,
|
||||||
is_dev: bool,
|
baseurl: String,
|
||||||
) -> Result<(), JsValue> {
|
) -> Result<(), JsValue> {
|
||||||
console_error_panic_hook::set_once();
|
console_error_panic_hook::set_once();
|
||||||
|
|
||||||
@ -216,9 +216,7 @@ pub async fn kcl_lsp_run(
|
|||||||
let token_types = kcl_lib::token::TokenType::all_semantic_token_types().unwrap();
|
let token_types = kcl_lib::token::TokenType::all_semantic_token_types().unwrap();
|
||||||
|
|
||||||
let mut zoo_client = kittycad::Client::new(token);
|
let mut zoo_client = kittycad::Client::new(token);
|
||||||
if is_dev {
|
zoo_client.set_base_url(baseurl.as_str());
|
||||||
zoo_client.set_base_url("https://api.dev.zoo.dev");
|
|
||||||
}
|
|
||||||
|
|
||||||
let file_manager = Arc::new(kcl_lib::fs::FileManager::new(fs));
|
let file_manager = Arc::new(kcl_lib::fs::FileManager::new(fs));
|
||||||
|
|
||||||
@ -313,7 +311,7 @@ pub async fn kcl_lsp_run(
|
|||||||
|
|
||||||
// NOTE: input needs to be an AsyncIterator<Uint8Array, never, void> specifically
|
// NOTE: input needs to be an AsyncIterator<Uint8Array, never, void> specifically
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub async fn copilot_lsp_run(config: ServerConfig, token: String, is_dev: bool) -> Result<(), JsValue> {
|
pub async fn copilot_lsp_run(config: ServerConfig, token: String, baseurl: String) -> Result<(), JsValue> {
|
||||||
console_error_panic_hook::set_once();
|
console_error_panic_hook::set_once();
|
||||||
|
|
||||||
let ServerConfig {
|
let ServerConfig {
|
||||||
@ -323,9 +321,7 @@ pub async fn copilot_lsp_run(config: ServerConfig, token: String, is_dev: bool)
|
|||||||
} = config;
|
} = config;
|
||||||
|
|
||||||
let mut zoo_client = kittycad::Client::new(token);
|
let mut zoo_client = kittycad::Client::new(token);
|
||||||
if is_dev {
|
zoo_client.set_base_url(baseurl.as_str());
|
||||||
zoo_client.set_base_url("https://api.dev.zoo.dev");
|
|
||||||
}
|
|
||||||
|
|
||||||
let file_manager = Arc::new(kcl_lib::fs::FileManager::new(fs));
|
let file_manager = Arc::new(kcl_lib::fs::FileManager::new(fs));
|
||||||
|
|
||||||
@ -511,3 +507,19 @@ pub fn parse_project_settings(toml_str: &str) -> Result<JsValue, String> {
|
|||||||
// gloo-serialize crate instead.
|
// gloo-serialize crate instead.
|
||||||
JsValue::from_serde(&settings).map_err(|e| e.to_string())
|
JsValue::from_serde(&settings).map_err(|e| e.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse the project route.
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn parse_project_route(configuration: &str, route: &str) -> Result<JsValue, String> {
|
||||||
|
console_error_panic_hook::set_once();
|
||||||
|
|
||||||
|
let configuration: kcl_lib::settings::types::Configuration =
|
||||||
|
serde_json::from_str(configuration).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
let route =
|
||||||
|
kcl_lib::settings::types::file::ProjectRoute::from_route(&configuration, route).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
// The serde-wasm-bindgen does not work here because of weird HashMap issues so we use the
|
||||||
|
// gloo-serialize crate instead.
|
||||||
|
JsValue::from_serde(&route).map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user