derive serde on all persisted entities; add Settings.version and Dashboard::blank
Store and serde derives coexist on all 23 dashboard types (prototype confirmed). Settings carries the saved-format version (1) as the migration hook anchor; Dashboard::blank() is the post-Clear empty campaign. mock() now builds Settings via Default.
This commit is contained in:
parent
a11e86e839
commit
f3b0ceb3e7
@ -45,9 +45,7 @@ impl Dashboard {
|
|||||||
quest_book: generate_mock_questbook(),
|
quest_book: generate_mock_questbook(),
|
||||||
notes: generate_mock_notebook(),
|
notes: generate_mock_notebook(),
|
||||||
people: generate_mock_contact_book(),
|
people: generate_mock_contact_book(),
|
||||||
settings: Settings {
|
settings: Settings::default(),
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,12 +9,14 @@
|
|||||||
//!
|
//!
|
||||||
//! The model is deliberately "wide": it covers the campaign header, the
|
//! The model is deliberately "wide": it covers the campaign header, the
|
||||||
//! player character, the party's common stash, quests, notes, contacts and
|
//! player character, the party's common stash, quests, notes, contacts and
|
||||||
//! settings. All of it currently comes from hardcoded mock data (the `mock`
|
//! settings. `mock` provides demo data, `Dashboard::blank` an empty slate;
|
||||||
//! module); nothing is persisted yet (see ROADMAP phase 1).
|
//! persistence (localStorage autosave, JSON export/import) lives in
|
||||||
|
//! `crate::state::persist`.
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
|
||||||
use chrono::{ NaiveDate, NaiveDateTime };
|
use chrono::{ NaiveDate, NaiveDateTime };
|
||||||
use reactive_stores::Store;
|
use reactive_stores::Store;
|
||||||
|
use serde::{ Deserialize, Serialize };
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
@ -66,7 +68,7 @@ pub struct ItemReferenceMap (pub HashMap<String, BookItem>);
|
|||||||
/// party's common inventory, quests, notes, contacts and settings. The
|
/// party's common inventory, quests, notes, contacts and settings. The
|
||||||
/// whole tree derives `Store`, so every nested field is independently
|
/// whole tree derives `Store`, so every nested field is independently
|
||||||
/// reactive. `Dashboard::mock()` provides the initial state.
|
/// reactive. `Dashboard::mock()` provides the initial state.
|
||||||
#[derive(Clone, Debug, Store)]
|
#[derive(Clone, Debug, Store, Serialize, Deserialize)]
|
||||||
pub struct Dashboard {
|
pub struct Dashboard {
|
||||||
pub campaign: String,
|
pub campaign: String,
|
||||||
pub campaign_image: String,
|
pub campaign_image: String,
|
||||||
@ -80,10 +82,49 @@ pub struct Dashboard {
|
|||||||
pub settings: Settings
|
pub settings: Settings
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Dashboard {
|
||||||
|
/// An empty dashboard: the blank slate after Clear.
|
||||||
|
///
|
||||||
|
/// No campaign name, no players, empty books, zeroed numbers.
|
||||||
|
pub fn blank () -> Self {
|
||||||
|
Self {
|
||||||
|
campaign: String::new(),
|
||||||
|
campaign_image: String::new(),
|
||||||
|
session: 0,
|
||||||
|
date: chrono::Local::now().date_naive(),
|
||||||
|
player: PlayerData {
|
||||||
|
name: Name::default(),
|
||||||
|
class: String::new(),
|
||||||
|
image: String::new(),
|
||||||
|
level: 0,
|
||||||
|
xp: 0,
|
||||||
|
temp_hp: 0,
|
||||||
|
hp: 0,
|
||||||
|
max_hp: 0,
|
||||||
|
balance: Balance::default(),
|
||||||
|
spell_slots: SpellSlots (core::array::from_fn(|_| SpellSlotLevel { used: 0, total: 0 })),
|
||||||
|
inventory: Inventory::default(),
|
||||||
|
attunements: Attunements (core::array::from_fn(|_| None)),
|
||||||
|
prepared: PreparedSpells (Vec::new()),
|
||||||
|
hit_dice: HitDice { used: 0, kind: 8 },
|
||||||
|
death_save_throws: DeathSaveThrows { failed: 0, succeeded: 0 },
|
||||||
|
},
|
||||||
|
common: CommonData {
|
||||||
|
balance: Balance::default(),
|
||||||
|
inventory: Inventory::default(),
|
||||||
|
},
|
||||||
|
quest_book: QuestBook (Vec::new()),
|
||||||
|
notes: NoteBook (Vec::new()),
|
||||||
|
people: ContactBook (HashMap::new()),
|
||||||
|
settings: Settings::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The player character: identity, class, level, XP, hit points, money,
|
/// The player character: identity, class, level, XP, hit points, money,
|
||||||
/// spell slots, inventory, attunements, prepared spells, hit dice and
|
/// spell slots, inventory, attunements, prepared spells, hit dice and
|
||||||
/// death saves. This is the heart of the character sheet.
|
/// death saves. This is the heart of the character sheet.
|
||||||
#[derive(Clone, Debug, Store)]
|
#[derive(Clone, Debug, Store, Serialize, Deserialize)]
|
||||||
pub struct PlayerData {
|
pub struct PlayerData {
|
||||||
pub name: Name,
|
pub name: Name,
|
||||||
pub class: String,
|
pub class: String,
|
||||||
@ -105,7 +146,7 @@ pub struct PlayerData {
|
|||||||
|
|
||||||
|
|
||||||
/// The party's shared stash: money and items usable by everyone.
|
/// The party's shared stash: money and items usable by everyone.
|
||||||
#[derive(Clone, Debug, Store)]
|
#[derive(Clone, Debug, Store, Serialize, Deserialize)]
|
||||||
pub struct CommonData {
|
pub struct CommonData {
|
||||||
pub balance: Balance,
|
pub balance: Balance,
|
||||||
pub inventory: Inventory,
|
pub inventory: Inventory,
|
||||||
@ -113,7 +154,7 @@ pub struct CommonData {
|
|||||||
|
|
||||||
/// Currency counts per denomination, platinum through copper.
|
/// Currency counts per denomination, platinum through copper.
|
||||||
/// Edited with `Numput` on the inventories page.
|
/// Edited with `Numput` on the inventories page.
|
||||||
#[derive(Clone, Debug, Store)]
|
#[derive(Clone, Debug, Store, Serialize, Deserialize, Default)]
|
||||||
pub struct Balance {
|
pub struct Balance {
|
||||||
pub platinum: u32,
|
pub platinum: u32,
|
||||||
pub gold: u32,
|
pub gold: u32,
|
||||||
@ -126,14 +167,14 @@ pub struct Balance {
|
|||||||
///
|
///
|
||||||
/// `#[store(key)]` gives every entry a stable reactive key (its `id`),
|
/// `#[store(key)]` gives every entry a stable reactive key (its `id`),
|
||||||
/// which keeps `For`/`ForEnumerate` updates cheap.
|
/// which keeps `For`/`ForEnumerate` updates cheap.
|
||||||
#[derive(Clone, Debug, Store)]
|
#[derive(Clone, Debug, Store, Serialize, Deserialize, Default)]
|
||||||
pub struct Inventory {
|
pub struct Inventory {
|
||||||
#[store(key: u64 = |row| row.id)]
|
#[store(key: u64 = |row| row.id)]
|
||||||
pub rows: Vec<InventoryEntry>
|
pub rows: Vec<InventoryEntry>
|
||||||
}
|
}
|
||||||
|
|
||||||
/// One inventory row: a quantity of a single item.
|
/// One inventory row: a quantity of a single item.
|
||||||
#[derive(Clone, Debug, Store, Default)]
|
#[derive(Clone, Debug, Store, Serialize, Deserialize, Default)]
|
||||||
pub struct InventoryEntry {
|
pub struct InventoryEntry {
|
||||||
pub id: u64,
|
pub id: u64,
|
||||||
pub quantity: u32,
|
pub quantity: u32,
|
||||||
@ -155,7 +196,7 @@ impl InventoryEntry {
|
|||||||
|
|
||||||
/// An item inside an inventory: name, weight (basis points), optional
|
/// An item inside an inventory: name, weight (basis points), optional
|
||||||
/// description, and free-form tags (e.g. `type`, `rarity`).
|
/// description, and free-form tags (e.g. `type`, `rarity`).
|
||||||
#[derive(Clone, Debug, Store, Default)]
|
#[derive(Clone, Debug, Store, Serialize, Deserialize, Default)]
|
||||||
pub struct InventoryItem {
|
pub struct InventoryItem {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub weight: u32,
|
pub weight: u32,
|
||||||
@ -164,12 +205,12 @@ pub struct InventoryItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The party's quest list. No UI yet — see ROADMAP phase 4.
|
/// The party's quest list. No UI yet — see ROADMAP phase 4.
|
||||||
#[derive(Clone, Debug, Store)]
|
#[derive(Clone, Debug, Store, Serialize, Deserialize)]
|
||||||
pub struct QuestBook (pub Vec<Quest>);
|
pub struct QuestBook (pub Vec<Quest>);
|
||||||
|
|
||||||
/// One quest: title, locations, dates, description, giver and reward.
|
/// One quest: title, locations, dates, description, giver and reward.
|
||||||
/// Note: there is no completion state yet (ROADMAP phase 4 adds one).
|
/// Note: there is no completion state yet (ROADMAP phase 4 adds one).
|
||||||
#[derive(Clone, Debug, Store)]
|
#[derive(Clone, Debug, Store, Serialize, Deserialize)]
|
||||||
pub struct Quest {
|
pub struct Quest {
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub location_taken: Option<String>,
|
pub location_taken: Option<String>,
|
||||||
@ -182,12 +223,12 @@ pub struct Quest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The party's session notes. No UI yet — see ROADMAP phase 5.
|
/// The party's session notes. No UI yet — see ROADMAP phase 5.
|
||||||
#[derive(Clone, Debug, Store)]
|
#[derive(Clone, Debug, Store, Serialize, Deserialize)]
|
||||||
pub struct NoteBook (pub Vec<Note>);
|
pub struct NoteBook (pub Vec<Note>);
|
||||||
|
|
||||||
/// One note: content, in-game date, real-world timestamp, session number
|
/// One note: content, in-game date, real-world timestamp, session number
|
||||||
/// and tags.
|
/// and tags.
|
||||||
#[derive(Clone, Debug, Store)]
|
#[derive(Clone, Debug, Store, Serialize, Deserialize)]
|
||||||
pub struct Note {
|
pub struct Note {
|
||||||
pub content: String,
|
pub content: String,
|
||||||
pub date_ingame: NaiveDate,
|
pub date_ingame: NaiveDate,
|
||||||
@ -197,28 +238,28 @@ pub struct Note {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Spell slots for the nine spell levels (index 0 = 1st level).
|
/// Spell slots for the nine spell levels (index 0 = 1st level).
|
||||||
#[derive(Clone, Debug, Store)]
|
#[derive(Clone, Debug, Store, Serialize, Deserialize)]
|
||||||
pub struct SpellSlots (pub [SpellSlotLevel; 9]);
|
pub struct SpellSlots (pub [SpellSlotLevel; 9]);
|
||||||
|
|
||||||
/// Used/total slots for one spell level.
|
/// Used/total slots for one spell level.
|
||||||
#[derive(Clone, Debug, Store)]
|
#[derive(Clone, Debug, Store, Serialize, Deserialize)]
|
||||||
pub struct SpellSlotLevel {
|
pub struct SpellSlotLevel {
|
||||||
pub used: u8,
|
pub used: u8,
|
||||||
pub total: u8,
|
pub total: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Three attunement slots (D&D rules allow three attuned items).
|
/// Three attunement slots (D&D rules allow three attuned items).
|
||||||
#[derive(Clone, Debug, Store)]
|
#[derive(Clone, Debug, Store, Serialize, Deserialize)]
|
||||||
pub struct Attunements (pub [Option<String>; 3]);
|
pub struct Attunements (pub [Option<String>; 3]);
|
||||||
|
|
||||||
|
|
||||||
/// The spell list the character has prepared (or is learning).
|
/// The spell list the character has prepared (or is learning).
|
||||||
#[derive(Clone, Debug, Store)]
|
#[derive(Clone, Debug, Store, Serialize, Deserialize)]
|
||||||
pub struct PreparedSpells (pub Vec<PreparedSpell>);
|
pub struct PreparedSpells (pub Vec<PreparedSpell>);
|
||||||
|
|
||||||
|
|
||||||
/// One prepared-spell slot: vacant or holding a spell.
|
/// One prepared-spell slot: vacant or holding a spell.
|
||||||
#[derive(Clone, Debug, Store)]
|
#[derive(Clone, Debug, Store, Serialize, Deserialize)]
|
||||||
pub enum PreparedSpell {
|
pub enum PreparedSpell {
|
||||||
Vacant,
|
Vacant,
|
||||||
Occupied (Spell)
|
Occupied (Spell)
|
||||||
@ -227,13 +268,13 @@ pub enum PreparedSpell {
|
|||||||
/// A spell. Only the name exists so far; the rest is a `@TODO`
|
/// A spell. Only the name exists so far; the rest is a `@TODO`
|
||||||
/// (see ROADMAP phase 2).
|
/// (see ROADMAP phase 2).
|
||||||
// @TODO
|
// @TODO
|
||||||
#[derive(Clone, Debug, Store)]
|
#[derive(Clone, Debug, Store, Serialize, Deserialize)]
|
||||||
pub struct Spell {
|
pub struct Spell {
|
||||||
pub name: String
|
pub name: String
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Hit dice: how many of the current die kind are already spent.
|
/// Hit dice: how many of the current die kind are already spent.
|
||||||
#[derive(Clone, Debug, Store)]
|
#[derive(Clone, Debug, Store, Serialize, Deserialize)]
|
||||||
pub struct HitDice {
|
pub struct HitDice {
|
||||||
pub used: u8,
|
pub used: u8,
|
||||||
pub kind: u8
|
pub kind: u8
|
||||||
@ -242,7 +283,7 @@ pub struct HitDice {
|
|||||||
|
|
||||||
/// Death-saving-throw counters: three failures kill, three successes
|
/// Death-saving-throw counters: three failures kill, three successes
|
||||||
/// stabilize.
|
/// stabilize.
|
||||||
#[derive(Clone, Debug, Store)]
|
#[derive(Clone, Debug, Store, Serialize, Deserialize)]
|
||||||
pub struct DeathSaveThrows {
|
pub struct DeathSaveThrows {
|
||||||
pub failed: u8,
|
pub failed: u8,
|
||||||
pub succeeded: u8,
|
pub succeeded: u8,
|
||||||
@ -257,11 +298,11 @@ impl DeathSaveThrows {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Known NPCs keyed by name. No UI yet — see ROADMAP phase 6.
|
/// Known NPCs keyed by name. No UI yet — see ROADMAP phase 6.
|
||||||
#[derive(Clone, Debug, Store)]
|
#[derive(Clone, Debug, Store, Serialize, Deserialize)]
|
||||||
pub struct ContactBook (pub HashMap<String, Contact>);
|
pub struct ContactBook (pub HashMap<String, Contact>);
|
||||||
|
|
||||||
/// One NPC contact: name, optional image, status, location and notes.
|
/// One NPC contact: name, optional image, status, location and notes.
|
||||||
#[derive(Clone, Debug, Store)]
|
#[derive(Clone, Debug, Store, Serialize, Deserialize)]
|
||||||
pub struct Contact {
|
pub struct Contact {
|
||||||
pub name: Name,
|
pub name: Name,
|
||||||
pub image: Option<String>,
|
pub image: Option<String>,
|
||||||
@ -271,7 +312,7 @@ pub struct Contact {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Life status of a contact, used for filtering.
|
/// Life status of a contact, used for filtering.
|
||||||
#[derive(Clone, Debug, Store)]
|
#[derive(Clone, Debug, Store, Serialize, Deserialize)]
|
||||||
pub enum ContactStatus {
|
pub enum ContactStatus {
|
||||||
Unknown,
|
Unknown,
|
||||||
DeadBygone,
|
DeadBygone,
|
||||||
@ -280,7 +321,7 @@ pub enum ContactStatus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A person's name: first, last, and an optional alias (nickname).
|
/// A person's name: first, last, and an optional alias (nickname).
|
||||||
#[derive(Clone, Debug, Store, Default)]
|
#[derive(Clone, Debug, Store, Serialize, Deserialize, Default)]
|
||||||
pub struct Name {
|
pub struct Name {
|
||||||
pub first: String,
|
pub first: String,
|
||||||
pub last: String,
|
pub last: String,
|
||||||
@ -298,15 +339,28 @@ impl Name {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// App settings. Empty today; will carry e.g. a persisted-format version.
|
/// App settings: currently only the saved-format version.
|
||||||
#[derive(Clone, Debug, Store, Default)]
|
#[derive(Clone, Debug, Store, Serialize, Deserialize)]
|
||||||
pub struct Settings {
|
pub struct Settings {
|
||||||
|
/// Saved-format version; bumped on breaking JSON changes.
|
||||||
|
pub version: u32
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Settings {
|
||||||
|
/// The current saved-format version.
|
||||||
|
pub const VERSION: u32 = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Settings {
|
||||||
|
/// Defaults carry the current format version.
|
||||||
|
fn default () -> Self {
|
||||||
|
Self { version: Self::VERSION }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Which side of the party an inventory belongs to.
|
/// Which side of the party an inventory belongs to.
|
||||||
#[derive(Clone, Copy, Debug, Store)]
|
#[derive(Clone, Copy, Debug, Store, Serialize, Deserialize)]
|
||||||
pub enum InventoryKind {
|
pub enum InventoryKind {
|
||||||
Personal,
|
Personal,
|
||||||
Common
|
Common
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user