use js_sys::{Array, Function, Reflect};
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::JsFuture;
#[cfg(target_arch = "wasm32")]
use web_sys::window;
pub type FsResult<T> = Result<T, String>;
#[derive(Debug, Clone)]
pub struct FileEntry {
pub name: String,
pub path: String,
pub is_directory: bool,
}
fn is_tauri() -> bool {
window()
.and_then(|w| Reflect::get(&w, &JsValue::from_str("__TAURI__")).ok())
.map(|v| !v.is_undefined())
.unwrap_or(false)
}
async fn tauri_invoke(cmd: &str, args: JsValue) -> Result<JsValue, JsValue> {
let window = window().ok_or_else(|| JsValue::from_str("No window"))?;
let tauri = Reflect::get(&window, &JsValue::from_str("__TAURI__"))?;
let core = Reflect::get(&tauri, &JsValue::from_str("core"))?;
let invoke_fn = Reflect::get(&core, &JsValue::from_str("invoke"))?;
let invoke_fn = invoke_fn
.dyn_into::<Function>()
.map_err(|_| JsValue::from_str("invoke is not a function"))?;
let promise = invoke_fn.call2(&JsValue::NULL, &JsValue::from_str(cmd), &args)?;
let promise = promise
.dyn_into::<js_sys::Promise>()
.map_err(|_| JsValue::from_str("invoke did not return a promise"))?;
JsFuture::from(promise).await
}
pub fn read_file(path: String) -> FsResult<String> {
if is_tauri() {
web_sys::console::log_1(&JsValue::from_str(&format!(
"Tauri detected, but using localStorage for: {}",
path
)));
}
let storage = window()
.ok_or("No window available")?
.local_storage()
.map_err(|e| format!("Failed to access localStorage: {:?}", e))?
.ok_or("localStorage not available")?;
let key = format!("wj_file:{}", path);
storage
.get_item(&key)
.map_err(|e| format!("Failed to read file: {:?}", e))?
.ok_or_else(|| format!("File not found: {}", path))
}
pub fn read_bytes(path: String) -> FsResult<Vec<u8>> {
let content = read_file(path)?;
Ok(content.into_bytes())
}
pub fn write_file(path: String, content: String) -> FsResult<()> {
if is_tauri() {
write_file_tauri(path, content)
} else {
write_file_localstorage(path, content)
}
}
fn write_file_tauri(path: String, content: String) -> FsResult<()> {
let window = window().ok_or("No window available")?;
let tauri_bridge = Reflect::get(&window, &JsValue::from_str("__WINDJAMMER_TAURI__"))
.map_err(|_| "Tauri bridge not found")?;
let write_fn = Reflect::get(&tauri_bridge, &JsValue::from_str("writeFileSync"))
.map_err(|_| "writeFileSync function not found")?;
let write_fn = write_fn
.dyn_into::<Function>()
.map_err(|_| "writeFileSync is not a function")?;
write_fn
.call2(
&JsValue::NULL,
&JsValue::from_str(&path),
&JsValue::from_str(&content),
)
.map_err(|e| format!("Failed to call writeFileSync: {:?}", e))?;
Ok(())
}
fn write_file_localstorage(path: String, content: String) -> FsResult<()> {
let storage = window()
.ok_or("No window available")?
.local_storage()
.map_err(|e| format!("Failed to access localStorage: {:?}", e))?
.ok_or("localStorage not available")?;
let key = format!("wj_file:{}", path);
storage
.set_item(&key, &content)
.map_err(|e| format!("Failed to write file: {:?}", e))?;
let dir = get_directory_from_path(&path);
add_file_to_directory(&dir, &path)?;
Ok(())
}
pub fn list_directory(path: String) -> FsResult<Vec<FileEntry>> {
let storage = window()
.ok_or("No window available")?
.local_storage()
.map_err(|e| format!("Failed to access localStorage: {:?}", e))?
.ok_or("localStorage not available")?;
let dir_key = format!("wj_dir:{}", path);
let files_json = storage
.get_item(&dir_key)
.map_err(|e| format!("Failed to list directory: {:?}", e))?
.unwrap_or_else(|| "[]".to_string());
let files: Vec<String> = serde_json::from_str(&files_json).unwrap_or_else(|_| Vec::new());
let mut entries = Vec::new();
for file_path in files {
let name = file_path
.split('/')
.last()
.unwrap_or(&file_path)
.to_string();
entries.push(FileEntry {
name,
path: file_path.clone(),
is_directory: false,
});
}
Ok(entries)
}
pub fn create_directory(path: String) -> FsResult<()> {
if is_tauri() {
create_directory_tauri(path)
} else {
create_directory_localstorage(path)
}
}
fn create_directory_tauri(path: String) -> FsResult<()> {
let window = window().ok_or("No window available")?;
let tauri_bridge = Reflect::get(&window, &JsValue::from_str("__WINDJAMMER_TAURI__"))
.map_err(|_| "Tauri bridge not found")?;
let create_fn = Reflect::get(&tauri_bridge, &JsValue::from_str("createDirectorySync"))
.map_err(|_| "createDirectorySync function not found")?;
let create_fn = create_fn
.dyn_into::<Function>()
.map_err(|_| "createDirectorySync is not a function")?;
create_fn
.call1(&JsValue::NULL, &JsValue::from_str(&path))
.map_err(|e| format!("Failed to call createDirectorySync: {:?}", e))?;
Ok(())
}
fn create_directory_localstorage(path: String) -> FsResult<()> {
let storage = window()
.ok_or("No window available")?
.local_storage()
.map_err(|e| format!("Failed to access localStorage: {:?}", e))?
.ok_or("localStorage not available")?;
let dir_key = format!("wj_dir:{}", path);
storage
.set_item(&dir_key, "[]")
.map_err(|e| format!("Failed to create directory: {:?}", e))?;
Ok(())
}
pub fn delete_file(path: String) -> FsResult<()> {
let storage = window()
.ok_or("No window available")?
.local_storage()
.map_err(|e| format!("Failed to access localStorage: {:?}", e))?
.ok_or("localStorage not available")?;
let key = format!("wj_file:{}", path);
storage
.remove_item(&key)
.map_err(|e| format!("Failed to delete file: {:?}", e))?;
let dir = get_directory_from_path(&path);
remove_file_from_directory(&dir, &path)?;
Ok(())
}
pub fn file_exists(path: String) -> bool {
window()
.and_then(|w| w.local_storage().ok())
.and_then(|s| s)
.and_then(|storage| {
let key = format!("wj_file:{}", path);
storage.get_item(&key).ok().flatten()
})
.is_some()
}
fn get_directory_from_path(path: &str) -> String {
let parts: Vec<&str> = path.rsplitn(2, '/').collect();
if parts.len() > 1 {
parts[1].to_string()
} else {
"/".to_string()
}
}
fn add_file_to_directory(dir: &str, file_path: &str) -> FsResult<()> {
let storage = window()
.ok_or("No window available")?
.local_storage()
.map_err(|e| format!("Failed to access localStorage: {:?}", e))?
.ok_or("localStorage not available")?;
let dir_key = format!("wj_dir:{}", dir);
let files_json = storage
.get_item(&dir_key)
.map_err(|e| format!("Failed to read directory: {:?}", e))?
.unwrap_or_else(|| "[]".to_string());
let mut files: Vec<String> = serde_json::from_str(&files_json).unwrap_or_else(|_| Vec::new());
if !files.contains(&file_path.to_string()) {
files.push(file_path.to_string());
let updated_json = serde_json::to_string(&files)
.map_err(|e| format!("Failed to serialize directory: {}", e))?;
storage
.set_item(&dir_key, &updated_json)
.map_err(|e| format!("Failed to update directory: {:?}", e))?;
}
Ok(())
}
fn remove_file_from_directory(dir: &str, file_path: &str) -> FsResult<()> {
let storage = window()
.ok_or("No window available")?
.local_storage()
.map_err(|e| format!("Failed to access localStorage: {:?}", e))?
.ok_or("localStorage not available")?;
let dir_key = format!("wj_dir:{}", dir);
let files_json = storage
.get_item(&dir_key)
.map_err(|e| format!("Failed to read directory: {:?}", e))?
.unwrap_or_else(|| "[]".to_string());
let mut files: Vec<String> = serde_json::from_str(&files_json).unwrap_or_else(|_| Vec::new());
files.retain(|f| f != file_path);
let updated_json = serde_json::to_string(&files)
.map_err(|e| format!("Failed to serialize directory: {}", e))?;
storage
.set_item(&dir_key, &updated_json)
.map_err(|e| format!("Failed to update directory: {:?}", e))?;
Ok(())
}