Initial scaffold: Sinatra map app with Tauri desktop shell

Sinatra backend serving a basic Leaflet map, wrapped in a Tauri
desktop shell that starts the same server in a native window.
This commit is contained in:
randy committed 2026-08-24 22:57:03 +02:00
commit 7329497e95
27 files changed
+5010

No files matched your search

+4495
View File
File diff suppressed because it is too large. Load diff
+22
View File
@@ -0,0 +1,22 @@
[package]
name = "maptool"
version = "0.1.0"
description = "Desktop shell for the MapTool mapping application"
edition = "2021"
rust-version = "1.77"
[lib]
name = "maptool_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
[build-dependencies]
tauri-build = { version = "2", features = [] }
[dependencies]
tauri = { version = "2", features = [] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
[[bin]]
name = "maptool"
path = "src/main.rs"
+3
View File
@@ -0,0 +1,3 @@
fn main() {
tauri_build::build()
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

+59
View File
@@ -0,0 +1,59 @@
#[cfg(not(debug_assertions))]
use std::process::{Child, Command};
#[cfg(not(debug_assertions))]
use std::sync::Mutex;
#[cfg(not(debug_assertions))]
use tauri::Manager;
#[cfg(not(debug_assertions))]
struct ServerProcess(Mutex<Option<Child>>);
pub fn run() {
let builder = tauri::Builder::default();
#[cfg(not(debug_assertions))]
let builder = builder.setup(|app| {
let child = spawn_server(&app.handle())
.expect("failed to start the Sinatra server (is Ruby/Bundler installed?)");
app.manage(ServerProcess(Mutex::new(Some(child))));
Ok(())
});
let app = builder
.build(tauri::generate_context!())
.expect("error while building the tauri application");
app.run(|_app_handle, event| {
#[cfg(not(debug_assertions))]
if let tauri::RunEvent::ExitRequested { .. } = event {
if let Some(state) = _app_handle.try_state::<ServerProcess>() {
if let Some(mut child) = state.0.lock().unwrap().take() {
let _ = child.kill();
}
}
}
});
}
// In development, `beforeDevCommand` (see tauri.conf.json) already starts the
// Sinatra server, so the window just points at it. In a packaged build there
// is no such hook, so the shell starts the server itself, next to the bundled
// resources, and stops it again when the window closes.
//
// NOTE: this assumes a Ruby + Bundler installation on the target machine.
// Producing a fully self-contained bundle (no external Ruby dependency)
// requires embedding a Ruby runtime as a sidecar (e.g. via tebako) — see
// docs/architecture.adoc for the open follow-up.
#[cfg(not(debug_assertions))]
fn spawn_server(app: &tauri::AppHandle) -> std::io::Result<Child> {
let resource_dir = app
.path()
.resource_dir()
.expect("failed to resolve the resource directory");
Command::new("bundle")
.args(["exec", "puma", "-C", "config/puma.rb"])
.current_dir(resource_dir)
.spawn()
}
+6
View File
@@ -0,0 +1,6 @@
// Prevents an additional console window on Windows in release builds.
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
fn main() {
maptool_lib::run();
}
+45
View File
@@ -0,0 +1,45 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "MapTool",
"version": "0.1.0",
"identifier": "net.priesol.maptool",
"build": {
"beforeDevCommand": "bundle exec puma -C ../config/puma.rb",
"devUrl": "http://127.0.0.1:4567",
"frontendDist": "http://127.0.0.1:4567"
},
"app": {
"windows": [
{
"title": "MapTool",
"width": 1200,
"height": 800,
"minWidth": 800,
"minHeight": 600
}
],
"security": {
"csp": null
}
},
"bundle": {
"active": true,
"targets": "all",
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
],
"resources": {
"../Gemfile": "Gemfile",
"../Gemfile.lock": "Gemfile.lock",
"../app.rb": "app.rb",
"../config.ru": "config.ru",
"../config/*": "config/",
"../public/*": "public/",
"../views/*": "views/"
}
}
}