use std::collections::HashMap;
use yew::prelude::*;
use serde::{Deserialize, Serialize};
use gloo_storage::{LocalStorage, Storage};
const WIZARD_STATE_KEY: &str = "skill-web-wizard-state";
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum WizardStep {
SelectSkill,
SelectTool,
ConfigureParameters,
Execute,
}
impl WizardStep {
pub fn next(&self) -> Option<Self> {
match self {
Self::SelectSkill => Some(Self::SelectTool),
Self::SelectTool => Some(Self::ConfigureParameters),
Self::ConfigureParameters => Some(Self::Execute),
Self::Execute => None,
}
}
pub fn prev(&self) -> Option<Self> {
match self {
Self::SelectSkill => None,
Self::SelectTool => Some(Self::SelectSkill),
Self::ConfigureParameters => Some(Self::SelectTool),
Self::Execute => Some(Self::ConfigureParameters),
}
}
pub fn number(&self) -> usize {
match self {
Self::SelectSkill => 1,
Self::SelectTool => 2,
Self::ConfigureParameters => 3,
Self::Execute => 4,
}
}
pub fn label(&self) -> &'static str {
match self {
Self::SelectSkill => "Select Skill",
Self::SelectTool => "Select Tool",
Self::ConfigureParameters => "Configure Parameters",
Self::Execute => "Execute",
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct WizardState {
pub current_step: WizardStep,
pub selected_skill: Option<String>,
pub selected_tool: Option<String>,
pub selected_instance: Option<String>,
#[serde(skip)]
pub parameters: HashMap<String, serde_json::Value>,
pub validation_errors: HashMap<String, String>,
pub steps_completed: HashMap<WizardStep, bool>,
}
impl Default for WizardState {
fn default() -> Self {
Self {
current_step: WizardStep::SelectSkill,
selected_skill: None,
selected_tool: None,
selected_instance: None,
parameters: HashMap::new(),
validation_errors: HashMap::new(),
steps_completed: HashMap::new(),
}
}
}
impl WizardState {
pub fn can_progress(&self) -> bool {
match self.current_step {
WizardStep::SelectSkill => self.selected_skill.is_some(),
WizardStep::SelectTool => self.selected_tool.is_some(),
WizardStep::ConfigureParameters => self.validation_errors.is_empty(),
WizardStep::Execute => false, }
}
pub fn is_step_accessible(&self, step: WizardStep) -> bool {
match step {
WizardStep::SelectSkill => true,
WizardStep::SelectTool => self.selected_skill.is_some(),
WizardStep::ConfigureParameters => {
self.selected_skill.is_some() && self.selected_tool.is_some()
}
WizardStep::Execute => {
self.selected_skill.is_some()
&& self.selected_tool.is_some()
&& self.validation_errors.is_empty()
}
}
}
pub fn complete_current_step(&mut self) {
self.steps_completed.insert(self.current_step, true);
}
}
pub struct WizardStateHandle {
state: UseStateHandle<WizardState>,
}
impl Clone for WizardStateHandle {
fn clone(&self) -> Self {
Self {
state: self.state.clone(),
}
}
}
impl WizardStateHandle {
pub fn get(&self) -> WizardState {
(*self.state).clone()
}
pub fn next(&self) {
let mut state = (*self.state).clone();
if !state.can_progress() {
return;
}
if let Some(next_step) = state.current_step.next() {
state.complete_current_step();
state.current_step = next_step;
self.state.set(state.clone());
Self::persist(&state);
}
}
pub fn prev(&self) {
let mut state = (*self.state).clone();
if let Some(prev_step) = state.current_step.prev() {
state.current_step = prev_step;
self.state.set(state.clone());
Self::persist(&state);
}
}
pub fn go_to(&self, step: WizardStep) {
let state = (*self.state).clone();
if !state.is_step_accessible(step) {
return;
}
let mut new_state = state;
new_state.current_step = step;
self.state.set(new_state.clone());
Self::persist(&new_state);
}
pub fn set_skill(&self, skill: String) {
let mut state = (*self.state).clone();
let has_tool = state.selected_tool.is_some();
let has_params = !state.parameters.is_empty();
if has_tool || has_params {
}
state.selected_skill = Some(skill);
state.selected_tool = None; state.parameters.clear(); state.complete_current_step();
state.current_step = WizardStep::SelectTool;
self.state.set(state.clone());
Self::persist(&state);
}
pub fn set_tool(&self, tool: String) {
let mut state = (*self.state).clone();
let has_params = !state.parameters.is_empty();
let changing_tool = state.selected_tool.as_ref().map(|t| t != &tool).unwrap_or(false);
if has_params && changing_tool {
state.parameters.clear();
}
state.selected_tool = Some(tool);
state.complete_current_step();
state.current_step = WizardStep::ConfigureParameters;
self.state.set(state.clone());
Self::persist(&state);
}
pub fn set_instance(&self, instance: Option<String>) {
let mut state = (*self.state).clone();
state.selected_instance = instance;
self.state.set(state.clone());
Self::persist(&state);
}
pub fn set_parameter(&self, name: String, value: serde_json::Value) {
let mut state = (*self.state).clone();
state.parameters.insert(name, value);
self.state.set(state.clone());
Self::persist(&state);
}
pub fn set_validation_errors(&self, errors: HashMap<String, String>) {
let mut state = (*self.state).clone();
state.validation_errors = errors;
self.state.set(state.clone());
}
pub fn reset(&self) {
let state = WizardState::default();
self.state.set(state.clone());
Self::persist(&state);
}
fn persist(state: &WizardState) {
let _ = LocalStorage::set(WIZARD_STATE_KEY, state);
}
fn load() -> WizardState {
LocalStorage::get(WIZARD_STATE_KEY).unwrap_or_default()
}
}
#[hook]
pub fn use_wizard_state() -> WizardStateHandle {
let state = use_state(WizardStateHandle::load);
WizardStateHandle { state }
}