skill_web/hooks/
use_wizard_state.rs1use std::collections::HashMap;
16use yew::prelude::*;
17use serde::{Deserialize, Serialize};
18use gloo_storage::{LocalStorage, Storage};
19
20const WIZARD_STATE_KEY: &str = "skill-web-wizard-state";
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
24pub enum WizardStep {
25 SelectSkill,
26 SelectTool,
27 ConfigureParameters,
28 Execute,
29}
30
31impl WizardStep {
32 pub fn next(&self) -> Option<Self> {
34 match self {
35 Self::SelectSkill => Some(Self::SelectTool),
36 Self::SelectTool => Some(Self::ConfigureParameters),
37 Self::ConfigureParameters => Some(Self::Execute),
38 Self::Execute => None,
39 }
40 }
41
42 pub fn prev(&self) -> Option<Self> {
44 match self {
45 Self::SelectSkill => None,
46 Self::SelectTool => Some(Self::SelectSkill),
47 Self::ConfigureParameters => Some(Self::SelectTool),
48 Self::Execute => Some(Self::ConfigureParameters),
49 }
50 }
51
52 pub fn number(&self) -> usize {
54 match self {
55 Self::SelectSkill => 1,
56 Self::SelectTool => 2,
57 Self::ConfigureParameters => 3,
58 Self::Execute => 4,
59 }
60 }
61
62 pub fn label(&self) -> &'static str {
64 match self {
65 Self::SelectSkill => "Select Skill",
66 Self::SelectTool => "Select Tool",
67 Self::ConfigureParameters => "Configure Parameters",
68 Self::Execute => "Execute",
69 }
70 }
71}
72
73#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
75pub struct WizardState {
76 pub current_step: WizardStep,
77 pub selected_skill: Option<String>,
78 pub selected_tool: Option<String>,
79 pub selected_instance: Option<String>,
80 #[serde(skip)]
81 pub parameters: HashMap<String, serde_json::Value>,
82 pub validation_errors: HashMap<String, String>,
83 pub steps_completed: HashMap<WizardStep, bool>,
84}
85
86impl Default for WizardState {
87 fn default() -> Self {
88 Self {
89 current_step: WizardStep::SelectSkill,
90 selected_skill: None,
91 selected_tool: None,
92 selected_instance: None,
93 parameters: HashMap::new(),
94 validation_errors: HashMap::new(),
95 steps_completed: HashMap::new(),
96 }
97 }
98}
99
100impl WizardState {
101 pub fn can_progress(&self) -> bool {
103 match self.current_step {
104 WizardStep::SelectSkill => self.selected_skill.is_some(),
105 WizardStep::SelectTool => self.selected_tool.is_some(),
106 WizardStep::ConfigureParameters => self.validation_errors.is_empty(),
107 WizardStep::Execute => false, }
109 }
110
111 pub fn is_step_accessible(&self, step: WizardStep) -> bool {
113 match step {
114 WizardStep::SelectSkill => true,
115 WizardStep::SelectTool => self.selected_skill.is_some(),
116 WizardStep::ConfigureParameters => {
117 self.selected_skill.is_some() && self.selected_tool.is_some()
118 }
119 WizardStep::Execute => {
120 self.selected_skill.is_some()
121 && self.selected_tool.is_some()
122 && self.validation_errors.is_empty()
123 }
124 }
125 }
126
127 pub fn complete_current_step(&mut self) {
129 self.steps_completed.insert(self.current_step, true);
130 }
131}
132
133pub struct WizardStateHandle {
135 state: UseStateHandle<WizardState>,
136}
137
138impl Clone for WizardStateHandle {
139 fn clone(&self) -> Self {
140 Self {
141 state: self.state.clone(),
142 }
143 }
144}
145
146impl WizardStateHandle {
147 pub fn get(&self) -> WizardState {
149 (*self.state).clone()
150 }
151
152 pub fn next(&self) {
154 let mut state = (*self.state).clone();
155
156 if !state.can_progress() {
157 return;
158 }
159
160 if let Some(next_step) = state.current_step.next() {
161 state.complete_current_step();
162 state.current_step = next_step;
163 self.state.set(state.clone());
164 Self::persist(&state);
165 }
166 }
167
168 pub fn prev(&self) {
170 let mut state = (*self.state).clone();
171
172 if let Some(prev_step) = state.current_step.prev() {
173 state.current_step = prev_step;
174 self.state.set(state.clone());
175 Self::persist(&state);
176 }
177 }
178
179 pub fn go_to(&self, step: WizardStep) {
181 let state = (*self.state).clone();
182
183 if !state.is_step_accessible(step) {
184 return;
185 }
186
187 let mut new_state = state;
188 new_state.current_step = step;
189 self.state.set(new_state.clone());
190 Self::persist(&new_state);
191 }
192
193 pub fn set_skill(&self, skill: String) {
195 let mut state = (*self.state).clone();
196
197 let has_tool = state.selected_tool.is_some();
199 let has_params = !state.parameters.is_empty();
200
201 if has_tool || has_params {
202 }
205
206 state.selected_skill = Some(skill);
207 state.selected_tool = None; state.parameters.clear(); state.complete_current_step();
210
211 state.current_step = WizardStep::SelectTool;
213
214 self.state.set(state.clone());
215 Self::persist(&state);
216 }
217
218 pub fn set_tool(&self, tool: String) {
220 let mut state = (*self.state).clone();
221
222 let has_params = !state.parameters.is_empty();
224 let changing_tool = state.selected_tool.as_ref().map(|t| t != &tool).unwrap_or(false);
225
226 if has_params && changing_tool {
227 state.parameters.clear();
229 }
230
231 state.selected_tool = Some(tool);
232 state.complete_current_step();
233
234 state.current_step = WizardStep::ConfigureParameters;
236
237 self.state.set(state.clone());
238 Self::persist(&state);
239 }
240
241 pub fn set_instance(&self, instance: Option<String>) {
243 let mut state = (*self.state).clone();
244 state.selected_instance = instance;
245 self.state.set(state.clone());
246 Self::persist(&state);
247 }
248
249 pub fn set_parameter(&self, name: String, value: serde_json::Value) {
251 let mut state = (*self.state).clone();
252 state.parameters.insert(name, value);
253 self.state.set(state.clone());
254 Self::persist(&state);
255 }
256
257 pub fn set_validation_errors(&self, errors: HashMap<String, String>) {
259 let mut state = (*self.state).clone();
260 state.validation_errors = errors;
261 self.state.set(state.clone());
262 }
264
265 pub fn reset(&self) {
267 let state = WizardState::default();
268 self.state.set(state.clone());
269 Self::persist(&state);
270 }
271
272 fn persist(state: &WizardState) {
274 let _ = LocalStorage::set(WIZARD_STATE_KEY, state);
275 }
276
277 fn load() -> WizardState {
279 LocalStorage::get(WIZARD_STATE_KEY).unwrap_or_default()
280 }
281}
282
283#[hook]
285pub fn use_wizard_state() -> WizardStateHandle {
286 let state = use_state(WizardStateHandle::load);
288
289 WizardStateHandle { state }
290}