Clean up tauri
@ -26,14 +26,6 @@
 | 
			
		||||
    "@lezer/lr": "^1.4.1",
 | 
			
		||||
    "@react-hook/resize-observer": "^2.0.1",
 | 
			
		||||
    "@replit/codemirror-interact": "^6.3.1",
 | 
			
		||||
    "@tauri-apps/api": "^2.0.0-beta.14",
 | 
			
		||||
    "@tauri-apps/plugin-dialog": "^2.0.0-beta.6",
 | 
			
		||||
    "@tauri-apps/plugin-fs": "^2.0.0-beta.6",
 | 
			
		||||
    "@tauri-apps/plugin-http": "^2.0.0-beta.7",
 | 
			
		||||
    "@tauri-apps/plugin-os": "^2.0.0-beta.6",
 | 
			
		||||
    "@tauri-apps/plugin-process": "^2.0.0-beta.6",
 | 
			
		||||
    "@tauri-apps/plugin-shell": "^2.0.0-beta.7",
 | 
			
		||||
    "@tauri-apps/plugin-updater": "^2.0.0-beta.6",
 | 
			
		||||
    "@ts-stack/markdown": "^1.5.0",
 | 
			
		||||
    "@tweenjs/tween.js": "^23.1.1",
 | 
			
		||||
    "@xstate/inspect": "^0.8.0",
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										3
									
								
								src-tauri/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						@ -1,3 +0,0 @@
 | 
			
		||||
# Generated by Cargo
 | 
			
		||||
# will have compiled files and executables
 | 
			
		||||
/target/
 | 
			
		||||
							
								
								
									
										7295
									
								
								src-tauri/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						@ -1,47 +0,0 @@
 | 
			
		||||
[package]
 | 
			
		||||
name = "app"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
description = "The Zoo Modeling App"
 | 
			
		||||
authors = ["Zoo Engineers <eng@zoo.dev>"]
 | 
			
		||||
license = ""
 | 
			
		||||
repository = "https://github.com/KittyCAD/modeling-app"
 | 
			
		||||
default-run = "app"
 | 
			
		||||
edition = "2021"
 | 
			
		||||
rust-version = "1.70"
 | 
			
		||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 | 
			
		||||
 | 
			
		||||
[build-dependencies]
 | 
			
		||||
tauri-build = { version = "2.0.0-beta.18", features = [] }
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
anyhow = "1"
 | 
			
		||||
kcl-lib = { version = "0.2", path = "../src/wasm-lib/kcl" }
 | 
			
		||||
kittycad = "0.3.7"
 | 
			
		||||
log = "0.4.21"
 | 
			
		||||
mdns-sd = "0.11.1"
 | 
			
		||||
oauth2 = "4.4.2"
 | 
			
		||||
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
 | 
			
		||||
serde_json = "1.0"
 | 
			
		||||
tauri = { version = "2.0.0-beta.23", features = [ "devtools", "unstable"] }
 | 
			
		||||
tauri-plugin-cli = { version = "2.0.0-beta.7" }
 | 
			
		||||
tauri-plugin-deep-link = { version = "2.0.0-beta.8" }
 | 
			
		||||
tauri-plugin-dialog = { version = "2.0.0-beta.6" }
 | 
			
		||||
tauri-plugin-fs = { version = "2.0.0-beta.10" }
 | 
			
		||||
tauri-plugin-http = { version = "2.0.0-beta.11" }
 | 
			
		||||
tauri-plugin-log = { version = "2.0.0-beta.7" }
 | 
			
		||||
tauri-plugin-os = { version = "2.0.0-beta.7" }
 | 
			
		||||
tauri-plugin-persisted-scope = { version = "2.0.0-beta.10" }
 | 
			
		||||
tauri-plugin-process = { version = "2.0.0-beta.7" }
 | 
			
		||||
tauri-plugin-shell = { version = "2.0.0-beta.8" }
 | 
			
		||||
tauri-plugin-updater = { version = "2.0.0-beta.9" }
 | 
			
		||||
tokio = { version = "1.37.0", features = ["time", "fs", "process"] }
 | 
			
		||||
toml = "0.8.2"
 | 
			
		||||
url = "2.5.0"
 | 
			
		||||
 | 
			
		||||
[features]
 | 
			
		||||
default = ["updater"]
 | 
			
		||||
# this feature is used for production builds or when `devPath` points to the filesystem and the built-in dev server is disabled.
 | 
			
		||||
# If you use cargo directly instead of tauri's cli you can use this feature flag to switch between tauri's `dev` and `build` modes.
 | 
			
		||||
# DO NOT REMOVE!!
 | 
			
		||||
custom-protocol = ["tauri/custom-protocol"]
 | 
			
		||||
updater = []
 | 
			
		||||
@ -1,376 +0,0 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 | 
			
		||||
<plist version="1.0">
 | 
			
		||||
    <dict>
 | 
			
		||||
        <key>CFBundleDevelopmentRegion</key>
 | 
			
		||||
        <string>en</string>
 | 
			
		||||
        <key>NSPrincipalClass</key>
 | 
			
		||||
        <string>NSApplication</string>
 | 
			
		||||
        <key>CFBundlePackageType</key>
 | 
			
		||||
        <string>APPL</string>
 | 
			
		||||
        <key>NSDesktopFolderUsageDescription</key>
 | 
			
		||||
        <string>Zoo Modeling App accesses the Desktop to load and save your project files and/or exported files here</string>
 | 
			
		||||
        <key>NSDocumentsFolderUsageDescription</key>
 | 
			
		||||
        <string>Zoo Modeling App accesses the Documents folder to load and save your project files and/or exported files here</string>
 | 
			
		||||
        <key>NSDownloadsFolderUsageDescription</key>
 | 
			
		||||
        <string>Zoo Modeling App accesses the Downloads folder to load and save your project files and/or exported files here</string>
 | 
			
		||||
        <key>ITSAppUsesNonExemptEncryption</key>
 | 
			
		||||
        <false/>
 | 
			
		||||
        <key>DTXcode</key>
 | 
			
		||||
        <string>1501</string>
 | 
			
		||||
        <key>DTXcodeBuild</key>
 | 
			
		||||
        <string>15A507</string>
 | 
			
		||||
        <key>CFBundleURLTypes</key>
 | 
			
		||||
        <array>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>CFBundleURLName</key>
 | 
			
		||||
                <string>dev.zoo.modeling-app</string>
 | 
			
		||||
                <key>CFBundleURLSchemes</key>
 | 
			
		||||
                <array>
 | 
			
		||||
                    <string>zoo-modeling-app</string>
 | 
			
		||||
                    <string>zoo</string>
 | 
			
		||||
                </array>
 | 
			
		||||
            </dict>
 | 
			
		||||
        </array>
 | 
			
		||||
        <key>LSFileQuarantineEnabled</key>
 | 
			
		||||
        <false/>
 | 
			
		||||
        <key>CFBundleDocumentTypes</key>
 | 
			
		||||
        <array>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>LSItemContentTypes</key>
 | 
			
		||||
                <array>
 | 
			
		||||
                    <string>dev.zoo.kcl</string>
 | 
			
		||||
                </array>
 | 
			
		||||
                <key>CFBundleTypeName</key>
 | 
			
		||||
                <string>KCL</string>
 | 
			
		||||
                <key>CFBundleTypeRole</key>
 | 
			
		||||
                <string>Editor</string>
 | 
			
		||||
                <key>LSTypeIsPackage</key>
 | 
			
		||||
                <false/>
 | 
			
		||||
                <key>LSHandlerRank</key>
 | 
			
		||||
                <string>Owner</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>LSItemContentTypes</key>
 | 
			
		||||
                <array>
 | 
			
		||||
                    <string>dev.zoo.toml</string>
 | 
			
		||||
                </array>
 | 
			
		||||
                <key>CFBundleTypeName</key>
 | 
			
		||||
                <string>TOML</string>
 | 
			
		||||
                <key>CFBundleTypeRole</key>
 | 
			
		||||
                <string>Editor</string>
 | 
			
		||||
                <key>LSTypeIsPackage</key>
 | 
			
		||||
                <false/>
 | 
			
		||||
                <key>LSHandlerRank</key>
 | 
			
		||||
                <string>Default</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>LSItemContentTypes</key>
 | 
			
		||||
                <array>
 | 
			
		||||
                    <string>dev.zoo.gltf</string>
 | 
			
		||||
                </array>
 | 
			
		||||
                <key>CFBundleTypeName</key>
 | 
			
		||||
                <string>glTF</string>
 | 
			
		||||
                <key>CFBundleTypeRole</key>
 | 
			
		||||
                <string>Editor</string>
 | 
			
		||||
                <key>LSTypeIsPackage</key>
 | 
			
		||||
                <false/>
 | 
			
		||||
                <key>LSHandlerRank</key>
 | 
			
		||||
                <string>Default</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>LSItemContentTypes</key>
 | 
			
		||||
                <array>
 | 
			
		||||
                    <string>dev.zoo.glb</string>
 | 
			
		||||
                </array>
 | 
			
		||||
                <key>CFBundleTypeName</key>
 | 
			
		||||
                <string>glb</string>
 | 
			
		||||
                <key>CFBundleTypeRole</key>
 | 
			
		||||
                <string>Editor</string>
 | 
			
		||||
                <key>LSTypeIsPackage</key>
 | 
			
		||||
                <false/>
 | 
			
		||||
                <key>LSHandlerRank</key>
 | 
			
		||||
                <string>Default</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>LSItemContentTypes</key>
 | 
			
		||||
                <array>
 | 
			
		||||
                    <string>dev.zoo.step</string>
 | 
			
		||||
                </array>
 | 
			
		||||
                <key>CFBundleTypeName</key>
 | 
			
		||||
                <string>STEP</string>
 | 
			
		||||
                <key>CFBundleTypeRole</key>
 | 
			
		||||
                <string>Editor</string>
 | 
			
		||||
                <key>LSTypeIsPackage</key>
 | 
			
		||||
                <false/>
 | 
			
		||||
                <key>LSHandlerRank</key>
 | 
			
		||||
                <string>Default</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>LSItemContentTypes</key>
 | 
			
		||||
                <array>
 | 
			
		||||
                    <string>dev.zoo.fbx</string>
 | 
			
		||||
                </array>
 | 
			
		||||
                <key>CFBundleTypeName</key>
 | 
			
		||||
                <string>FBX</string>
 | 
			
		||||
                <key>CFBundleTypeRole</key>
 | 
			
		||||
                <string>Editor</string>
 | 
			
		||||
                <key>LSTypeIsPackage</key>
 | 
			
		||||
                <false/>
 | 
			
		||||
                <key>LSHandlerRank</key>
 | 
			
		||||
                <string>Default</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>LSItemContentTypes</key>
 | 
			
		||||
                <array>
 | 
			
		||||
                    <string>dev.zoo.sldprt</string>
 | 
			
		||||
                </array>
 | 
			
		||||
                <key>CFBundleTypeName</key>
 | 
			
		||||
                <string>Solidworks Part</string>
 | 
			
		||||
                <key>CFBundleTypeRole</key>
 | 
			
		||||
                <string>Viewer</string>
 | 
			
		||||
                <key>LSTypeIsPackage</key>
 | 
			
		||||
                <false/>
 | 
			
		||||
                <key>LSHandlerRank</key>
 | 
			
		||||
                <string>Default</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>LSItemContentTypes</key>
 | 
			
		||||
                <array>
 | 
			
		||||
                    <string>public.geometry-definition-format</string>
 | 
			
		||||
                </array>
 | 
			
		||||
                <key>CFBundleTypeName</key>
 | 
			
		||||
                <string>OBJ</string>
 | 
			
		||||
                <key>CFBundleTypeRole</key>
 | 
			
		||||
                <string>Editor</string>
 | 
			
		||||
                <key>LSTypeIsPackage</key>
 | 
			
		||||
                <false/>
 | 
			
		||||
                <key>LSHandlerRank</key>
 | 
			
		||||
                <string>Default</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>LSItemContentTypes</key>
 | 
			
		||||
                <array>
 | 
			
		||||
                    <string>public.polygon-file-format</string>
 | 
			
		||||
                </array>
 | 
			
		||||
                <key>CFBundleTypeName</key>
 | 
			
		||||
                <string>PLY</string>
 | 
			
		||||
                <key>CFBundleTypeRole</key>
 | 
			
		||||
                <string>Editor</string>
 | 
			
		||||
                <key>LSTypeIsPackage</key>
 | 
			
		||||
                <false/>
 | 
			
		||||
                <key>LSHandlerRank</key>
 | 
			
		||||
                <string>Default</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>LSItemContentTypes</key>
 | 
			
		||||
                <array>
 | 
			
		||||
                    <string>public.standard-tesselated-geometry-format</string>
 | 
			
		||||
                </array>
 | 
			
		||||
                <key>CFBundleTypeName</key>
 | 
			
		||||
                <string>STL</string>
 | 
			
		||||
                <key>CFBundleTypeRole</key>
 | 
			
		||||
                <string>Editor</string>
 | 
			
		||||
                <key>LSTypeIsPackage</key>
 | 
			
		||||
                <false/>
 | 
			
		||||
                <key>LSHandlerRank</key>
 | 
			
		||||
                <string>Default</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>LSItemContentTypes</key>
 | 
			
		||||
                <array>
 | 
			
		||||
                    <string>public.folder</string>
 | 
			
		||||
                </array>
 | 
			
		||||
                <key>CFBundleTypeName</key>
 | 
			
		||||
                <string>Folders</string>
 | 
			
		||||
                <key>CFBundleTypeRole</key>
 | 
			
		||||
                <string>Viewer</string>
 | 
			
		||||
                <key>LSHandlerRank</key>
 | 
			
		||||
                <string>Alternate</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
        </array>
 | 
			
		||||
        <key>UTExportedTypeDeclarations</key>
 | 
			
		||||
        <array>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>UTTypeIdentifier</key>
 | 
			
		||||
                <string>dev.zoo.kcl</string>
 | 
			
		||||
                <key>UTTypeReferenceURL</key>
 | 
			
		||||
                <string>https://zoo.dev/docs/kcl</string>
 | 
			
		||||
                <key>UTTypeConformsTo</key>
 | 
			
		||||
                <array>
 | 
			
		||||
                    <string>public.source-code</string>
 | 
			
		||||
                    <string>public.data</string>
 | 
			
		||||
                    <string>public.text</string>
 | 
			
		||||
                    <string>public.plain-text</string>
 | 
			
		||||
                    <string>public.3d-content</string>
 | 
			
		||||
                    <string>public.script</string>
 | 
			
		||||
                </array>
 | 
			
		||||
                <key>UTTypeDescription</key>
 | 
			
		||||
                <string>KCL (KittyCAD Language) document</string>
 | 
			
		||||
                <key>UTTypeTagSpecification</key>
 | 
			
		||||
                <dict>
 | 
			
		||||
                    <key>public.filename-extension</key>
 | 
			
		||||
                    <array>
 | 
			
		||||
                        <string>kcl</string>
 | 
			
		||||
                    </array>
 | 
			
		||||
                    <key>public.mime-type</key>
 | 
			
		||||
                    <array>
 | 
			
		||||
                        <string>text/vnd.zoo.kcl</string>
 | 
			
		||||
                    </array>
 | 
			
		||||
                </dict>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>UTTypeIdentifier</key>
 | 
			
		||||
                <string>dev.zoo.gltf</string>
 | 
			
		||||
                <key>UTTypeReferenceURL</key>
 | 
			
		||||
                <string>https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html</string>
 | 
			
		||||
                <key>UTTypeConformsTo</key>
 | 
			
		||||
                <array>
 | 
			
		||||
                    <string>public.data</string>
 | 
			
		||||
                    <string>public.text</string>
 | 
			
		||||
                    <string>public.plain-text</string>
 | 
			
		||||
                    <string>public.3d-content</string>
 | 
			
		||||
                    <string>public.json</string>
 | 
			
		||||
                </array>
 | 
			
		||||
                <key>UTTypeDescription</key>
 | 
			
		||||
                <string>Graphics Library Transmission Format (glTF)</string>
 | 
			
		||||
                <key>UTTypeTagSpecification</key>
 | 
			
		||||
                <dict>
 | 
			
		||||
                    <key>public.filename-extension</key>
 | 
			
		||||
                    <array>
 | 
			
		||||
                        <string>gltf</string>
 | 
			
		||||
                    </array>
 | 
			
		||||
                    <key>public.mime-type</key>
 | 
			
		||||
                    <array>
 | 
			
		||||
                        <string>model/gltf+json</string>
 | 
			
		||||
                    </array>
 | 
			
		||||
                </dict>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>UTTypeIdentifier</key>
 | 
			
		||||
                <string>dev.zoo.glb</string>
 | 
			
		||||
                <key>UTTypeReferenceURL</key>
 | 
			
		||||
                <string>https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html</string>
 | 
			
		||||
                <key>UTTypeConformsTo</key>
 | 
			
		||||
                <array>
 | 
			
		||||
                    <string>public.data</string>
 | 
			
		||||
                    <string>public.3d-content</string>
 | 
			
		||||
                </array>
 | 
			
		||||
                <key>UTTypeDescription</key>
 | 
			
		||||
                <string>Graphics Library Transmission Format (glTF) binary</string>
 | 
			
		||||
                <key>UTTypeTagSpecification</key>
 | 
			
		||||
                <dict>
 | 
			
		||||
                    <key>public.filename-extension</key>
 | 
			
		||||
                    <array>
 | 
			
		||||
                        <string>glb</string>
 | 
			
		||||
                    </array>
 | 
			
		||||
                    <key>public.mime-type</key>
 | 
			
		||||
                    <array>
 | 
			
		||||
                        <string>model/gltf-binary</string>
 | 
			
		||||
                    </array>
 | 
			
		||||
                </dict>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>UTTypeIdentifier</key>
 | 
			
		||||
                <string>dev.zoo.step</string>
 | 
			
		||||
                <key>UTTypeReferenceURL</key>
 | 
			
		||||
                <string>https://www.loc.gov/preservation/digital/formats/fdd/fdd000448.shtml</string>
 | 
			
		||||
                <key>UTTypeConformsTo</key>
 | 
			
		||||
                <array>
 | 
			
		||||
                    <string>public.data</string>
 | 
			
		||||
                    <string>public.3d-content</string>
 | 
			
		||||
                    <string>public.text</string>
 | 
			
		||||
                    <string>public.plain-text</string>
 | 
			
		||||
                </array>
 | 
			
		||||
                <key>UTTypeDescription</key>
 | 
			
		||||
                <string>STEP-file, ISO 10303-21</string>
 | 
			
		||||
                <key>UTTypeTagSpecification</key>
 | 
			
		||||
                <dict>
 | 
			
		||||
                    <key>public.filename-extension</key>
 | 
			
		||||
                    <array>
 | 
			
		||||
                        <string>step</string>
 | 
			
		||||
                        <string>stp</string>
 | 
			
		||||
                    </array>
 | 
			
		||||
                    <key>public.mime-type</key>
 | 
			
		||||
                    <array>
 | 
			
		||||
                        <string>model/step</string>
 | 
			
		||||
                    </array>
 | 
			
		||||
                </dict>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>UTTypeIdentifier</key>
 | 
			
		||||
                <string>dev.zoo.sldprt</string>
 | 
			
		||||
                <key>UTTypeReferenceURL</key>
 | 
			
		||||
                <string>https://docs.fileformat.com/cad/sldprt/</string>
 | 
			
		||||
                <key>UTTypeConformsTo</key>
 | 
			
		||||
                <array>
 | 
			
		||||
                    <string>public.data</string>
 | 
			
		||||
                    <string>public.3d-content</string>
 | 
			
		||||
                </array>
 | 
			
		||||
                <key>UTTypeDescription</key>
 | 
			
		||||
                <string>Solidworks Part</string>
 | 
			
		||||
                <key>UTTypeTagSpecification</key>
 | 
			
		||||
                <dict>
 | 
			
		||||
                    <key>public.filename-extension</key>
 | 
			
		||||
                    <array>
 | 
			
		||||
                        <string>sldprt</string>
 | 
			
		||||
                    </array>
 | 
			
		||||
                    <key>public.mime-type</key>
 | 
			
		||||
                    <array>
 | 
			
		||||
                        <string>model/vnd.solidworks.sldprt</string>
 | 
			
		||||
                    </array>
 | 
			
		||||
                </dict>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>UTTypeIdentifier</key>
 | 
			
		||||
                <string>dev.zoo.fbx</string>
 | 
			
		||||
                <key>UTTypeReferenceURL</key>
 | 
			
		||||
                <string>https://en.wikipedia.org/wiki/FBX</string>
 | 
			
		||||
                <key>UTTypeConformsTo</key>
 | 
			
		||||
                <array>
 | 
			
		||||
                    <string>public.data</string>
 | 
			
		||||
                    <string>public.3d-content</string>
 | 
			
		||||
                </array>
 | 
			
		||||
                <key>UTTypeDescription</key>
 | 
			
		||||
                <string>Autodesk Filmbox (FBX) format</string>
 | 
			
		||||
                <key>UTTypeTagSpecification</key>
 | 
			
		||||
                <dict>
 | 
			
		||||
                    <key>public.filename-extension</key>
 | 
			
		||||
                    <array>
 | 
			
		||||
                        <string>fbx</string>
 | 
			
		||||
                        <string>fbxb</string>
 | 
			
		||||
                    </array>
 | 
			
		||||
                    <key>public.mime-type</key>
 | 
			
		||||
                    <array>
 | 
			
		||||
                        <string>model/vnd.autodesk.fbx</string>
 | 
			
		||||
                    </array>
 | 
			
		||||
                </dict>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>UTTypeIdentifier</key>
 | 
			
		||||
                <string>dev.zoo.toml</string>
 | 
			
		||||
                <key>UTTypeReferenceURL</key>
 | 
			
		||||
                <string>https://toml.io/en/</string>
 | 
			
		||||
                <key>UTTypeConformsTo</key>
 | 
			
		||||
                <array>
 | 
			
		||||
                    <string>public.data</string>
 | 
			
		||||
                    <string>public.text</string>
 | 
			
		||||
                    <string>public.plain-text</string>
 | 
			
		||||
                </array>
 | 
			
		||||
                <key>UTTypeDescription</key>
 | 
			
		||||
                <string>Tom's Obvious Minimal Language</string>
 | 
			
		||||
                <key>UTTypeTagSpecification</key>
 | 
			
		||||
                <dict>
 | 
			
		||||
                    <key>public.filename-extension</key>
 | 
			
		||||
                    <array>
 | 
			
		||||
                        <string>kcl</string>
 | 
			
		||||
                    </array>
 | 
			
		||||
                    <key>public.mime-type</key>
 | 
			
		||||
                    <array>
 | 
			
		||||
                        <string>text/toml</string>
 | 
			
		||||
                    </array>
 | 
			
		||||
                </dict>
 | 
			
		||||
            </dict>
 | 
			
		||||
        </array>
 | 
			
		||||
    </dict>
 | 
			
		||||
</plist>
 | 
			
		||||
@ -1,3 +0,0 @@
 | 
			
		||||
fn main() {
 | 
			
		||||
    tauri_build::build()
 | 
			
		||||
}
 | 
			
		||||
@ -1,127 +0,0 @@
 | 
			
		||||
{
 | 
			
		||||
  "$schema": "../gen/schemas/desktop-schema.json",
 | 
			
		||||
  "identifier": "main-capability",
 | 
			
		||||
  "description": "Capability for the main window",
 | 
			
		||||
  "context": "local",
 | 
			
		||||
  "windows": [
 | 
			
		||||
    "main"
 | 
			
		||||
  ],
 | 
			
		||||
  "permissions": [
 | 
			
		||||
    "cli:default",
 | 
			
		||||
    "deep-link:default",
 | 
			
		||||
    "log:default",
 | 
			
		||||
    "path:default",
 | 
			
		||||
    "event:default",
 | 
			
		||||
    "window:default",
 | 
			
		||||
    "app:default",
 | 
			
		||||
    "resources:default",
 | 
			
		||||
    "menu:default",
 | 
			
		||||
    "tray:default",
 | 
			
		||||
    "fs:allow-create",
 | 
			
		||||
    "fs:allow-read-file",
 | 
			
		||||
    "fs:allow-read-text-file",
 | 
			
		||||
    "fs:allow-write-file",
 | 
			
		||||
    "fs:allow-write-text-file",
 | 
			
		||||
    "fs:allow-read-dir",
 | 
			
		||||
    "fs:allow-copy-file",
 | 
			
		||||
    "fs:allow-mkdir",
 | 
			
		||||
    "fs:allow-remove",
 | 
			
		||||
    "fs:allow-rename",
 | 
			
		||||
    "fs:allow-exists",
 | 
			
		||||
    "fs:allow-stat",
 | 
			
		||||
    {
 | 
			
		||||
      "identifier": "fs:scope",
 | 
			
		||||
      "allow": [
 | 
			
		||||
        {
 | 
			
		||||
          "path": "$TEMP"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          "path": "$TEMP/**/*"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          "path": "$HOME"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          "path": "$HOME/**/*"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          "path": "$HOME/.config"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          "path": "$HOME/.config/**/*"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          "path": "$APPCONFIG"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          "path": "$APPCONFIG/**/*"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          "path": "$DOCUMENT"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          "path": "$DOCUMENT/**/*"
 | 
			
		||||
        }
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    "shell:allow-open",
 | 
			
		||||
    {
 | 
			
		||||
      "identifier": "shell:allow-execute",
 | 
			
		||||
      "allow": [
 | 
			
		||||
        {
 | 
			
		||||
          "name": "open",
 | 
			
		||||
          "cmd": "open",
 | 
			
		||||
          "args": [
 | 
			
		||||
            "-R",
 | 
			
		||||
            {
 | 
			
		||||
              "validator": "\\S+"
 | 
			
		||||
            }
 | 
			
		||||
          ],
 | 
			
		||||
          "sidecar": false
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          "name": "explorer",
 | 
			
		||||
          "cmd": "explorer",
 | 
			
		||||
          "args": [
 | 
			
		||||
            "/select",
 | 
			
		||||
            {
 | 
			
		||||
              "validator": "\\S+"
 | 
			
		||||
            }
 | 
			
		||||
          ],
 | 
			
		||||
          "sidecar": false
 | 
			
		||||
        }
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    "dialog:allow-open",
 | 
			
		||||
    "dialog:allow-save",
 | 
			
		||||
    "dialog:allow-message",
 | 
			
		||||
    "dialog:allow-ask",
 | 
			
		||||
    "dialog:allow-confirm",
 | 
			
		||||
    {
 | 
			
		||||
      "identifier": "http:default",
 | 
			
		||||
      "allow": [
 | 
			
		||||
        "https://dev.kittycad.io/*",
 | 
			
		||||
        "https://dev.zoo.dev/*",
 | 
			
		||||
        "https://kittycad.io/*",
 | 
			
		||||
        "https://zoo.dev/*",
 | 
			
		||||
        "https://api.dev.kittycad.io/*",
 | 
			
		||||
        "https://api.dev.zoo.dev/*"
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    "os:allow-platform",
 | 
			
		||||
    "os:allow-version",
 | 
			
		||||
    "os:allow-os-type",
 | 
			
		||||
    "os:allow-family",
 | 
			
		||||
    "os:allow-arch",
 | 
			
		||||
    "os:allow-exe-extension",
 | 
			
		||||
    "os:allow-locale",
 | 
			
		||||
    "os:allow-hostname",
 | 
			
		||||
    "process:allow-restart",
 | 
			
		||||
    "updater:default"
 | 
			
		||||
  ],
 | 
			
		||||
  "platforms": [
 | 
			
		||||
    "linux",
 | 
			
		||||
    "macOS",
 | 
			
		||||
    "windows"
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
@ -1,24 +0,0 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 | 
			
		||||
<plist version="1.0">
 | 
			
		||||
    <dict>
 | 
			
		||||
        <key>com.apple.security.app-sandbox</key>
 | 
			
		||||
        <true/>
 | 
			
		||||
        <key>com.apple.security.network.server</key>
 | 
			
		||||
        <true/>
 | 
			
		||||
        <key>com.apple.security.network.client</key>
 | 
			
		||||
        <true/>
 | 
			
		||||
        <key>com.apple.security.files.user-selected.read-write</key>
 | 
			
		||||
        <true/>
 | 
			
		||||
        <key>com.apple.security.files.downloads.read-write</key>
 | 
			
		||||
        <true/>
 | 
			
		||||
        <key>com.apple.application-identifier</key>
 | 
			
		||||
        <string>92H8YB3B95.dev.zoo.modeling-app</string>
 | 
			
		||||
        <key>com.apple.developer.team-identifier</key>
 | 
			
		||||
        <string>92H8YB3B95</string>
 | 
			
		||||
        <key>com.apple.developer.associated-domains</key>
 | 
			
		||||
        <array>
 | 
			
		||||
            <string>applinks:app.zoo.dev</string>
 | 
			
		||||
        </array>
 | 
			
		||||
    </dict>
 | 
			
		||||
</plist>
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 8.1 KiB  | 
| 
		 Before Width: | Height: | Size: 21 KiB  | 
| 
		 Before Width: | Height: | Size: 1.5 KiB  | 
| 
		 Before Width: | Height: | Size: 6.7 KiB  | 
| 
		 Before Width: | Height: | Size: 9.8 KiB  | 
| 
		 Before Width: | Height: | Size: 10 KiB  | 
| 
		 Before Width: | Height: | Size: 24 KiB  | 
| 
		 Before Width: | Height: | Size: 1.4 KiB  | 
| 
		 Before Width: | Height: | Size: 28 KiB  | 
| 
		 Before Width: | Height: | Size: 2.3 KiB  | 
| 
		 Before Width: | Height: | Size: 4.0 KiB  | 
| 
		 Before Width: | Height: | Size: 5.6 KiB  | 
| 
		 Before Width: | Height: | Size: 2.7 KiB  | 
| 
		 Before Width: | Height: | Size: 2.6 KiB  | 
| 
		 Before Width: | Height: | Size: 12 KiB  | 
| 
		 Before Width: | Height: | Size: 2.6 KiB  | 
| 
		 Before Width: | Height: | Size: 2.5 KiB  | 
| 
		 Before Width: | Height: | Size: 6.5 KiB  | 
| 
		 Before Width: | Height: | Size: 2.5 KiB  | 
| 
		 Before Width: | Height: | Size: 5.7 KiB  | 
| 
		 Before Width: | Height: | Size: 16 KiB  | 
| 
		 Before Width: | Height: | Size: 5.7 KiB  | 
| 
		 Before Width: | Height: | Size: 9.5 KiB  | 
| 
		 Before Width: | Height: | Size: 29 KiB  | 
| 
		 Before Width: | Height: | Size: 9.5 KiB  | 
| 
		 Before Width: | Height: | Size: 14 KiB  | 
| 
		 Before Width: | Height: | Size: 45 KiB  | 
| 
		 Before Width: | Height: | Size: 14 KiB  | 
| 
		 Before Width: | Height: | Size: 32 KiB  | 
| 
		 Before Width: | Height: | Size: 59 KiB  | 
| 
		 Before Width: | Height: | Size: 793 B  | 
| 
		 Before Width: | Height: | Size: 1.7 KiB  | 
| 
		 Before Width: | Height: | Size: 1.7 KiB  | 
| 
		 Before Width: | Height: | Size: 2.6 KiB  | 
| 
		 Before Width: | Height: | Size: 1.2 KiB  | 
| 
		 Before Width: | Height: | Size: 2.7 KiB  | 
| 
		 Before Width: | Height: | Size: 2.7 KiB  | 
| 
		 Before Width: | Height: | Size: 4.2 KiB  | 
| 
		 Before Width: | Height: | Size: 1.7 KiB  | 
| 
		 Before Width: | Height: | Size: 3.7 KiB  | 
| 
		 Before Width: | Height: | Size: 3.7 KiB  | 
| 
		 Before Width: | Height: | Size: 5.8 KiB  | 
| 
		 Before Width: | Height: | Size: 62 KiB  | 
| 
		 Before Width: | Height: | Size: 5.8 KiB  | 
| 
		 Before Width: | Height: | Size: 9.3 KiB  | 
| 
		 Before Width: | Height: | Size: 3.6 KiB  | 
| 
		 Before Width: | Height: | Size: 7.4 KiB  | 
| 
		 Before Width: | Height: | Size: 8.6 KiB  | 
@ -1,6 +0,0 @@
 | 
			
		||||
max_width = 120
 | 
			
		||||
edition = "2018"
 | 
			
		||||
format_code_in_doc_comments = true
 | 
			
		||||
format_strings = false
 | 
			
		||||
imports_granularity = "Crate"
 | 
			
		||||
group_imports = "StdExternalCrate"
 | 
			
		||||
@ -1,603 +0,0 @@
 | 
			
		||||
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
 | 
			
		||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
 | 
			
		||||
 | 
			
		||||
pub(crate) mod state;
 | 
			
		||||
 | 
			
		||||
use std::{
 | 
			
		||||
    env,
 | 
			
		||||
    path::{Path, PathBuf},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use anyhow::Result;
 | 
			
		||||
use kcl_lib::settings::types::{
 | 
			
		||||
    file::{FileEntry, Project, ProjectRoute, ProjectState},
 | 
			
		||||
    project::ProjectConfiguration,
 | 
			
		||||
    Configuration,
 | 
			
		||||
};
 | 
			
		||||
use oauth2::TokenResponse;
 | 
			
		||||
use tauri::{ipc::InvokeError, Manager};
 | 
			
		||||
use tauri_plugin_cli::CliExt;
 | 
			
		||||
use tauri_plugin_shell::ShellExt;
 | 
			
		||||
 | 
			
		||||
const DEFAULT_HOST: &str = "https://api.zoo.dev";
 | 
			
		||||
const SETTINGS_FILE_NAME: &str = "settings.toml";
 | 
			
		||||
const PROJECT_SETTINGS_FILE_NAME: &str = "project.toml";
 | 
			
		||||
const PROJECT_FOLDER: &str = "zoo-modeling-app-projects";
 | 
			
		||||
 | 
			
		||||
#[tauri::command]
 | 
			
		||||
async fn rename_project_directory(project_path: &str, new_name: &str) -> Result<PathBuf, InvokeError> {
 | 
			
		||||
    let project_dir = std::path::Path::new(project_path);
 | 
			
		||||
 | 
			
		||||
    kcl_lib::settings::types::file::rename_project_directory(project_dir, new_name)
 | 
			
		||||
        .await
 | 
			
		||||
        .map_err(InvokeError::from_anyhow)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[tauri::command]
 | 
			
		||||
fn get_initial_default_dir(app: tauri::AppHandle) -> Result<PathBuf, InvokeError> {
 | 
			
		||||
    let dir = match app.path().document_dir() {
 | 
			
		||||
        Ok(dir) => dir,
 | 
			
		||||
        Err(_) => {
 | 
			
		||||
            // for headless Linux (eg. Github Actions)
 | 
			
		||||
            let home_dir = app.path().home_dir()?;
 | 
			
		||||
            home_dir.join("Documents")
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    Ok(dir.join(PROJECT_FOLDER))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[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()?;
 | 
			
		||||
 | 
			
		||||
    // 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))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[tauri::command]
 | 
			
		||||
async fn read_app_settings_file(app: tauri::AppHandle) -> Result<Configuration, InvokeError> {
 | 
			
		||||
    let mut settings_path = get_app_settings_file_path(&app).await?;
 | 
			
		||||
    let mut needs_migration = false;
 | 
			
		||||
 | 
			
		||||
    // Check if this file exists.
 | 
			
		||||
    if !settings_path.exists() {
 | 
			
		||||
        // Try the backwards compatible path.
 | 
			
		||||
        // TODO: Remove this after a few releases.
 | 
			
		||||
        let app_config_dir = app.path().app_config_dir()?;
 | 
			
		||||
        settings_path = format!(
 | 
			
		||||
            "{}user.toml",
 | 
			
		||||
            app_config_dir.display().to_string().trim_end_matches('/')
 | 
			
		||||
        )
 | 
			
		||||
        .into();
 | 
			
		||||
        needs_migration = true;
 | 
			
		||||
        // Check if this path exists.
 | 
			
		||||
        if !settings_path.exists() {
 | 
			
		||||
            let mut default = Configuration::default();
 | 
			
		||||
            default.settings.project.directory = get_initial_default_dir(app.clone())?;
 | 
			
		||||
            // Return the default configuration.
 | 
			
		||||
            return Ok(default);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let contents = tokio::fs::read_to_string(&settings_path)
 | 
			
		||||
        .await
 | 
			
		||||
        .map_err(|e| InvokeError::from_anyhow(e.into()))?;
 | 
			
		||||
    let mut parsed = Configuration::backwards_compatible_toml_parse(&contents).map_err(InvokeError::from_anyhow)?;
 | 
			
		||||
    if parsed.settings.project.directory == PathBuf::new() {
 | 
			
		||||
        parsed.settings.project.directory = get_initial_default_dir(app.clone())?;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // TODO: Remove this after a few releases.
 | 
			
		||||
    if needs_migration {
 | 
			
		||||
        write_app_settings_file(app, parsed.clone()).await?;
 | 
			
		||||
        // Delete the old file.
 | 
			
		||||
        tokio::fs::remove_file(settings_path)
 | 
			
		||||
            .await
 | 
			
		||||
            .map_err(|e| InvokeError::from_anyhow(e.into()))?;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Ok(parsed)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[tauri::command]
 | 
			
		||||
async fn write_app_settings_file(app: tauri::AppHandle, configuration: Configuration) -> Result<(), InvokeError> {
 | 
			
		||||
    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()))?;
 | 
			
		||||
    tokio::fs::write(settings_path, contents.as_bytes())
 | 
			
		||||
        .await
 | 
			
		||||
        .map_err(|e| InvokeError::from_anyhow(e.into()))?;
 | 
			
		||||
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async fn get_project_settings_file_path(project_path: &str) -> Result<PathBuf, InvokeError> {
 | 
			
		||||
    let project_dir = std::path::Path::new(project_path);
 | 
			
		||||
 | 
			
		||||
    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]
 | 
			
		||||
async fn read_project_settings_file(project_path: &str) -> Result<ProjectConfiguration, InvokeError> {
 | 
			
		||||
    let settings_path = get_project_settings_file_path(project_path).await?;
 | 
			
		||||
 | 
			
		||||
    // Check if this file exists.
 | 
			
		||||
    if !settings_path.exists() {
 | 
			
		||||
        // Return the default configuration.
 | 
			
		||||
        return Ok(ProjectConfiguration::default());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let contents = tokio::fs::read_to_string(&settings_path)
 | 
			
		||||
        .await
 | 
			
		||||
        .map_err(|e| InvokeError::from_anyhow(e.into()))?;
 | 
			
		||||
    let parsed = ProjectConfiguration::backwards_compatible_toml_parse(&contents).map_err(InvokeError::from_anyhow)?;
 | 
			
		||||
 | 
			
		||||
    Ok(parsed)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[tauri::command]
 | 
			
		||||
async fn write_project_settings_file(
 | 
			
		||||
    project_path: &str,
 | 
			
		||||
    configuration: ProjectConfiguration,
 | 
			
		||||
) -> Result<(), InvokeError> {
 | 
			
		||||
    let settings_path = get_project_settings_file_path(project_path).await?;
 | 
			
		||||
    let contents = toml::to_string_pretty(&configuration).map_err(|e| InvokeError::from_anyhow(e.into()))?;
 | 
			
		||||
    tokio::fs::write(settings_path, contents.as_bytes())
 | 
			
		||||
        .await
 | 
			
		||||
        .map_err(|e| InvokeError::from_anyhow(e.into()))?;
 | 
			
		||||
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Initialize the directory that holds all the projects.
 | 
			
		||||
#[tauri::command]
 | 
			
		||||
async fn initialize_project_directory(configuration: Configuration) -> Result<PathBuf, InvokeError> {
 | 
			
		||||
    configuration
 | 
			
		||||
        .ensure_project_directory_exists()
 | 
			
		||||
        .await
 | 
			
		||||
        .map_err(InvokeError::from_anyhow)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Create a new project directory.
 | 
			
		||||
#[tauri::command]
 | 
			
		||||
async fn create_new_project_directory(
 | 
			
		||||
    configuration: Configuration,
 | 
			
		||||
    project_name: &str,
 | 
			
		||||
    initial_code: Option<&str>,
 | 
			
		||||
) -> Result<Project, InvokeError> {
 | 
			
		||||
    configuration
 | 
			
		||||
        .create_new_project_directory(project_name, initial_code)
 | 
			
		||||
        .await
 | 
			
		||||
        .map_err(InvokeError::from_anyhow)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// List all the projects in the project directory.
 | 
			
		||||
#[tauri::command]
 | 
			
		||||
async fn list_projects(configuration: Configuration) -> Result<Vec<Project>, InvokeError> {
 | 
			
		||||
    configuration.list_projects().await.map_err(InvokeError::from_anyhow)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Get information about a project.
 | 
			
		||||
#[tauri::command]
 | 
			
		||||
async fn get_project_info(configuration: Configuration, project_path: &str) -> Result<Project, InvokeError> {
 | 
			
		||||
    configuration
 | 
			
		||||
        .get_project_info(project_path)
 | 
			
		||||
        .await
 | 
			
		||||
        .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]
 | 
			
		||||
async fn read_dir_recursive(path: &str) -> Result<FileEntry, InvokeError> {
 | 
			
		||||
    kcl_lib::settings::utils::walk_dir(Path::new(path).to_path_buf())
 | 
			
		||||
        .await
 | 
			
		||||
        .map_err(InvokeError::from_anyhow)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// This command instantiates a new window with auth.
 | 
			
		||||
/// The string returned from this method is the access token.
 | 
			
		||||
#[tauri::command]
 | 
			
		||||
async fn login(app: tauri::AppHandle, host: &str) -> Result<String, InvokeError> {
 | 
			
		||||
    log::debug!("Logging in...");
 | 
			
		||||
    // Do an OAuth 2.0 Device Authorization Grant dance to get a token.
 | 
			
		||||
    let device_auth_url = oauth2::DeviceAuthorizationUrl::new(format!("{host}/oauth2/device/auth"))
 | 
			
		||||
        .map_err(|e| InvokeError::from_anyhow(e.into()))?;
 | 
			
		||||
    // We can hardcode the client ID.
 | 
			
		||||
    // This value is safe to be embedded in version control.
 | 
			
		||||
    // This is the client ID of the KittyCAD app.
 | 
			
		||||
    let client_id = "2af127fb-e14e-400a-9c57-a9ed08d1a5b7".to_string();
 | 
			
		||||
    let auth_client = oauth2::basic::BasicClient::new(
 | 
			
		||||
        oauth2::ClientId::new(client_id),
 | 
			
		||||
        None,
 | 
			
		||||
        oauth2::AuthUrl::new(format!("{host}/authorize")).map_err(|e| InvokeError::from_anyhow(e.into()))?,
 | 
			
		||||
        Some(
 | 
			
		||||
            oauth2::TokenUrl::new(format!("{host}/oauth2/device/token"))
 | 
			
		||||
                .map_err(|e| InvokeError::from_anyhow(e.into()))?,
 | 
			
		||||
        ),
 | 
			
		||||
    )
 | 
			
		||||
    .set_auth_type(oauth2::AuthType::RequestBody)
 | 
			
		||||
    .set_device_authorization_url(device_auth_url);
 | 
			
		||||
 | 
			
		||||
    let details: oauth2::devicecode::StandardDeviceAuthorizationResponse = auth_client
 | 
			
		||||
        .exchange_device_code()
 | 
			
		||||
        .map_err(|e| InvokeError::from_anyhow(e.into()))?
 | 
			
		||||
        .request_async(oauth2::reqwest::async_http_client)
 | 
			
		||||
        .await
 | 
			
		||||
        .map_err(|e| InvokeError::from_anyhow(e.into()))?;
 | 
			
		||||
 | 
			
		||||
    let Some(auth_uri) = details.verification_uri_complete() else {
 | 
			
		||||
        return Err(InvokeError::from("getting the verification uri failed"));
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Open the system browser with the auth_uri.
 | 
			
		||||
    // We do this in the browser and not a separate window because we want 1password and
 | 
			
		||||
    // other crap to work well.
 | 
			
		||||
    // TODO: find a better way to share this value with tauri e2e tests
 | 
			
		||||
    // Here we're using an env var to enable the /tmp file (windows not supported for now)
 | 
			
		||||
    // and bypass the shell::open call as it fails on GitHub Actions.
 | 
			
		||||
    let e2e_tauri_enabled = env::var("E2E_TAURI_ENABLED").is_ok();
 | 
			
		||||
    if e2e_tauri_enabled {
 | 
			
		||||
        log::warn!("E2E_TAURI_ENABLED is set, won't open {} externally", auth_uri.secret());
 | 
			
		||||
        let mut temp = String::from("/tmp");
 | 
			
		||||
        // Overwrite with Windows variable
 | 
			
		||||
        match env::var("TEMP") {
 | 
			
		||||
            Ok(val) => temp = val,
 | 
			
		||||
            Err(_e) => println!("Fallback to default /tmp"),
 | 
			
		||||
        }
 | 
			
		||||
        let path = Path::new(&temp).join("kittycad_user_code");
 | 
			
		||||
        println!("Writing to {}", path.to_string_lossy());
 | 
			
		||||
        tokio::fs::write(path, details.user_code().secret())
 | 
			
		||||
            .await
 | 
			
		||||
            .map_err(|e| InvokeError::from_anyhow(e.into()))?;
 | 
			
		||||
    } else {
 | 
			
		||||
        app.shell()
 | 
			
		||||
            .open(auth_uri.secret(), None)
 | 
			
		||||
            .map_err(|e| InvokeError::from_anyhow(e.into()))?;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Wait for the user to login.
 | 
			
		||||
    let token = auth_client
 | 
			
		||||
        .exchange_device_access_token(&details)
 | 
			
		||||
        .request_async(oauth2::reqwest::async_http_client, tokio::time::sleep, None)
 | 
			
		||||
        .await
 | 
			
		||||
        .map_err(|e| InvokeError::from_anyhow(e.into()))?
 | 
			
		||||
        .access_token()
 | 
			
		||||
        .secret()
 | 
			
		||||
        .to_string();
 | 
			
		||||
 | 
			
		||||
    Ok(token)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
///This command returns the KittyCAD user info given a token.
 | 
			
		||||
/// The string returned from this method is the user info as a json string.
 | 
			
		||||
#[tauri::command]
 | 
			
		||||
async fn get_user(token: &str, hostname: &str) -> Result<kittycad::types::User, InvokeError> {
 | 
			
		||||
    // Use the host passed in if it's set.
 | 
			
		||||
    // Otherwise, use the default host.
 | 
			
		||||
    let host = if hostname.is_empty() {
 | 
			
		||||
        DEFAULT_HOST.to_string()
 | 
			
		||||
    } else {
 | 
			
		||||
        hostname.to_string()
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Change the baseURL to the one we want.
 | 
			
		||||
    let mut baseurl = host.to_string();
 | 
			
		||||
    if !host.starts_with("http://") && !host.starts_with("https://") {
 | 
			
		||||
        baseurl = format!("https://{host}");
 | 
			
		||||
        if host.starts_with("localhost") {
 | 
			
		||||
            baseurl = format!("http://{host}")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    log::debug!("Getting user info...");
 | 
			
		||||
 | 
			
		||||
    // use kittycad library to fetch the user info from /user/me
 | 
			
		||||
    let mut client = kittycad::Client::new(token);
 | 
			
		||||
 | 
			
		||||
    if baseurl != DEFAULT_HOST {
 | 
			
		||||
        client.set_base_url(&baseurl);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let user_info: kittycad::types::User = client
 | 
			
		||||
        .users()
 | 
			
		||||
        .get_self()
 | 
			
		||||
        .await
 | 
			
		||||
        .map_err(|e| InvokeError::from_anyhow(e.into()))?;
 | 
			
		||||
 | 
			
		||||
    Ok(user_info)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Open the selected path in the system file manager.
 | 
			
		||||
/// From this GitHub comment: https://github.com/tauri-apps/tauri/issues/4062#issuecomment-1338048169
 | 
			
		||||
/// But with the Linux support removed since we don't need it for now.
 | 
			
		||||
#[tauri::command]
 | 
			
		||||
fn show_in_folder(app: tauri::AppHandle, path: &str) -> Result<(), InvokeError> {
 | 
			
		||||
    // Check if the file exists.
 | 
			
		||||
    // If it doesn't, return an error.
 | 
			
		||||
    if !Path::new(path).exists() {
 | 
			
		||||
        return Err(InvokeError::from_anyhow(anyhow::anyhow!(
 | 
			
		||||
            "The file `{}` does not exist",
 | 
			
		||||
            path
 | 
			
		||||
        )));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[cfg(not(unix))]
 | 
			
		||||
    {
 | 
			
		||||
        app.shell()
 | 
			
		||||
            .command("explorer")
 | 
			
		||||
            .args(["/select,", path]) // The comma after select is not a typo
 | 
			
		||||
            .spawn()
 | 
			
		||||
            .map_err(|e| InvokeError::from_anyhow(e.into()))?;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[cfg(unix)]
 | 
			
		||||
    {
 | 
			
		||||
        app.shell()
 | 
			
		||||
            .command("open")
 | 
			
		||||
            .args(["-R", path])
 | 
			
		||||
            .spawn()
 | 
			
		||||
            .map_err(|e| InvokeError::from_anyhow(e.into()))?;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const SERVICE_NAME: &str = "_machine-api._tcp.local.";
 | 
			
		||||
 | 
			
		||||
async fn find_machine_api() -> Result<Option<String>> {
 | 
			
		||||
    println!("Looking for machine API...");
 | 
			
		||||
    // Timeout if no response is received after 5 seconds.
 | 
			
		||||
    let timeout_duration = std::time::Duration::from_secs(5);
 | 
			
		||||
 | 
			
		||||
    let mdns = mdns_sd::ServiceDaemon::new()?;
 | 
			
		||||
 | 
			
		||||
    // Browse for a service type.
 | 
			
		||||
    let receiver = mdns.browse(SERVICE_NAME)?;
 | 
			
		||||
    let resp = tokio::time::timeout(
 | 
			
		||||
        timeout_duration,
 | 
			
		||||
        tokio::spawn(async move {
 | 
			
		||||
            while let Ok(event) = receiver.recv() {
 | 
			
		||||
                if let mdns_sd::ServiceEvent::ServiceResolved(info) = event {
 | 
			
		||||
                    if let Some(addr) = info.get_addresses().iter().next() {
 | 
			
		||||
                        return Some(format!("{}:{}", addr, info.get_port()));
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            None
 | 
			
		||||
        }),
 | 
			
		||||
    )
 | 
			
		||||
    .await;
 | 
			
		||||
 | 
			
		||||
    // Shut down.
 | 
			
		||||
    mdns.shutdown()?;
 | 
			
		||||
 | 
			
		||||
    let Ok(Ok(Some(addr))) = resp else {
 | 
			
		||||
        return Ok(None);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    Ok(Some(addr))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[tauri::command]
 | 
			
		||||
async fn get_machine_api_ip() -> Result<Option<String>, InvokeError> {
 | 
			
		||||
    let machine_api = find_machine_api().await.map_err(InvokeError::from_anyhow)?;
 | 
			
		||||
 | 
			
		||||
    Ok(machine_api)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[tauri::command]
 | 
			
		||||
async fn list_machines() -> Result<String, InvokeError> {
 | 
			
		||||
    let machine_api = find_machine_api().await.map_err(InvokeError::from_anyhow)?;
 | 
			
		||||
 | 
			
		||||
    let Some(machine_api) = machine_api else {
 | 
			
		||||
        // Empty array.
 | 
			
		||||
        return Ok("[]".to_string());
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let client = reqwest::Client::new();
 | 
			
		||||
    let response = client
 | 
			
		||||
        .get(format!("http://{}/machines", machine_api))
 | 
			
		||||
        .send()
 | 
			
		||||
        .await
 | 
			
		||||
        .map_err(|e| InvokeError::from_anyhow(e.into()))?;
 | 
			
		||||
 | 
			
		||||
    let text = response.text().await.map_err(|e| InvokeError::from_anyhow(e.into()))?;
 | 
			
		||||
    Ok(text)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[allow(dead_code)]
 | 
			
		||||
fn open_url_sync(app: &tauri::AppHandle, url: &url::Url) {
 | 
			
		||||
    log::debug!("Opening URL: {:?}", url);
 | 
			
		||||
    let cloned_url = url.clone();
 | 
			
		||||
    let runner: tauri::async_runtime::JoinHandle<Result<ProjectState>> = tauri::async_runtime::spawn(async move {
 | 
			
		||||
        let url_str = cloned_url.path().to_string();
 | 
			
		||||
 | 
			
		||||
        log::debug!("Opening URL path : {}", url_str);
 | 
			
		||||
        let path = Path::new(url_str.as_str());
 | 
			
		||||
        ProjectState::new_from_path(path.to_path_buf()).await
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // Block on the handle.
 | 
			
		||||
    match tauri::async_runtime::block_on(runner) {
 | 
			
		||||
        Ok(Ok(store)) => {
 | 
			
		||||
            // Create a state object to hold the project.
 | 
			
		||||
            app.manage(state::Store::new(store));
 | 
			
		||||
        }
 | 
			
		||||
        Err(e) => {
 | 
			
		||||
            log::warn!("Error opening URL:{} {:?}", url, e);
 | 
			
		||||
        }
 | 
			
		||||
        Ok(Err(e)) => {
 | 
			
		||||
            log::warn!("Error opening URL:{} {:?}", url, e);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn main() -> Result<()> {
 | 
			
		||||
    tauri::Builder::default()
 | 
			
		||||
        .invoke_handler(tauri::generate_handler![
 | 
			
		||||
            get_state,
 | 
			
		||||
            set_state,
 | 
			
		||||
            get_initial_default_dir,
 | 
			
		||||
            initialize_project_directory,
 | 
			
		||||
            create_new_project_directory,
 | 
			
		||||
            list_projects,
 | 
			
		||||
            get_project_info,
 | 
			
		||||
            parse_project_route,
 | 
			
		||||
            get_user,
 | 
			
		||||
            login,
 | 
			
		||||
            read_dir_recursive,
 | 
			
		||||
            show_in_folder,
 | 
			
		||||
            read_app_settings_file,
 | 
			
		||||
            write_app_settings_file,
 | 
			
		||||
            read_project_settings_file,
 | 
			
		||||
            write_project_settings_file,
 | 
			
		||||
            rename_project_directory,
 | 
			
		||||
            get_machine_api_ip,
 | 
			
		||||
            list_machines
 | 
			
		||||
        ])
 | 
			
		||||
        .plugin(tauri_plugin_cli::init())
 | 
			
		||||
        .plugin(tauri_plugin_deep_link::init())
 | 
			
		||||
        .plugin(tauri_plugin_dialog::init())
 | 
			
		||||
        .plugin(tauri_plugin_fs::init())
 | 
			
		||||
        .plugin(tauri_plugin_http::init())
 | 
			
		||||
        .plugin(
 | 
			
		||||
            tauri_plugin_log::Builder::new()
 | 
			
		||||
                .targets([
 | 
			
		||||
                    tauri_plugin_log::Target::new(tauri_plugin_log::TargetKind::Stdout),
 | 
			
		||||
                    tauri_plugin_log::Target::new(tauri_plugin_log::TargetKind::LogDir { file_name: None }),
 | 
			
		||||
                ])
 | 
			
		||||
                .level(log::LevelFilter::Debug)
 | 
			
		||||
                .build(),
 | 
			
		||||
        )
 | 
			
		||||
        .plugin(tauri_plugin_os::init())
 | 
			
		||||
        .plugin(tauri_plugin_persisted_scope::init())
 | 
			
		||||
        .plugin(tauri_plugin_process::init())
 | 
			
		||||
        .plugin(tauri_plugin_shell::init())
 | 
			
		||||
        .setup(|app| {
 | 
			
		||||
            // Do update things.
 | 
			
		||||
            #[cfg(debug_assertions)]
 | 
			
		||||
            {
 | 
			
		||||
                app.get_webview("main").unwrap().open_devtools();
 | 
			
		||||
            }
 | 
			
		||||
            #[cfg(not(debug_assertions))]
 | 
			
		||||
            #[cfg(feature = "updater")]
 | 
			
		||||
            {
 | 
			
		||||
                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() {
 | 
			
		||||
                            log::info!("Got path in cli argument: {}", value);
 | 
			
		||||
                            source_path = Some(Path::new(value).to_path_buf());
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                Err(err) => {
 | 
			
		||||
                    return Err(anyhow::anyhow!("Error parsing CLI arguments: {:?}", err).into());
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if verbose {
 | 
			
		||||
                log::debug!("Verbose mode enabled.");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // 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 { ProjectState::new_from_path(source_path).await });
 | 
			
		||||
 | 
			
		||||
            // 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));
 | 
			
		||||
 | 
			
		||||
            // Listen on the deep links.
 | 
			
		||||
            app.listen("deep-link://new-url", |event| {
 | 
			
		||||
                log::info!("got deep-link url: {:?}", event);
 | 
			
		||||
                // TODO: open_url_sync(app.handle(), event.url);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            Ok(())
 | 
			
		||||
        })
 | 
			
		||||
        .build(tauri::generate_context!())?
 | 
			
		||||
        .run(
 | 
			
		||||
            #[allow(unused_variables)]
 | 
			
		||||
            |app, event| {
 | 
			
		||||
                #[cfg(any(target_os = "macos", target_os = "ios"))]
 | 
			
		||||
                if let tauri::RunEvent::Opened { urls } = event {
 | 
			
		||||
                    log::info!("Opened URLs: {:?}", urls);
 | 
			
		||||
 | 
			
		||||
                    // Handle the first URL.
 | 
			
		||||
                    // TODO: do we want to handle more than one URL?
 | 
			
		||||
                    // Under what conditions would we even have more than one?
 | 
			
		||||
                    if let Some(url) = urls.first() {
 | 
			
		||||
                        open_url_sync(app, url);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
@ -1,21 +0,0 @@
 | 
			
		||||
//! 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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,8 +0,0 @@
 | 
			
		||||
{
 | 
			
		||||
  "$schema": "../node_modules/@tauri-apps/cli/schema.json",
 | 
			
		||||
  "bundle": {
 | 
			
		||||
    "macOS": {
 | 
			
		||||
      "entitlements": "entitlements/app-store.entitlements"
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -1,84 +0,0 @@
 | 
			
		||||
{
 | 
			
		||||
  "$schema": "../node_modules/@tauri-apps/cli/schema.json",
 | 
			
		||||
  "app": {
 | 
			
		||||
    "security": {
 | 
			
		||||
      "csp": null
 | 
			
		||||
    },
 | 
			
		||||
    "windows": [
 | 
			
		||||
      {
 | 
			
		||||
        "fullscreen": false,
 | 
			
		||||
        "height": 1200,
 | 
			
		||||
        "resizable": true,
 | 
			
		||||
        "title": "Zoo Modeling App",
 | 
			
		||||
        "width": 1800
 | 
			
		||||
      }
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  "build": {
 | 
			
		||||
    "beforeDevCommand": "yarn start",
 | 
			
		||||
    "devUrl": "http://localhost:3000",
 | 
			
		||||
    "frontendDist": "../build"
 | 
			
		||||
  },
 | 
			
		||||
  "bundle": {
 | 
			
		||||
    "active": true,
 | 
			
		||||
    "category": "DeveloperTool",
 | 
			
		||||
    "copyright": "",
 | 
			
		||||
    "externalBin": [],
 | 
			
		||||
    "icon": [
 | 
			
		||||
      "icons/32x32.png",
 | 
			
		||||
      "icons/128x128.png",
 | 
			
		||||
      "icons/128x128@2x.png",
 | 
			
		||||
      "icons/icon.icns",
 | 
			
		||||
      "icons/icon.ico"
 | 
			
		||||
    ],
 | 
			
		||||
    "linux": {
 | 
			
		||||
      "deb": {
 | 
			
		||||
        "depends": []
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "longDescription": "",
 | 
			
		||||
    "macOS": {},
 | 
			
		||||
    "resources": [],
 | 
			
		||||
    "shortDescription": "",
 | 
			
		||||
    "targets": "all"
 | 
			
		||||
  },
 | 
			
		||||
  "identifier": "dev.zoo.modeling-app",
 | 
			
		||||
  "plugins": {
 | 
			
		||||
    "cli": {
 | 
			
		||||
      "description": "Zoo Modeling App CLI",
 | 
			
		||||
      "args": [
 | 
			
		||||
        {
 | 
			
		||||
          "short": "v",
 | 
			
		||||
          "name": "verbose",
 | 
			
		||||
          "description": "Verbosity level"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          "name": "source",
 | 
			
		||||
          "description": "The file or directory to open",
 | 
			
		||||
          "required": false,
 | 
			
		||||
          "index": 1,
 | 
			
		||||
          "takesValue": true
 | 
			
		||||
        }
 | 
			
		||||
      ],
 | 
			
		||||
      "subcommands": {}
 | 
			
		||||
    },
 | 
			
		||||
    "deep-link": {
 | 
			
		||||
      "mobile": [
 | 
			
		||||
        {
 | 
			
		||||
          "host": "app.zoo.dev"
 | 
			
		||||
        }
 | 
			
		||||
      ],
 | 
			
		||||
      "desktop": {
 | 
			
		||||
        "schemes": [
 | 
			
		||||
          "zoo",
 | 
			
		||||
          "zoo-modeling-app"
 | 
			
		||||
        ]
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "shell": {
 | 
			
		||||
      "open": true
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  "productName": "Zoo Modeling App",
 | 
			
		||||
  "version": "0.24.10"
 | 
			
		||||
}
 | 
			
		||||
@ -1,57 +0,0 @@
 | 
			
		||||
{
 | 
			
		||||
  "$schema": "../node_modules/@tauri-apps/cli/schema.json",
 | 
			
		||||
  "bundle": {
 | 
			
		||||
    "windows": {
 | 
			
		||||
      "certificateThumbprint": "F4C9A52FF7BC26EE5E054946F6B11DEEA94C748D",
 | 
			
		||||
      "digestAlgorithm": "sha256",
 | 
			
		||||
      "timestampUrl": "http://timestamp.digicert.com"
 | 
			
		||||
    },
 | 
			
		||||
    "fileAssociations": [
 | 
			
		||||
      {
 | 
			
		||||
        "ext": ["kcl"],
 | 
			
		||||
        "mimeType": "text/vnd.zoo.kcl"
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "ext": ["obj"],
 | 
			
		||||
        "mimeType": "model/obj"
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "ext": ["gltf"],
 | 
			
		||||
        "mimeType": "model/gltf+json"
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "ext": ["glb"],
 | 
			
		||||
        "mimeType": "model/gltf+binary"
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "ext": ["fbx", "fbxb"],
 | 
			
		||||
        "mimeType": "model/fbx"
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "ext": ["stl"],
 | 
			
		||||
        "mimeType": "model/stl"
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "ext": ["ply"],
 | 
			
		||||
        "mimeType": "model/ply"
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "ext": ["step", "stp"],
 | 
			
		||||
        "mimeType": "model/step"
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "ext": ["sldprt"],
 | 
			
		||||
        "mimeType": "model/sldprt"
 | 
			
		||||
      }
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  "plugins": {
 | 
			
		||||
    "updater": {
 | 
			
		||||
      "active": true,
 | 
			
		||||
      "endpoints": [
 | 
			
		||||
        "https://dl.zoo.dev/releases/modeling-app/last_update.json"
 | 
			
		||||
      ],
 | 
			
		||||
      "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEUzNzA4MjBEQjFBRTY4NzYKUldSMmFLNnhEWUp3NCtsT21Jd05wQktOaGVkOVp6MUFma0hNTDRDSnI2RkJJTEZOWG1ncFhqcU8K"
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -16,7 +16,6 @@ import {
 | 
			
		||||
import { useCommandsContext } from 'hooks/useCommandsContext'
 | 
			
		||||
import { fileMachine } from 'machines/fileMachine'
 | 
			
		||||
import { isDesktop } from 'lib/isDesktop'
 | 
			
		||||
import { join, sep } from '@tauri-apps/api/path'
 | 
			
		||||
import { DEFAULT_FILE_NAME, FILE_EXT } from 'lib/constants'
 | 
			
		||||
import { getProjectInfo } from 'lib/desktop'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -18,9 +18,9 @@ import { APP_VERSION } from 'routes/Settings'
 | 
			
		||||
import { createAndOpenNewProject, getSettingsFolderPaths } from 'lib/desktopFS'
 | 
			
		||||
import { paths } from 'lib/paths'
 | 
			
		||||
import { useDotDotSlash } from 'hooks/useDotDotSlash'
 | 
			
		||||
import { sep } from '@tauri-apps/api/path'
 | 
			
		||||
import { ForwardedRef, forwardRef, useEffect } from 'react'
 | 
			
		||||
import { useLspContext } from 'components/LspProvider'
 | 
			
		||||
import { ForwardedRef, forwardRef, useEffect } from 'react'
 | 
			
		||||
 | 
			
		||||
interface AllSettingsFieldsProps {
 | 
			
		||||
  searchParamTab: SettingsLevel
 | 
			
		||||
 | 
			
		||||
@ -27,7 +27,7 @@ export default class CodeManager {
 | 
			
		||||
 | 
			
		||||
    const storedCode = safeLSGetItem(PERSIST_CODE_KEY)
 | 
			
		||||
    // TODO #819 remove zustand persistence logic in a few months
 | 
			
		||||
    // short term migration, shouldn't make a difference for tauri app users
 | 
			
		||||
    // short term migration, shouldn't make a difference for desktop app users
 | 
			
		||||
    // anyway since that's filesystem based.
 | 
			
		||||
    const zustandStore = JSON.parse(safeLSGetItem('store') || '{}')
 | 
			
		||||
    if (storedCode === null && zustandStore?.state?.code) {
 | 
			
		||||
 | 
			
		||||
@ -932,10 +932,142 @@ class EngineConnection extends EventTarget {
 | 
			
		||||
            event.data
 | 
			
		||||
          )
 | 
			
		||||
 | 
			
		||||
          if (!message.success) {
 | 
			
		||||
            const errorsString = message?.errors
 | 
			
		||||
              ?.map((error) => {
 | 
			
		||||
                return `  - ${error.error_code}: ${error.message}`
 | 
			
		||||
    const createWebSocketConnection = () => {
 | 
			
		||||
      this.state = {
 | 
			
		||||
        type: EngineConnectionStateType.Connecting,
 | 
			
		||||
        value: {
 | 
			
		||||
          type: ConnectingType.WebSocketConnecting,
 | 
			
		||||
        },
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      this.websocket = new WebSocket(this.url, [])
 | 
			
		||||
      this.websocket.binaryType = 'arraybuffer'
 | 
			
		||||
 | 
			
		||||
      this.onWebSocketOpen = (event) => {
 | 
			
		||||
        this.state = {
 | 
			
		||||
          type: EngineConnectionStateType.Connecting,
 | 
			
		||||
          value: {
 | 
			
		||||
            type: ConnectingType.WebSocketOpen,
 | 
			
		||||
          },
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // This is required for when KCMA is running stand-alone / within desktop.
 | 
			
		||||
        // Otherwise when run in a browser, the token is sent implicitly via
 | 
			
		||||
        // the Cookie header.
 | 
			
		||||
        if (this.token) {
 | 
			
		||||
          this.send({
 | 
			
		||||
            type: 'headers',
 | 
			
		||||
            headers: { Authorization: `Bearer ${this.token}` },
 | 
			
		||||
          })
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Send an initial ping
 | 
			
		||||
        this.send({ type: 'ping' })
 | 
			
		||||
        this.pingPongSpan.ping = new Date()
 | 
			
		||||
      }
 | 
			
		||||
      this.websocket.addEventListener('open', this.onWebSocketOpen)
 | 
			
		||||
 | 
			
		||||
      this.onWebSocketClose = (event) => {
 | 
			
		||||
        this.disconnectAll()
 | 
			
		||||
        this.finalizeIfAllConnectionsClosed()
 | 
			
		||||
      }
 | 
			
		||||
      this.websocket.addEventListener('close', this.onWebSocketClose)
 | 
			
		||||
 | 
			
		||||
      this.onWebSocketError = (event) => {
 | 
			
		||||
        this.disconnectAll()
 | 
			
		||||
 | 
			
		||||
        this.state = {
 | 
			
		||||
          type: EngineConnectionStateType.Disconnecting,
 | 
			
		||||
          value: {
 | 
			
		||||
            type: DisconnectingType.Error,
 | 
			
		||||
            value: {
 | 
			
		||||
              error: ConnectionError.WebSocketError,
 | 
			
		||||
              context: event,
 | 
			
		||||
            },
 | 
			
		||||
          },
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      this.websocket.addEventListener('error', this.onWebSocketError)
 | 
			
		||||
 | 
			
		||||
      this.onWebSocketMessage = (event) => {
 | 
			
		||||
        // In the EngineConnection, we're looking for messages to/from
 | 
			
		||||
        // the server that relate to the ICE handshake, or WebRTC
 | 
			
		||||
        // negotiation. There may be other messages (including ArrayBuffer
 | 
			
		||||
        // messages) that are intended for the GUI itself, so be careful
 | 
			
		||||
        // when assuming we're the only consumer or that all messages will
 | 
			
		||||
        // be carefully formatted here.
 | 
			
		||||
 | 
			
		||||
        if (typeof event.data !== 'string') {
 | 
			
		||||
          return
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const message: Models['WebSocketResponse_type'] = JSON.parse(event.data)
 | 
			
		||||
 | 
			
		||||
        if (!message.success) {
 | 
			
		||||
          const errorsString = message?.errors
 | 
			
		||||
            ?.map((error) => {
 | 
			
		||||
              return `  - ${error.error_code}: ${error.message}`
 | 
			
		||||
            })
 | 
			
		||||
            .join('\n')
 | 
			
		||||
          if (message.request_id) {
 | 
			
		||||
            const artifactThatFailed =
 | 
			
		||||
              this.engineCommandManager.artifactMap[message.request_id]
 | 
			
		||||
            console.error(
 | 
			
		||||
              `Error in response to request ${message.request_id}:\n${errorsString}
 | 
			
		||||
  failed cmd type was ${artifactThatFailed?.type}`
 | 
			
		||||
            )
 | 
			
		||||
          } else {
 | 
			
		||||
            console.error(`Error from server:\n${errorsString}`)
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          const firstError = message?.errors[0]
 | 
			
		||||
          if (firstError.error_code === 'auth_token_invalid') {
 | 
			
		||||
            this.state = {
 | 
			
		||||
              type: EngineConnectionStateType.Disconnecting,
 | 
			
		||||
              value: {
 | 
			
		||||
                type: DisconnectingType.Error,
 | 
			
		||||
                value: {
 | 
			
		||||
                  error: ConnectionError.BadAuthToken,
 | 
			
		||||
                  context: firstError.message,
 | 
			
		||||
                },
 | 
			
		||||
              },
 | 
			
		||||
            }
 | 
			
		||||
            this.disconnectAll()
 | 
			
		||||
          }
 | 
			
		||||
          return
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let resp = message.resp
 | 
			
		||||
 | 
			
		||||
        // If there's no body to the response, we can bail here.
 | 
			
		||||
        if (!resp || !resp.type) {
 | 
			
		||||
          return
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        switch (resp.type) {
 | 
			
		||||
          case 'pong':
 | 
			
		||||
            this.pingPongSpan.pong = new Date()
 | 
			
		||||
            break
 | 
			
		||||
          case 'ice_server_info':
 | 
			
		||||
            let ice_servers = resp.data?.ice_servers
 | 
			
		||||
 | 
			
		||||
            // Now that we have some ICE servers it makes sense
 | 
			
		||||
            // to start initializing the RTCPeerConnection. RTCPeerConnection
 | 
			
		||||
            // will begin the ICE process.
 | 
			
		||||
            createPeerConnection()
 | 
			
		||||
 | 
			
		||||
            this.state = {
 | 
			
		||||
              type: EngineConnectionStateType.Connecting,
 | 
			
		||||
              value: {
 | 
			
		||||
                type: ConnectingType.PeerConnectionCreated,
 | 
			
		||||
              },
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // No ICE servers can be valid in a local dev. env.
 | 
			
		||||
            if (ice_servers?.length === 0) {
 | 
			
		||||
              console.warn('No ICE servers')
 | 
			
		||||
              this.pc?.setConfiguration({
 | 
			
		||||
                bundlePolicy: 'max-bundle',
 | 
			
		||||
              })
 | 
			
		||||
              .join('\n')
 | 
			
		||||
            if (message.request_id) {
 | 
			
		||||
 | 
			
		||||
@ -9,7 +9,7 @@ export const openExternalBrowserIfDesktop = (to) => function(e) {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Open a new browser window tauri style or browser style.
 | 
			
		||||
// Open a new browser window desktop style or browser style.
 | 
			
		||||
export default async function openWindow(url: string) {
 | 
			
		||||
  if (isDesktop()) {
 | 
			
		||||
    await window.electron.openExternal(url)
 | 
			
		||||
 | 
			
		||||
@ -10,7 +10,6 @@ import {
 | 
			
		||||
} from 'lib/constants'
 | 
			
		||||
import { loadAndValidateSettings } from './settings/settingsUtils'
 | 
			
		||||
import makeUrlPathRelative from './makeUrlPathRelative'
 | 
			
		||||
import { sep } from '@tauri-apps/api/path'
 | 
			
		||||
import { codeManager, kclManager } from 'lib/singletons'
 | 
			
		||||
import { fileSystemManager } from 'lang/std/fileSystemManager'
 | 
			
		||||
import {
 | 
			
		||||
 | 
			
		||||
@ -206,7 +206,7 @@ export function createSettings() {
 | 
			
		||||
              />
 | 
			
		||||
              <button
 | 
			
		||||
                onClick={async () => {
 | 
			
		||||
                  // In Tauri end-to-end tests we can't control the file picker,
 | 
			
		||||
                  // In desktop end-to-end tests we can't control the file picker,
 | 
			
		||||
                  // so we seed the new directory value in the element's dataset
 | 
			
		||||
                  const inputRefVal = inputRef.current?.dataset.testValue
 | 
			
		||||
                  if (inputRef.current && inputRefVal && !Array.isArray(inputRefVal)) {
 | 
			
		||||
 | 
			
		||||
@ -8,7 +8,6 @@ import { isDesktop } from 'lib/isDesktop'
 | 
			
		||||
import { useNavigate, useRouteLoaderData } from 'react-router-dom'
 | 
			
		||||
import { paths } from 'lib/paths'
 | 
			
		||||
import { codeManager, kclManager } from 'lib/singletons'
 | 
			
		||||
import { join } from '@tauri-apps/api/path'
 | 
			
		||||
import {
 | 
			
		||||
  APP_NAME,
 | 
			
		||||
  ONBOARDING_PROJECT_NAME,
 | 
			
		||||
 | 
			
		||||
@ -7,7 +7,7 @@ export default function ProjectMenu() {
 | 
			
		||||
  const { context } = useModelingContext()
 | 
			
		||||
  const dismiss = useDismiss()
 | 
			
		||||
  const next = useNextClick(onboardingPaths.EXPORT)
 | 
			
		||||
  const tauri = isDesktop()
 | 
			
		||||
  const onDesktop = isDesktop()
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="fixed grid justify-center items-start inset-0 z-50 pointer-events-none">
 | 
			
		||||
@ -20,14 +20,14 @@ export default function ProjectMenu() {
 | 
			
		||||
        <section className="flex-1">
 | 
			
		||||
          <h2 className="text-2xl font-bold">Project Menu</h2>
 | 
			
		||||
          <p className="my-4">
 | 
			
		||||
            Click on {tauri ? `your part's name` : `the app name`} in the upper
 | 
			
		||||
            Click on {onDesktop ? `your part's name` : `the app name`} in the upper
 | 
			
		||||
            left to open the project menu, where you can open the project
 | 
			
		||||
            settings and export your current part.
 | 
			
		||||
            {tauri && (
 | 
			
		||||
            {onDesktop && (
 | 
			
		||||
              <> You can click the Zoo logo to quickly navigate home.</>
 | 
			
		||||
            )}
 | 
			
		||||
          </p>
 | 
			
		||||
          {tauri ? (
 | 
			
		||||
          {onDesktop ? (
 | 
			
		||||
            <>
 | 
			
		||||
              <p className="my-4">
 | 
			
		||||
                From here you can manage files in your project and export your
 | 
			
		||||
 | 
			
		||||