using Statum; using Invercargill; using Invercargill.DataStructures; namespace Example { // Typed slot models — GObject properties mapped to/from Properties via // GObjectMapping. ActionDto fields round-trip as nested JSON. public class CounterPrivate : Object { public int delta { get; set; } } public class CounterPublic : Object { public int value { get; set; } public Model.ActionDto bump { get; set; } } public class PagePublic : Object { public Model.ActionDto login { get; set; } } public class AuthPublic : Object { public string name { get; set; } public string role { get; set; } public Model.ActionDto logout { get; set; } public Model.ActionDto update_announcement { get; set; } } public class AuthPrivate : Object { public bool is_admin { get; set; } } /** Global broadcast model — a simple announcement string. */ public class AnnouncementPublic : Object { public string text { get; set; } } /** Derived model — a short preview of the announcement, produced by the topic modifier. */ public class AnnouncementPreview : Object { public string summary { get; set; } public int chars { get; set; } } /** Topic: loads the current announcement from the global slot when triggered. */ public class AnnouncementTopic : Statum.Topic { private StateService state_service = Inversion.inject(); public override async AnnouncementPublic load(string topic_key) throws GLib.Error { var slot = state_service.get_slot(StateService.GLOBAL_KEY_PREFIX + "announcement"); var model = new AnnouncementPublic(); if (slot != null && ((!)slot).current_state != null) { try { model = GObjectMapping.from_properties_typed( typeof(AnnouncementPublic), ((!)slot).current_state.public_data); } catch {} } return model; } } /** Modifier: derives an AnnouncementPreview from the AnnouncementPublic payload. */ public class AnnouncementPreviewModifier : Statum.StateModifier { public override Type state_type { get { return typeof(AnnouncementPreview); } } public override GLib.Object? derive(GLib.Object payload, GLib.Object? current, HeldStates held) { var announcement = (AnnouncementPublic) payload; var preview = new AnnouncementPreview(); preview.chars = announcement.text.length; preview.summary = announcement.text.length > 40 ? announcement.text.substring(0, 40) + "…" : announcement.text; return preview; } } /** * Entrypoint for the home page: hydrates counter + page slots with typed * models, embedding action references as fields. */ public class HomeEntrypoint : StatumEntrypoint { public override async DirectiveBuilder handle() throws GLib.Error { var bump_private = new CounterPrivate(); bump_private.delta = 1; var bump_ref = action_registry.author_private(bump_private); var counter = new CounterPublic(); counter.value = 0; counter.bump = bump_ref; var page = new PagePublic(); page.login = action_registry.author(); // Create a derived "preview" slot and register a topic watcher so it // updates live when the announcement changes. var preview_model = new AnnouncementPreview(); preview_model.summary = ""; preview_model.chars = 0; var preview_slot = state_service.new_slot(Scope.PAGE, new State() { type_name = "preview", public_data = GObjectMapping.to_properties(preview_model), private_data = new PropertyDictionary() }); topic_registry.watch("announcement:latest", preview_slot.id, typeof(AnnouncementPreview).name(), request.held); return directives() .set_global("announcement") .subscribe_global("announcement") .set_typed("counter", Scope.PAGE, counter) .set_typed("page", Scope.PAGE, page) .set(preview_slot) .subscribe(preview_slot.id); } } /** * Entrypoint for the guarded dashboard page. */ public class DashboardEntrypoint : StatumEntrypoint { public override async DirectiveBuilder handle() throws GLib.Error { return directives() .set_global("announcement") .notify("info", "Welcome to your dashboard"); } } }