use fission::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DashboardState {
pub now_seconds: u64,
pub sync_status: String,
pub sync_connected: bool,
}
impl Default for DashboardState {
fn default() -> Self {
Self {
now_seconds: 0,
sync_status: "Not connected".into(),
sync_connected: false,
}
}
}
impl GlobalState for DashboardState {}
#[fission_action]
pub struct ClockTick;
fn on_clock_tick(
state: &mut DashboardState,
_action: ClockTick,
ctx: &mut ReducerContext<DashboardState>,
) {
if let Some(now) = ctx.input.timer_tick::<u64>() {
state.now_seconds = now;
}
}
impl From<DashboardScreen> for Widget {
fn from(component: DashboardScreen) -> Self {
let (ctx, view) = fission::build::current::<DashboardState>();
let tick = ctx.bind(
ClockTick,
reduce_with!(on_clock_tick),
);
ctx.resources.timer(
TimerResource::new(
ResourceKey::new("dashboard-clock"),
std::time::Duration::from_secs(1),
0_u64,
)
.immediate()
.on_tick(tick),
);
Text::new(format!("Seconds: {}", view.state().now_seconds)).into()
}
}
use fission::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Debug)]
pub struct SyncService;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct SyncConfig {
pub workspace_id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum SyncCommand {
RefreshNow,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct SyncCommandOk;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct SyncCommandErr {
pub message: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum SyncEvent {
Connected,
SyncFinished { summary: String },
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct SyncStartErr {
pub message: String,
}
impl ServiceSpec for SyncService {
type Config = SyncConfig;
type Command = SyncCommand;
type CommandOk = SyncCommandOk;
type CommandErr = SyncCommandErr;
type Event = SyncEvent;
type StartErr = SyncStartErr;
const NAME: &'static str = "sync-service";
}
const SYNC_SERVICE: ServiceType<SyncService> = ServiceType::new("sync-service");
#[fission_action]
pub struct SyncStarted;
#[fission_action]
pub struct SyncEventArrived;
#[fission_action]
pub struct SyncStartFailed;
impl From<DashboardScreen> for Widget {
fn from(component: DashboardScreen) -> Self {
let (ctx, view) = fission::build::current::<DashboardState>();
let on_started = ctx.bind(
SyncStarted,
reduce_with!(on_sync_started),
);
let on_event = ctx.bind(
SyncEventArrived,
reduce_with!(on_sync_event),
);
let on_start_failed = ctx.bind(
SyncStartFailed,
reduce_with!(on_sync_start_failed),
);
ctx.resources.service(
ServiceResource::new(
ResourceKey::new("workspace-sync"),
ServiceSlot::singleton(SYNC_SERVICE),
SyncConfig {
workspace_id: "primary".into(),
},
)
.on_started(on_started)
.on_event(on_event)
.on_start_failed(on_start_failed),
);
Text::new(view.state().sync_status.clone()).into()
}
}
fn on_sync_started(
state: &mut DashboardState,
_action: SyncStarted,
_ctx: &mut ReducerContext<DashboardState>,
) {
state.sync_connected = true;
state.sync_status = "Connected".into();
}
fn on_sync_event(
state: &mut DashboardState,
_action: SyncEventArrived,
ctx: &mut ReducerContext<DashboardState>,
) {
if let Some(event) = ctx.input.service_event(SYNC_SERVICE) {
match event {
SyncEvent::Connected => {
state.sync_connected = true;
state.sync_status = "Connected".into();
}
SyncEvent::SyncFinished { summary } => {
state.sync_status = summary;
}
}
}
}
fn on_sync_start_failed(
state: &mut DashboardState,
_action: SyncStartFailed,
ctx: &mut ReducerContext<DashboardState>,
) {
state.sync_connected = false;
state.sync_status = ctx
.input
.service_start_error_message(SYNC_SERVICE)
.unwrap_or("Sync failed to start")
.to_string();
}
Check | Expected result |
|---|---|
Build | cargo check or the relevant fission command finishes without target-specific configuration errors. |
Behavior | The screen, package, server route, or test flow described by the recipe is visible and responds to input. |
Failure path | At least one denied, missing, invalid, or empty case renders a useful message instead of silently doing nothing. |
Next link | The reference page linked from the recipe explains the exact fields, commands, or types used here. |