From 1f3184baf682c8db0383896465919fd47926ed65 Mon Sep 17 00:00:00 2001 From: yaroslav k Date: Sun, 16 Aug 2026 12:31:22 +0300 Subject: [PATCH] undo: header buttons, shortcuts, wiring MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - App provides HistoryHandle as context (both build modes; stacks stay empty on the server) and passes it to persist::init_hydrated - persist: init_hydrated starts undo tracking after the localStorage load (first captured state = saved state, not mock); clear_all and apply_import call undo::before_replace so they are undoable - header: Отменить/Вернуть buttons with disabled states driven by the reactive stack depths; Ctrl+Z / Ctrl+Shift+Z / Ctrl+Y via a window keydown listener that skips editable targets (browser owns those) - SCSS: disabled control buttons dim out --- src/app.rs | 5 +++-- src/components/header.rs | 20 ++++++++++++++++++-- style/_header.scss | 5 +++++ 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/app.rs b/src/app.rs index b6e9195..6aec956 100644 --- a/src/app.rs +++ b/src/app.rs @@ -32,11 +32,12 @@ pub fn App() -> impl IntoView { let store = Store::new(Dashboard::mock()); provide_context(store.clone()); provide_context(ModalState::new()); + provide_context(crate::state::undo::HistoryHandle::new(store.clone())); console_error_panic_hook::set_once(); - // Client-only: load the saved state after mount, then autosave. + // Client-only: load the saved state after mount, then autosave + undo. #[cfg(feature = "hydrate")] - crate::state::persist::init_hydrated(&store); + crate::state::persist::init_hydrated(&store, &expect_context::()); let modal = expect_context::(); diff --git a/src/components/header.rs b/src/components/header.rs index 7716079..efb84fc 100644 --- a/src/components/header.rs +++ b/src/components/header.rs @@ -6,18 +6,24 @@ use wasm_bindgen::JsCast; use crate::components::modal::ModalState; use crate::prelude::*; use crate::state::persist::{ apply_import, clear_all, export_download }; +use crate::state::undo::HistoryHandle; /// Campaign header: editable name, campaign image, in-game date and -/// session steppers, and the persistence controls. +/// session steppers, the persistence controls, and undo/redo. /// /// Save downloads the dashboard as `dashboard.json`, Load imports one via /// a hidden file picker (with a confirm), Clear wipes to the blank /// campaign (with a confirm). Changes also autosave to localStorage -/// (see `crate::state::persist`). +/// (see `crate::state::persist`) and feed the undo history (see +/// `crate::state::undo`); the undo/redo buttons and the Ctrl+Z / +/// Ctrl+Shift+Z / Ctrl+Y shortcuts revert the last edit bursts. #[component] pub fn Header () -> impl IntoView { let state = expect_context::(); let modal = expect_context::(); + let history = expect_context::(); + let undo_depth = history.undo_depth(); + let redo_depth = history.redo_depth(); let file_input = NodeRef::::new(); let campaign = state.campaign(); let date = state.date(); @@ -123,6 +129,16 @@ pub fn Header () -> impl IntoView { + + diff --git a/style/_header.scss b/style/_header.scss index a1a8011..b93a5f3 100644 --- a/style/_header.scss +++ b/style/_header.scss @@ -59,6 +59,11 @@ header { line-height: 1em; margin: 2px 5px; @include mixins.button; + + &:disabled { + opacity: 0.45; + cursor: default; + } } }