Make app fully portable, remove installers
- Remove MSI and NSIS installer targets, only produce vesper.exe - Remove tauri-plugin-window-state, replace with manual window state save/restore to data/window-state.json next to the exe - Redirect WebView2 user data (including localStorage) to data/ folder next to the exe via WEBVIEW2_USER_DATA_DIR - Nothing written to AppData, registry, or any system location - Update README with portable usage info
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -19,6 +19,9 @@ src-tauri/target/
|
|||||||
icon-*.html
|
icon-*.html
|
||||||
nul
|
nul
|
||||||
|
|
||||||
|
# Portable data directory
|
||||||
|
data/
|
||||||
|
|
||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
.vscode/*
|
.vscode/*
|
||||||
!.vscode/extensions.json
|
!.vscode/extensions.json
|
||||||
|
|||||||
25
README.md
25
README.md
@@ -60,7 +60,7 @@ A beautiful, distraction-free markdown reader for Windows. Vesper renders your m
|
|||||||
- **UI scale** (50%--200%) with a spinner in the View menu, persisted across sessions via localStorage
|
- **UI scale** (50%--200%) with a spinner in the View menu, persisted across sessions via localStorage
|
||||||
- **Kinetic scrolling** on right-mouse-button drag with iOS-style rubber band overscroll and damped spring snapback
|
- **Kinetic scrolling** on right-mouse-button drag with iOS-style rubber band overscroll and damped spring snapback
|
||||||
- **Custom scrollbars** via OverlayScrollbars -- thin, auto-hiding after 800ms, accent-colored on hover
|
- **Custom scrollbars** via OverlayScrollbars -- thin, auto-hiding after 800ms, accent-colored on hover
|
||||||
- Window state (size, position, maximized) remembered between sessions via `tauri-plugin-window-state`
|
- Window state (size, position, maximized) remembered between sessions
|
||||||
|
|
||||||
### File Handling
|
### File Handling
|
||||||
|
|
||||||
@@ -107,11 +107,7 @@ A beautiful, distraction-free markdown reader for Windows. Vesper renders your m
|
|||||||
|
|
||||||
### Download
|
### Download
|
||||||
|
|
||||||
Download the latest installer from the [Releases](https://github.com/yourusername/vesper/releases) page:
|
Download the latest `vesper.exe` from the [Releases](https://github.com/yourusername/vesper/releases) page. No installation required -- Vesper is fully portable.
|
||||||
|
|
||||||
- **NSIS installer** (`.exe`) -- recommended, includes uninstaller
|
|
||||||
- **MSI installer** (`.msi`) -- alternative for enterprise/group policy deployment
|
|
||||||
- **Portable exe** -- standalone `vesper.exe` with no installation required
|
|
||||||
|
|
||||||
### Build from Source
|
### Build from Source
|
||||||
|
|
||||||
@@ -135,10 +131,7 @@ npm run tauri dev
|
|||||||
npm run tauri build
|
npm run tauri build
|
||||||
```
|
```
|
||||||
|
|
||||||
Build outputs:
|
Build output: `src-tauri/target/release/vesper.exe`
|
||||||
- `src-tauri/target/release/vesper.exe` -- portable executable
|
|
||||||
- `src-tauri/target/release/bundle/msi/Vesper_1.0.0_x64_en-US.msi` -- MSI installer
|
|
||||||
- `src-tauri/target/release/bundle/nsis/Vesper_1.0.0_x64-setup.exe` -- NSIS installer
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -160,11 +153,21 @@ Build outputs:
|
|||||||
|
|
||||||
- `tauri-plugin-dialog` -- native file open dialog
|
- `tauri-plugin-dialog` -- native file open dialog
|
||||||
- `tauri-plugin-fs` -- file system read access
|
- `tauri-plugin-fs` -- file system read access
|
||||||
- `tauri-plugin-window-state` -- persist and restore window size, position, and maximized state
|
|
||||||
- `tauri-plugin-opener` -- system default app launcher
|
- `tauri-plugin-opener` -- system default app launcher
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Portable
|
||||||
|
|
||||||
|
Vesper is fully portable. It stores all data in a `data/` folder next to the executable:
|
||||||
|
|
||||||
|
- `data/window-state.json` -- window position, size, and maximized state
|
||||||
|
- `data/EBWebView/` -- WebView2 data including localStorage (UI zoom preference)
|
||||||
|
|
||||||
|
Nothing is written to AppData, the registry, or any other system location. To move Vesper to another machine, just copy the exe (and optionally the `data/` folder to preserve settings).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Design Philosophy
|
## Design Philosophy
|
||||||
|
|
||||||
1. **Content First** -- the reader is the primary focus; all UI chrome can be hidden
|
1. **Content First** -- the reader is the primary focus; all UI chrome can be hidden
|
||||||
|
|||||||
10
package-lock.json
generated
10
package-lock.json
generated
@@ -16,7 +16,6 @@
|
|||||||
"@tauri-apps/plugin-dialog": "^2.6.0",
|
"@tauri-apps/plugin-dialog": "^2.6.0",
|
||||||
"@tauri-apps/plugin-fs": "^2.4.5",
|
"@tauri-apps/plugin-fs": "^2.4.5",
|
||||||
"@tauri-apps/plugin-opener": "^2",
|
"@tauri-apps/plugin-opener": "^2",
|
||||||
"@tauri-apps/plugin-window-state": "^2.4.1",
|
|
||||||
"daisyui": "^5.5.18",
|
"daisyui": "^5.5.18",
|
||||||
"framer-motion": "^12.34.0",
|
"framer-motion": "^12.34.0",
|
||||||
"highlight.js": "^11.11.1",
|
"highlight.js": "^11.11.1",
|
||||||
@@ -2196,15 +2195,6 @@
|
|||||||
"@tauri-apps/api": "^2.8.0"
|
"@tauri-apps/api": "^2.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tauri-apps/plugin-window-state": {
|
|
||||||
"version": "2.4.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-window-state/-/plugin-window-state-2.4.1.tgz",
|
|
||||||
"integrity": "sha512-OuvdrzyY8Q5Dbzpj+GcrnV1iCeoZbcFdzMjanZMMcAEUNy/6PH5pxZPXpaZLOR7whlzXiuzx0L9EKZbH7zpdRw==",
|
|
||||||
"license": "MIT OR Apache-2.0",
|
|
||||||
"dependencies": {
|
|
||||||
"@tauri-apps/api": "^2.8.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@tootallnate/quickjs-emscripten": {
|
"node_modules/@tootallnate/quickjs-emscripten": {
|
||||||
"version": "0.23.0",
|
"version": "0.23.0",
|
||||||
"resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz",
|
"resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz",
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{"name":"vesper","private":true,"version":"1.0.0","type":"module","scripts":{"dev":"vite","build":"tsc && vite build","preview":"vite preview","tauri":"tauri"},"dependencies":{"@fontsource-variable/inter":"^5.2.8","@fontsource-variable/jetbrains-mono":"^5.2.8","@tailwindcss/typography":"^0.5.19","@tailwindcss/vite":"^4.1.18","@tauri-apps/api":"^2","@tauri-apps/plugin-dialog":"^2.6.0","@tauri-apps/plugin-fs":"^2.4.5","@tauri-apps/plugin-opener":"^2","@tauri-apps/plugin-window-state":"^2.4.1","daisyui":"^5.5.18","framer-motion":"^12.34.0","highlight.js":"^11.11.1","lucide-react":"^0.564.0","markdown-it":"^14.1.1","markdown-it-mark":"^4.0.0","markdown-it-sub":"^2.0.0","markdown-it-sup":"^2.0.0","markdown-it-task-lists":"^2.1.1","overlayscrollbars":"^2.14.0","overlayscrollbars-react":"^0.5.6","react":"^19.1.0","react-dom":"^19.1.0","tailwindcss":"^4.1.18"},"devDependencies":{"@tauri-apps/cli":"^2","@types/markdown-it":"^14.1.2","@types/react":"^19.1.8","@types/react-dom":"^19.1.6","@vitejs/plugin-react":"^4.6.0","png-to-ico":"^3.0.1","puppeteer-core":"^24.37.3","sharp":"^0.34.5","typescript":"~5.8.3","vite":"^7.0.4"}}
|
{"name":"vesper","private":true,"version":"1.0.0","type":"module","scripts":{"dev":"vite","build":"tsc && vite build","preview":"vite preview","tauri":"tauri"},"dependencies":{"@fontsource-variable/inter":"^5.2.8","@fontsource-variable/jetbrains-mono":"^5.2.8","@tailwindcss/typography":"^0.5.19","@tailwindcss/vite":"^4.1.18","@tauri-apps/api":"^2","@tauri-apps/plugin-dialog":"^2.6.0","@tauri-apps/plugin-fs":"^2.4.5","@tauri-apps/plugin-opener":"^2","daisyui":"^5.5.18","framer-motion":"^12.34.0","highlight.js":"^11.11.1","lucide-react":"^0.564.0","markdown-it":"^14.1.1","markdown-it-mark":"^4.0.0","markdown-it-sub":"^2.0.0","markdown-it-sup":"^2.0.0","markdown-it-task-lists":"^2.1.1","overlayscrollbars":"^2.14.0","overlayscrollbars-react":"^0.5.6","react":"^19.1.0","react-dom":"^19.1.0","tailwindcss":"^4.1.18"},"devDependencies":{"@tauri-apps/cli":"^2","@types/markdown-it":"^14.1.2","@types/react":"^19.1.8","@types/react-dom":"^19.1.6","@vitejs/plugin-react":"^4.6.0","png-to-ico":"^3.0.1","puppeteer-core":"^24.37.3","sharp":"^0.34.5","typescript":"~5.8.3","vite":"^7.0.4"}}
|
||||||
16
src-tauri/Cargo.lock
generated
16
src-tauri/Cargo.lock
generated
@@ -3703,21 +3703,6 @@ dependencies = [
|
|||||||
"zbus",
|
"zbus",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tauri-plugin-window-state"
|
|
||||||
version = "2.4.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "73736611e14142408d15353e21e3cca2f12a3cfb523ad0ce85999b6d2ef1a704"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags 2.10.0",
|
|
||||||
"log",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"tauri",
|
|
||||||
"tauri-plugin",
|
|
||||||
"thiserror 2.0.18",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-runtime"
|
name = "tauri-runtime"
|
||||||
version = "2.10.0"
|
version = "2.10.0"
|
||||||
@@ -4305,7 +4290,6 @@ dependencies = [
|
|||||||
"tauri-plugin-dialog",
|
"tauri-plugin-dialog",
|
||||||
"tauri-plugin-fs",
|
"tauri-plugin-fs",
|
||||||
"tauri-plugin-opener",
|
"tauri-plugin-opener",
|
||||||
"tauri-plugin-window-state",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ tauri = { version = "2", features = [] }
|
|||||||
tauri-plugin-opener = "2"
|
tauri-plugin-opener = "2"
|
||||||
tauri-plugin-dialog = "2"
|
tauri-plugin-dialog = "2"
|
||||||
tauri-plugin-fs = "2"
|
tauri-plugin-fs = "2"
|
||||||
tauri-plugin-window-state = "2"
|
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
|
|
||||||
|
|||||||
@@ -13,8 +13,6 @@
|
|||||||
"core:window:allow-start-dragging",
|
"core:window:allow-start-dragging",
|
||||||
"fs:default",
|
"fs:default",
|
||||||
"fs:allow-read-text-file",
|
"fs:allow-read-text-file",
|
||||||
"fs:read-all",
|
"fs:read-all"
|
||||||
"window-state:allow-restore-state",
|
|
||||||
"window-state:allow-save-window-state"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,94 @@
|
|||||||
|
use std::env;
|
||||||
|
use std::fs;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tauri::Manager;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Default)]
|
||||||
|
struct WindowState {
|
||||||
|
x: Option<i32>,
|
||||||
|
y: Option<i32>,
|
||||||
|
width: Option<u32>,
|
||||||
|
height: Option<u32>,
|
||||||
|
maximized: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_data_dir() -> PathBuf {
|
||||||
|
env::current_exe()
|
||||||
|
.ok()
|
||||||
|
.and_then(|p| p.parent().map(|p| p.to_path_buf()))
|
||||||
|
.unwrap_or_else(|| PathBuf::from("."))
|
||||||
|
.join("data")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_window_state() -> WindowState {
|
||||||
|
let path = get_data_dir().join("window-state.json");
|
||||||
|
fs::read_to_string(&path)
|
||||||
|
.ok()
|
||||||
|
.and_then(|s| serde_json::from_str(&s).ok())
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn save_window_state(state: &WindowState) {
|
||||||
|
let dir = get_data_dir();
|
||||||
|
let _ = fs::create_dir_all(&dir);
|
||||||
|
let path = dir.join("window-state.json");
|
||||||
|
if let Ok(json) = serde_json::to_string_pretty(state) {
|
||||||
|
let _ = fs::write(path, json);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
|
let data_dir = get_data_dir();
|
||||||
|
let _ = fs::create_dir_all(&data_dir);
|
||||||
|
|
||||||
|
// Redirect WebView2 user data to portable location next to the exe
|
||||||
|
env::set_var(
|
||||||
|
"WEBVIEW2_USER_DATA_DIR",
|
||||||
|
data_dir.to_string_lossy().to_string(),
|
||||||
|
);
|
||||||
|
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
.plugin(tauri_plugin_opener::init())
|
.plugin(tauri_plugin_opener::init())
|
||||||
.plugin(tauri_plugin_dialog::init())
|
.plugin(tauri_plugin_dialog::init())
|
||||||
.plugin(tauri_plugin_fs::init())
|
.plugin(tauri_plugin_fs::init())
|
||||||
.plugin(tauri_plugin_window_state::Builder::new().build())
|
.setup(|app| {
|
||||||
|
let state = load_window_state();
|
||||||
|
let window = app.get_webview_window("main").unwrap();
|
||||||
|
|
||||||
|
if let (Some(x), Some(y)) = (state.x, state.y) {
|
||||||
|
let _ = window.set_position(tauri::Position::Physical(
|
||||||
|
tauri::PhysicalPosition::new(x, y),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if let (Some(w), Some(h)) = (state.width, state.height) {
|
||||||
|
let _ = window.set_size(tauri::Size::Physical(
|
||||||
|
tauri::PhysicalSize::new(w, h),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if let Some(true) = state.maximized {
|
||||||
|
let _ = window.maximize();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.on_window_event(|window, event| {
|
||||||
|
if let tauri::WindowEvent::CloseRequested { .. } = event {
|
||||||
|
let maximized = window.is_maximized().unwrap_or(false);
|
||||||
|
if let (Ok(pos), Ok(size)) = (window.outer_position(), window.outer_size()) {
|
||||||
|
let state = WindowState {
|
||||||
|
x: Some(pos.x),
|
||||||
|
y: Some(pos.y),
|
||||||
|
width: Some(size.width),
|
||||||
|
height: Some(size.height),
|
||||||
|
maximized: Some(maximized),
|
||||||
|
};
|
||||||
|
save_window_state(&state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
},
|
},
|
||||||
"bundle": {
|
"bundle": {
|
||||||
"active": true,
|
"active": true,
|
||||||
"targets": "all",
|
"targets": [],
|
||||||
"icon": [
|
"icon": [
|
||||||
"icons/32x32.png",
|
"icons/32x32.png",
|
||||||
"icons/128x128.png",
|
"icons/128x128.png",
|
||||||
|
|||||||
Reference in New Issue
Block a user