Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a Tauri/WASM filesystem #104

Open
wants to merge 26 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
ea6e14e
Add TypeScript support.
PythonCoderAS Jun 4, 2024
46368ab
Type simulator.js
PythonCoderAS Jun 7, 2024
ccf4ff2
Add window globals
PythonCoderAS Jun 11, 2024
7e3d873
Properly type rust functions/worker
PythonCoderAS Jun 14, 2024
8e04784
Move old JS files to typescript
PythonCoderAS Jun 14, 2024
50df096
Properly type rust functions/worker
PythonCoderAS Jun 14, 2024
0a4957e
Create file system integration for Tauri
AleksBekker Jun 18, 2024
96fa1e3
Add a menu for browser (WASM) mode
PythonCoderAS Jun 21, 2024
8bd7076
Add initial sidebar support
PythonCoderAS Jun 21, 2024
9af6e2e
Add directory flag to read_dir
AleksBekker Jun 28, 2024
dad68de
Create support for a WASM FS
PythonCoderAS Jul 9, 2024
058887a
Hook up sidebar to virtual FS
PythonCoderAS Jul 12, 2024
8d305e4
Implement write in file system interface
AleksBekker Jul 16, 2024
2fa808d
Make a working file/folder FS implementation
PythonCoderAS Jul 16, 2024
c25e6d4
Add new modal and project persistence
PythonCoderAS Jul 19, 2024
d1e4818
Make open/saving work as expected
PythonCoderAS Jul 26, 2024
e5f0184
Merge remote-tracking branch 'origin/filesystem-ops' into add-wasm-fs
PythonCoderAS Jul 26, 2024
728f28a
Rename to make connection clearer
PythonCoderAS Jul 26, 2024
2428f9a
Fix rust functions validation
PythonCoderAS Jul 26, 2024
1a26527
Work on getting open folder/open project working
PythonCoderAS Aug 2, 2024
4b52b5f
Integrate Tauri with Wasm FS
PythonCoderAS Aug 6, 2024
f111d50
Merge remote-tracking branch 'origin/main' into add-wasm-fs
PythonCoderAS Aug 9, 2024
1190d91
Fix Tauri invoker bug (#103)
AleksBekker Aug 9, 2024
758a2fd
Merge remote-tracking branch 'origin/filesystem-ops' into add-wasm-fs
PythonCoderAS Aug 9, 2024
1224862
Fix parallelization problem
PythonCoderAS Aug 9, 2024
3996e98
Move FS to shared FS file
PythonCoderAS Aug 9, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"root": true,
"env": {
"browser": true,
"es2021": true
"es2021": true,
"es2023": true
},
"extends": [
"eslint:recommended",
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
14 changes: 10 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"wasm-build": "wasm-pack build -d ../../wasm --target web rezasm-app/rezasm-wasm/"
},
"dependencies": {
"@headlessui/react": "^2.0.4",
"@tauri-apps/api": "^1.4.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
Expand All @@ -30,19 +31,24 @@
"devDependencies": {
"@babel/core": "^7.22.17",
"@tauri-apps/cli": "^1.4.0",
"eslint-plugin-react": "^7.33.2",
"lodash": "^4.17.21",
"npm-run-all": "^4.1.5",
"tailwindcss": "^3.3.3",
"@types/react": "^18.2.66",
"@types/react-dom": "^18.2.22",
"@typescript-eslint/eslint-plugin": "^7.2.0",
"@typescript-eslint/parser": "^7.2.0",
"@vitejs/plugin-react": "^4.2.1",
"autoprefixer": "^10.4.19",
"eslint": "^8.57.0",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.6",
"lodash": "^4.17.21",
"npm-run-all": "^4.1.5",
"postcss": "^8.4.38",
"tailwindcss": "^3.4.4",
"typescript": "^5.2.2",
"vite": "^5.2.0"
},
"engines": {
"node": ">=20"
}
}
6 changes: 6 additions & 0 deletions postcss.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
2 changes: 1 addition & 1 deletion rezasm-app/rezasm-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ rezasm-web-core = { path = "../../rezasm-source/rezasm-web-core" }
lazy_static = "1.4.0"
serde = { version = "1.0.188", features = ["derive"] }
serde_json = "1.0.105"
tauri = { version = "1.4.1", features = ["shell-open"] }
tauri = { version = "1.4.1", features = [ "fs-all", "dialog-open", "shell-open"] }

# this feature is used for production builds or when `devPath` points to the filesystem
# DO NOT REMOVE!!
Expand Down
49 changes: 49 additions & 0 deletions rezasm-app/rezasm-tauri/src/file_system.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use std::fs;

extern crate tauri;

/// Creates a Tauri command from a function that returns () OR an error
macro_rules! void_or_error_command {
($fn_name:ident, $wrapped_fn:expr, $( $arg_name:ident : $arg_type:ty ),*) => {
#[tauri::command]
pub fn $fn_name($( $arg_name : $arg_type),*) -> Result<(), String> {
$wrapped_fn($($arg_name), *).map_err(|err| err.to_string())?;
Ok(())
}
};
}

/// Creates a Tauri command from a function that returns the wrapped function's result OR an error
macro_rules! return_or_error_command {
($fn_name:ident, $wrapped_fn:expr, $return_type:ty, $( $arg_name:ident : $arg_type:ty ),*) => {
#[tauri::command]
pub fn $fn_name($( $arg_name : $arg_type),*) -> Result<$return_type, String> {
$wrapped_fn($($arg_name), *).map_err(|err| err.to_string())
}
};
}

return_or_error_command!(tauri_copy_file, fs::copy, u64, from: &str, to: &str);
return_or_error_command!(tauri_read_to_string, fs::read_to_string, String, path: &str);

void_or_error_command!(tauri_create_dir, fs::create_dir, path: &str);
void_or_error_command!(tauri_create_dir_with_parents, fs::create_dir_all, path: &str);
void_or_error_command!(tauri_create_file, fs::File::create, path: &str);
void_or_error_command!(tauri_remove_file, fs::remove_file, path: &str);
void_or_error_command!(tauri_rename, fs::rename, from: &str, to: &str);
void_or_error_command!(tauri_write_file, fs::write, path: &str, contents: &str);

// Can only delete empty directory
void_or_error_command!(tauri_remove_dir, fs::remove_dir, path: &str);

// Deletes all contents of a (potentially) non-empty directory
void_or_error_command!(tauri_remove_dir_recursive, fs::remove_dir_all, path: &str);

#[tauri::command]
pub fn tauri_read_dir(path: &str) -> Result<Vec<(String, bool)>, String> {
Ok(fs::read_dir(path)
.map_err(|err| err.to_string())?
.filter_map(|entry| entry.ok().map(|e| e.path()))
.filter_map(|path| Some((path.to_str()?.to_string(), path.is_dir())))
.collect())
}
70 changes: 57 additions & 13 deletions rezasm-app/rezasm-tauri/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

mod file_system;
mod tauri_reader;
mod tauri_writer;

Expand All @@ -18,6 +19,12 @@ use rezasm_web_core::{
use tauri::{Manager, Window};
use tauri_reader::TauriReader;

use crate::file_system::{
tauri_copy_file, tauri_create_dir, tauri_create_dir_with_parents, tauri_create_file,
tauri_read_dir, tauri_read_to_string, tauri_remove_dir, tauri_remove_dir_recursive,
tauri_remove_file, tauri_rename, tauri_write_file,
};

use crate::tauri_writer::TauriWriter;
use std::{
io::Write,
Expand All @@ -28,7 +35,7 @@ lazy_static! {
static ref WINDOW: Arc<RwLock<Option<Window>>> = Arc::new(RwLock::new(None));
}

pub const WINDOW_NAME: &'static str = "main";
pub const WINDOW_NAME: &str = "main";

pub fn get_window() -> Window {
WINDOW
Expand Down Expand Up @@ -113,20 +120,16 @@ fn tauri_get_word_size() -> usize {
fn tauri_receive_input(data: &str) {
let mut simulator = get_simulator_mut();
let reader = simulator.get_reader_mut();
reader.write(data.as_bytes()).unwrap();
reader.write(&[b'\n']).unwrap();
reader.write_all(data.as_bytes()).unwrap();
reader.write_all(&[b'\n']).unwrap();
}

fn main() {
register_instructions();
initialize_simulator(
Some(ReaderCell::new(TauriReader::new())),
Some(Box::new(TauriWriter::new())),
);
type Handler = dyn Fn(tauri::Invoke) + Send + Sync;

tauri::Builder::default()
.setup(|app| Ok(set_window(app.get_window(WINDOW_NAME).unwrap())))
.invoke_handler(tauri::generate_handler![
lazy_static::lazy_static! {
/// The tauri handler containing all file system methods
static ref EZASM_HANDLER: Box<Handler> =
Box::new(tauri::generate_handler![
tauri_load,
tauri_reset,
tauri_step,
Expand All @@ -141,7 +144,48 @@ fn main() {
tauri_get_memory_slice,
tauri_get_word_size,
tauri_receive_input,
])
]);
}

fn main() {
let handler: Box<Handler> = Box::new(tauri::generate_handler![
tauri_load,
tauri_reset,
tauri_step,
tauri_step_back,
tauri_stop,
tauri_is_completed,
tauri_get_exit_status,
tauri_get_register_value,
tauri_get_register_names,
tauri_get_register_values,
tauri_get_memory_bounds,
tauri_get_memory_slice,
tauri_get_word_size,
tauri_receive_input,
// File system
tauri_copy_file,
tauri_create_dir,
tauri_create_dir_with_parents,
tauri_create_file,
tauri_read_dir,
tauri_read_to_string,
tauri_remove_dir,
tauri_remove_dir_recursive,
tauri_remove_file,
tauri_rename,
tauri_write_file,
]);

register_instructions();
initialize_simulator(
Some(ReaderCell::new(TauriReader::new())),
Some(Box::new(TauriWriter::new())),
);

tauri::Builder::default()
.setup(|app| Ok(set_window(app.get_window(WINDOW_NAME).unwrap())))
.invoke_handler(handler)
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
11 changes: 11 additions & 0 deletions rezasm-app/rezasm-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"$schema": "https://raw.githubusercontent.com/tauri-apps/tauri/v1.0.5/tooling/cli/schema.json",
"build": {
"beforeDevCommand": "npm run dev",
"beforeBuildCommand": "npm run build",
Expand All @@ -19,6 +20,16 @@
},
"window": {
"all": false
},
"dialog": {
"open": true
},
"fs": {
"all": true,
"scope": [
"$APPLOCALDATA/",
"$APPLOCALDATA/*"
]
}
},
"bundle": {
Expand Down
8 changes: 3 additions & 5 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import {HashRouter, Route, Routes} from "react-router-dom";
import Code from "./components/Code.jsx";
import Code from "./components/Code.js";
import Home from "./components/Home.jsx";
import Downloads from "./components/Downloads.jsx";
import "../dist/output.css";
import "./styles.css";

const HOME_PATH = "/";
const CODE_PATH = "/code/";
const DOWNLOAD_PATH = "/downloads/";

function App() {
export default function App() {
return (
<HashRouter future={{ v7_startTransition: true }}>
<Routes>
Expand All @@ -20,6 +20,4 @@ function App() {
);
}

export default App;

export { HOME_PATH, CODE_PATH, DOWNLOAD_PATH };
118 changes: 118 additions & 0 deletions src/components/BrowserMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import {Menu, MenuButton, MenuItem, MenuItems, Transition} from "@headlessui/react";
import React, {PropsWithChildren} from "react";

export function MenuHeading(props: PropsWithChildren) {
return (
<MenuButton
className="inline-flex w-full justify-center gap-x-1.5 bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm hover:bg-gray-50">
{props.children}
</MenuButton>
);
}

export function MenuOption(props: PropsWithChildren<React.JSX.IntrinsicElements["a"]>) {
const {children, ...otherProps} = props;
return <MenuItem>
{({focus}) => (
<span
{...otherProps}
role="menuitem"
className={
(focus ? "bg-gray-100 text-gray-900" : "text-gray-700") +
" block px-4 py-2 text-sm cursor-pointer"
}
>
{children}
</span>
)}
</MenuItem>;
}

function MenuSection(props: PropsWithChildren) {
return <div className="menu-section">
{props.children}
</div>;
}

function SectionMenu(props: PropsWithChildren<{ heading: string }>) {
return <Menu as="div" className="relative inline-block text-left">
<div>
<MenuHeading>{props.heading}</MenuHeading>
</div>
<Transition
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<MenuItems
className="absolute z-10 mt-2 w-56 divide-y divide-gray-100 origin-top-right bg-white shadow-lg focus:outline-none">
{props.children}
</MenuItems>
</Transition>
</Menu>;
}

function FileMenu() {
return <SectionMenu heading="File">
<MenuSection>
<MenuOption>
Open Folder
</MenuOption>
<MenuOption>
Open File
</MenuOption>
</MenuSection>
<MenuSection>
<MenuOption>
Save
</MenuOption>
</MenuSection>
<MenuSection>
<MenuOption>
Export File
</MenuOption>
<MenuOption>
Export Folder
</MenuOption>
<MenuOption>
Export Project
</MenuOption>
</MenuSection>
</SectionMenu>;
}

function EditMenu() {
return <SectionMenu heading="Edit">
<MenuSection>
<MenuOption>
Undo
</MenuOption>
<MenuOption>
Redo
</MenuOption>
</MenuSection>
<MenuSection>
<MenuOption>
Cut
</MenuOption>
<MenuOption>
Copy
</MenuOption>
<MenuOption>
Paste
</MenuOption>
</MenuSection>
</SectionMenu>;
}

export default function BrowserMenu() {
return (
<header className="menu-bar px-4 space-x-1 flex">
<FileMenu/>
<EditMenu/>
</header>
);
}
Loading
Loading