Desktop build: settings and progress kept as files, savegames in a saves folder, system file dialog for loading
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1df97b4af2
commit
db521ed2ee
4 files changed
+413
-18
No files matched your search
Generated
+274
-1
@@ -26,6 +26,27 @@ version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"
|
||||
|
||||
[[package]]
|
||||
name = "block2"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5"
|
||||
dependencies = [
|
||||
"objc2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.25.2"
|
||||
@@ -68,11 +89,24 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dispatch2"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38"
|
||||
dependencies = [
|
||||
"bitflags 2.13.2",
|
||||
"block2",
|
||||
"libc",
|
||||
"objc2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dungeoncraft"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"macroquad",
|
||||
"rfd",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
@@ -119,6 +153,30 @@ dependencies = [
|
||||
"ttf-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
"pin-project-lite",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glam"
|
||||
version = "0.27.0"
|
||||
@@ -155,6 +213,17 @@ version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8dc09d2154098bbf4ef54eab6b117a3c24cbbffe94046b16ee6d6d4510584d3a"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"futures-util",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.191"
|
||||
@@ -167,6 +236,12 @@ version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d15f86d12da44cd8d4ec3c73984537eb7f4cadcb9776a575a0a8a05ca11005c"
|
||||
|
||||
[[package]]
|
||||
name = "macroquad"
|
||||
version = "0.4.16"
|
||||
@@ -258,19 +333,86 @@ dependencies = [
|
||||
"malloc_buf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08849bbd4767dfae9457696856ae1c84fe4e0281bbe4a7abff2d0e06fb7981f8"
|
||||
dependencies = [
|
||||
"objc2-encode",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-app-kit"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c"
|
||||
dependencies = [
|
||||
"bitflags 2.13.2",
|
||||
"block2",
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-foundation"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
|
||||
dependencies = [
|
||||
"bitflags 2.13.2",
|
||||
"dispatch2",
|
||||
"objc2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-encode"
|
||||
version = "4.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33"
|
||||
|
||||
[[package]]
|
||||
name = "objc2-foundation"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
|
||||
dependencies = [
|
||||
"bitflags 2.13.2",
|
||||
"objc2",
|
||||
"objc2-core-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
||||
|
||||
[[package]]
|
||||
name = "png"
|
||||
version = "0.17.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 1.3.2",
|
||||
"crc32fast",
|
||||
"fdeflate",
|
||||
"flate2",
|
||||
"miniz_oxide 0.8.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pollster"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.107"
|
||||
@@ -295,6 +437,42 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "raw-window-handle"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539"
|
||||
|
||||
[[package]]
|
||||
name = "rfd"
|
||||
version = "0.17.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20dafead71c16a34e1ff357ddefc8afc11e7d51d6d2b9fbd07eaa48e3e540220"
|
||||
dependencies = [
|
||||
"block2",
|
||||
"dispatch2",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"log",
|
||||
"objc2",
|
||||
"objc2-app-kit",
|
||||
"objc2-core-foundation",
|
||||
"objc2-foundation",
|
||||
"percent-encoding",
|
||||
"pollster",
|
||||
"raw-window-handle",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.229"
|
||||
@@ -344,6 +522,12 @@ version = "0.3.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "3.0.9"
|
||||
@@ -355,6 +539,15 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.54.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b52fd3255258d26fac7264ab5a76b53c13367136dc011d3d6ad4f8ed070396a3"
|
||||
dependencies = [
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ttf-parser"
|
||||
version = "0.25.1"
|
||||
@@ -370,6 +563,71 @@ version = "1.0.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.130"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a0181c14ddd6c7b8b0b0132a5f1ea2f32be0d893cd3ea97b6e3b28886b8a3d1c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"rustversion",
|
||||
"wasm-bindgen-macro",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-futures"
|
||||
version = "0.4.80"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e14e0327cb5368ad60ccb22d04a9827a693c95a926d8d125e9fa39e0baec501"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"tokio",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.130"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e931a02a39abdbf66966fc35902b5ed58344750a70885bed4ebb3d8eaf28ed9"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.130"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ca4b54280e455bd831e33a6b8e734baf432fe63fbc35c42ac14ce67a043a53a"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.130"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b1f9c3798725016c6ee5f2f57869ec0831e8bbc0ab0310a86e787e97f41389e"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7d179596ff5ec0f6de4db64d54011bde6289cc49603d3a6e4ce3f329207ab88"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
@@ -392,6 +650,21 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zlib-rs"
|
||||
version = "0.6.8"
|
||||
|
||||
@@ -17,3 +17,7 @@ codegen-units = 1
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 1
|
||||
|
||||
# desktop builds: system file dialog (xdg portal on Linux) and sound output
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
rfd = { version = "0.17", default-features = false, features = ["xdg-portal"] }
|
||||
+7
-3
@@ -1497,7 +1497,7 @@ async fn main() {
|
||||
MenuAction::Save | MenuAction::FileSave => {
|
||||
let name = save_file_name();
|
||||
platform::download(&name, app.autosave_json().as_bytes());
|
||||
app.set_status(i18n::trf("save.saved", &[name]), now);
|
||||
app.set_status(i18n::trf("save.saved", &[platform::saved_as(&name)]), now);
|
||||
}
|
||||
MenuAction::CloudSave(id, name) => {
|
||||
if cloud.save(id.as_deref(), &name, &app.autosave_json()) {
|
||||
@@ -1544,7 +1544,7 @@ async fn main() {
|
||||
MenuAction::CampaignSave => {
|
||||
let name = menu.campaign_doc.file_name();
|
||||
platform::download(&name, menu.campaign_doc.save().as_bytes());
|
||||
app.set_status(i18n::trf("campaign.saved", &[name]), now);
|
||||
app.set_status(i18n::trf("campaign.saved", &[platform::saved_as(&name)]), now);
|
||||
}
|
||||
MenuAction::CampaignEditName => {
|
||||
if let Some(value) = prompt_line(&i18n::tr("campaign.prompt_name"), &menu.campaign_doc.name, 60) {
|
||||
@@ -2349,7 +2349,11 @@ app.pending_briefing = briefing_of(&ed.doc, i);
|
||||
ui.show_help = false;
|
||||
r3d.invalidate();
|
||||
}
|
||||
editor::EdAction::SaveFile => platform::download(&ed.doc.file_name(), ed.doc.save().as_bytes()),
|
||||
editor::EdAction::SaveFile => {
|
||||
let name = ed.doc.file_name();
|
||||
platform::download(&name, ed.doc.save().as_bytes());
|
||||
ed.set_status(i18n::trf("campaign.saved", &[platform::saved_as(&name)]));
|
||||
}
|
||||
editor::EdAction::OpenFile => platform::open_file(),
|
||||
editor::EdAction::ServerSave => {
|
||||
if !cloud.available() {
|
||||
|
||||
+128
-14
@@ -1,7 +1,7 @@
|
||||
// Browser integration via our own miniquad JS plugin (www/dc_plugin.js):
|
||||
// persistent key/value storage (localStorage), a cache that is written to storage when the
|
||||
// page is hidden or closed, file download, file upload and HTTP requests.
|
||||
// Native builds use simple fallbacks.
|
||||
// Native builds keep the storage as files in the data directory (see imp below).
|
||||
|
||||
// Result of an HTTP request: status 0 means the service could not be reached
|
||||
pub struct HttpResult {
|
||||
@@ -113,6 +113,11 @@ mod imp {
|
||||
unsafe { dc_cache_set(key.as_ptr(), key.len() as u32, val.as_ptr(), val.len() as u32) }
|
||||
}
|
||||
|
||||
// where download() put a file, for the status line: the browser's download folder
|
||||
pub fn saved_as(name: &str) -> String {
|
||||
name.to_string()
|
||||
}
|
||||
|
||||
pub fn download(name: &str, data: &[u8]) {
|
||||
unsafe { dc_download(name.as_ptr(), name.len() as u32, data.as_ptr(), data.len() as u32) }
|
||||
}
|
||||
@@ -188,14 +193,84 @@ mod imp {
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
mod imp {
|
||||
use std::cell::RefCell;
|
||||
// Desktop builds: the storage keys are files in the data directory, savegames and
|
||||
// campaigns are written to its "saves" folder, the system file dialog opens files.
|
||||
use std::collections::HashMap;
|
||||
thread_local! {
|
||||
static STORE: RefCell<HashMap<String, String>> = RefCell::new(HashMap::new());
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Mutex;
|
||||
|
||||
static STORE: Mutex<Option<HashMap<String, String>>> = Mutex::new(None);
|
||||
static PICKED: Mutex<Option<Vec<u8>>> = Mutex::new(None);
|
||||
static PICKING: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
|
||||
|
||||
// DUNGEONCRAFT_DATA, else the usual per-user application data folder
|
||||
pub fn data_dir() -> PathBuf {
|
||||
let env = |k: &str| std::env::var_os(k).filter(|v| !v.is_empty()).map(PathBuf::from);
|
||||
if let Some(d) = env("DUNGEONCRAFT_DATA") {
|
||||
return d;
|
||||
}
|
||||
if cfg!(target_os = "windows") {
|
||||
if let Some(d) = env("APPDATA") {
|
||||
return d.join("DungeonCraft");
|
||||
}
|
||||
} else if cfg!(target_os = "macos") {
|
||||
if let Some(h) = env("HOME") {
|
||||
return h.join("Library/Application Support/DungeonCraft");
|
||||
}
|
||||
} else if let Some(d) = env("XDG_DATA_HOME").or_else(|| env("HOME").map(|h| h.join(".local/share"))) {
|
||||
return d.join("dungeoncraft");
|
||||
}
|
||||
PathBuf::from("dungeoncraft-data")
|
||||
}
|
||||
|
||||
pub fn saves_dir() -> PathBuf {
|
||||
data_dir().join("saves")
|
||||
}
|
||||
|
||||
// a storage key as a file name: only harmless characters
|
||||
pub(super) fn key_file(key: &str) -> String {
|
||||
let name: String = key.chars().map(|c| if c.is_ascii_alphanumeric() || matches!(c, '.' | '-' | '_') { c } else { '_' }).collect();
|
||||
if name.is_empty() || name.starts_with('.') {
|
||||
format!("_{}", name)
|
||||
} else {
|
||||
name
|
||||
}
|
||||
}
|
||||
|
||||
// writes through a temporary file, so a crash never leaves half a file
|
||||
fn write_file(path: &std::path::Path, data: &[u8]) -> std::io::Result<()> {
|
||||
if let Some(dir) = path.parent() {
|
||||
std::fs::create_dir_all(dir)?;
|
||||
}
|
||||
let tmp = path.with_extension("tmp");
|
||||
std::fs::write(&tmp, data)?;
|
||||
std::fs::rename(&tmp, path)
|
||||
}
|
||||
|
||||
pub fn storage_get(key: &str) -> Option<String> {
|
||||
STORE.with(|s| s.borrow().get(key).cloned())
|
||||
let mut store = STORE.lock().unwrap_or_else(|e| e.into_inner());
|
||||
let store = store.get_or_insert_with(HashMap::new);
|
||||
if let Some(v) = store.get(key) {
|
||||
return Some(v.clone());
|
||||
}
|
||||
let v = std::fs::read_to_string(data_dir().join("store").join(key_file(key))).ok()?;
|
||||
store.insert(key.to_string(), v.clone());
|
||||
Some(v)
|
||||
}
|
||||
|
||||
pub fn storage_set(key: &str, val: &str) {
|
||||
STORE.lock().unwrap_or_else(|e| e.into_inner()).get_or_insert_with(HashMap::new).insert(key.to_string(), val.to_string());
|
||||
let path = data_dir().join("store").join(key_file(key));
|
||||
if let Err(e) = write_file(&path, val.as_bytes()) {
|
||||
eprintln!("DungeonCraft: cannot write {}: {}", path.display(), e);
|
||||
}
|
||||
}
|
||||
|
||||
// there is no "page left" moment on the desktop: write at once
|
||||
pub fn cache_on_leave(key: &str, val: &str) {
|
||||
storage_set(key, val);
|
||||
}
|
||||
|
||||
pub fn prompt(_title: &str, _default: &str) -> Option<String> {
|
||||
None
|
||||
}
|
||||
@@ -208,19 +283,47 @@ mod imp {
|
||||
pub fn fetch_download_state() -> i32 {
|
||||
0
|
||||
}
|
||||
pub fn storage_set(key: &str, val: &str) {
|
||||
STORE.with(|s| s.borrow_mut().insert(key.to_string(), val.to_string()));
|
||||
}
|
||||
pub fn cache_on_leave(key: &str, val: &str) {
|
||||
storage_set(key, val);
|
||||
}
|
||||
|
||||
// a "download" lands in the saves folder
|
||||
pub fn download(name: &str, data: &[u8]) {
|
||||
let _ = std::fs::write(name, data);
|
||||
let path = saves_dir().join(key_file(name));
|
||||
if let Err(e) = write_file(&path, data) {
|
||||
eprintln!("DungeonCraft: cannot write {}: {}", path.display(), e);
|
||||
}
|
||||
pub fn open_file() {}
|
||||
}
|
||||
|
||||
// where download() put a file, for the status line
|
||||
pub fn saved_as(name: &str) -> String {
|
||||
saves_dir().join(key_file(name)).display().to_string()
|
||||
}
|
||||
|
||||
fn pick() {
|
||||
let dir = saves_dir();
|
||||
let _ = std::fs::create_dir_all(&dir);
|
||||
let file = rfd::FileDialog::new().set_directory(&dir).pick_file();
|
||||
if let Some(bytes) = file.and_then(|f| std::fs::read(f).ok()) {
|
||||
*PICKED.lock().unwrap_or_else(|e| e.into_inner()) = Some(bytes);
|
||||
}
|
||||
PICKING.store(false, std::sync::atomic::Ordering::SeqCst);
|
||||
}
|
||||
|
||||
// opens the system file dialog (one at a time); poll with take_file()
|
||||
pub fn open_file() {
|
||||
if PICKING.swap(true, std::sync::atomic::Ordering::SeqCst) {
|
||||
return;
|
||||
}
|
||||
// macOS dialogs must run on the main thread; elsewhere the game keeps running
|
||||
if cfg!(target_os = "macos") {
|
||||
pick();
|
||||
} else if std::thread::Builder::new().name("file dialog".into()).spawn(pick).is_err() {
|
||||
PICKING.store(false, std::sync::atomic::Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn take_file() -> Option<Vec<u8>> {
|
||||
None
|
||||
PICKED.lock().unwrap_or_else(|e| e.into_inner()).take()
|
||||
}
|
||||
|
||||
// no savegame service in native builds: every request fails like an unreachable server
|
||||
pub fn http(_method: &str, _url: &str, _body: &[u8]) -> u32 {
|
||||
0
|
||||
@@ -270,3 +373,14 @@ pub use imp::*;
|
||||
pub extern "C" fn dungeoncraft_crate_version() -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
#[cfg(all(test, not(target_arch = "wasm32")))]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn storage_keys_become_harmless_file_names() {
|
||||
assert_eq!(super::imp::key_file("dc.settings"), "dc.settings");
|
||||
assert_eq!(super::imp::key_file("../etc/passwd"), "_.._etc_passwd");
|
||||
assert_eq!(super::imp::key_file("a b/c"), "a_b_c");
|
||||
assert_eq!(super::imp::key_file(""), "_");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user