Project state improvements (#2239)
This commit is contained in:
113
src-tauri/Cargo.lock
generated
113
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,33 @@ 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",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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 +801,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 +997,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 +2403,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kcl-lib"
|
name = "kcl-lib"
|
||||||
version = "0.1.51"
|
version = "0.1.52"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"approx",
|
"approx",
|
||||||
@ -4580,6 +4662,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 +5046,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"
|
||||||
@ -5820,6 +5923,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.52", 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, 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,6 +39,19 @@ fn get_initial_default_dir(app: tauri::AppHandle) -> Result<PathBuf, InvokeError
|
|||||||
Ok(dir.join(PROJECT_FOLDER))
|
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(())
|
||||||
|
}
|
||||||
|
|
||||||
fn get_app_settings_file_path(app: &tauri::AppHandle) -> Result<PathBuf, InvokeError> {
|
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()?;
|
||||||
Ok(app_config_dir.join(SETTINGS_FILE_NAME))
|
Ok(app_config_dir.join(SETTINGS_FILE_NAME))
|
||||||
@ -172,9 +188,9 @@ 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)
|
||||||
}
|
}
|
||||||
@ -328,6 +344,8 @@ fn main() -> Result<()> {
|
|||||||
Ok(())
|
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,
|
||||||
@ -342,6 +360,113 @@ 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| {
|
||||||
|
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 {
|
||||||
|
// 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(())
|
||||||
|
})
|
||||||
.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,6 +50,22 @@
|
|||||||
},
|
},
|
||||||
"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
|
||||||
}
|
}
|
||||||
|
@ -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,30 @@ 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) {
|
||||||
|
console.log('appState', 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,
|
||||||
|
@ -25,7 +25,9 @@ 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<
|
||||||
|
ReturnType<typeof createSettings> | ReturnType<typeof redirect>
|
||||||
|
> => {
|
||||||
let { settings } = await loadAndValidateSettings()
|
let { settings } = 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,
|
||||||
@ -105,7 +107,7 @@ export const fileLoader: LoaderFunction = async ({
|
|||||||
const projectData: IndexLoaderData = {
|
const projectData: IndexLoaderData = {
|
||||||
code,
|
code,
|
||||||
project: isTauri()
|
project: isTauri()
|
||||||
? await getProjectInfo(projectName)
|
? await getProjectInfo(projectPath)
|
||||||
: {
|
: {
|
||||||
name: projectName,
|
name: projectName,
|
||||||
path: projectPath,
|
path: projectPath,
|
||||||
|
@ -6,6 +6,17 @@ 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'
|
||||||
|
|
||||||
|
// 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 +64,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 +72,7 @@ export async function getProjectInfo(
|
|||||||
}
|
}
|
||||||
return await invoke<Project>('get_project_info', {
|
return await invoke<Project>('get_project_info', {
|
||||||
configuration,
|
configuration,
|
||||||
projectName,
|
projectPath,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
2
src/wasm-lib/Cargo.lock
generated
2
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.52"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"approx 0.5.1",
|
"approx 0.5.1",
|
||||||
|
@ -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.52"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://github.com/KittyCAD/modeling-app"
|
repository = "https://github.com/KittyCAD/modeling-app"
|
||||||
|
@ -5,6 +5,15 @@ use parse_display::{Display, FromStr};
|
|||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// 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>,
|
||||||
|
}
|
||||||
|
|
||||||
/// 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 +32,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;
|
||||||
|
@ -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,6 +1,6 @@
|
|||||||
//! Utility functions for settings.
|
//! Utility functions for settings.
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::Path;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
@ -8,20 +8,21 @@ use crate::settings::types::file::FileEntry;
|
|||||||
|
|
||||||
/// 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? {
|
||||||
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?);
|
||||||
@ -34,9 +35,8 @@ 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)
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user