| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206 |
- using Inversion;
- using Spry;
- using Spry.Authentication;
- using Statum;
- using ClankerSaddle.Data;
- using ClankerSaddle.Services;
- namespace ClankerSaddle {
- /** Sealed note identity carried by the note row actions. */
- public class NoteActionPrivate : Object {
- public int64 note_id { get; set; }
- }
- /**
- * Common base for actions sealed against a note id: materialises
- * the typed private blob and exposes the resolved id (falling
- * back to the hidden `note_id` form field).
- */
- public abstract class SealedNoteAction : TypedStatumAction<NoteActionPrivate> {
- protected override Type private_type { get { return typeof(NoteActionPrivate); } }
- /** The sealed note id, or 0 when neither blob nor form field carried one. */
- protected int64 sealed_note_id() {
- var ctx = request_private;
- return ctx != null ? ((!)ctx).note_id : Forms.integer(request, "note_id", 0);
- }
- }
- /**
- * Common base for the note-row actions that answer with the
- * refreshed dashboard slot (toggle/delete — add and promote carry
- * no sealed id of their own, and add reuses the helper below):
- * owns the service set {@link DashboardState.build} needs, so a
- * mutation answers with refreshed state in ONE call.
- */
- public abstract class DashboardRefreshingNoteAction : SealedNoteAction {
- protected TaskService task_service = inject<TaskService>();
- protected SaddleService saddle_service = inject<SaddleService>();
- protected TimelineService timeline_service = inject<TimelineService>();
- protected InvercargillSql.Orm.OrmSession db = Inversion.inject<InvercargillSql.Orm.OrmSession>();
- protected PushService push_service = inject<PushService>();
- protected NoteService note_service = inject<NoteService>();
- protected UserService users = inject<UserService>();
- protected ModelConfigService model_config = inject<ModelConfigService>();
- /**
- * Rebuilds the dashboard slot from current data — the same
- * build the page and its pulse refresh run.
- */
- protected async Statum.Slot dashboard_slot() throws GLib.Error {
- var me = SpryAuth.current(request);
- return yield DashboardState.build(state_service, task_service,
- saddle_service, timeline_service, db, note_service, action_registry,
- push_service, me != null ? ((!)me).user_id : 0, model_config);
- }
- }
- /**
- * Adds a dashboard scratchpad note (the body is the whole note —
- * no project or assignment semantics). Answers with the
- * refreshed dashboard slot, like the page's own refresh. A plain
- * action (no sealed id — the form is not row-scoped).
- */
- public class AddNoteAction : StatumAction {
- private NoteService notes = inject<NoteService>();
- private TaskService task_service = inject<TaskService>();
- private SaddleService saddle_service = inject<SaddleService>();
- private TimelineService timeline_service = inject<TimelineService>();
- private NoteService note_service = inject<NoteService>();
- private InvercargillSql.Orm.OrmSession db = Inversion.inject<InvercargillSql.Orm.OrmSession>();
- private PushService push_service = inject<PushService>();
- private UserService users = inject<UserService>();
- private ModelConfigService model_config = inject<ModelConfigService>();
- public override async DirectiveBuilder handle() throws GLib.Error {
- var guard = yield SpryAuth.require_login(directives(), request, users);
- if (guard != null) {
- return (!)guard;
- }
- string body = Forms.field_raw(request, "body");
- if (body.strip().length == 0) {
- return directives().notify("error", "A note body is required");
- }
- try {
- yield notes.add_note(body);
- var me = SpryAuth.current(request);
- var state = yield DashboardState.build(state_service, task_service,
- saddle_service, timeline_service, db, note_service, action_registry,
- push_service, me != null ? ((!)me).user_id : 0, model_config);
- return directives().set(state);
- } catch (Error e) {
- return directives().notify("error", @"Could not add the note: $(e.message)");
- }
- }
- }
- /**
- * Toggles a note's done flag (the checkbox on the row). The id
- * comes from the sealed {@link NoteActionPrivate} blob; answers
- * with the refreshed dashboard slot.
- */
- public class ToggleNoteAction : DashboardRefreshingNoteAction {
- private NoteService notes = inject<NoteService>();
- public override async DirectiveBuilder handle() throws GLib.Error {
- var guard = yield SpryAuth.require_login(directives(), request, users);
- if (guard != null) {
- return (!)guard;
- }
- int64 note_id = sealed_note_id();
- if (note_id <= 0) {
- return directives().navigate("/");
- }
- try {
- // Flip (the row button carries no direction field): an
- // explicit "0"/"1" form value still forces a direction.
- var current = yield notes.get_note(note_id);
- if (current == null) {
- return directives().notify("error", "No such note").navigate("/");
- }
- string direction = Forms.field(request, "done");
- bool target = direction.length > 0
- ? direction == "1" : !((!)current).done;
- yield notes.set_done(note_id, target);
- return directives().set(yield dashboard_slot());
- } catch (Error e) {
- return directives().notify("error", @"Could not update the note: $(e.message)");
- }
- }
- }
- /**
- * Deletes a note. The id comes from the sealed private blob;
- * answers with the refreshed dashboard slot.
- */
- public class DeleteNoteAction : DashboardRefreshingNoteAction {
- private NoteService notes = inject<NoteService>();
- public override async DirectiveBuilder handle() throws GLib.Error {
- var guard = yield SpryAuth.require_login(directives(), request, users);
- if (guard != null) {
- return (!)guard;
- }
- int64 note_id = sealed_note_id();
- if (note_id <= 0) {
- return directives().navigate("/");
- }
- try {
- yield notes.delete_note(note_id);
- return directives().set(yield dashboard_slot());
- } catch (Error e) {
- return directives().notify("error", @"Could not delete the note: $(e.message)");
- }
- }
- }
- /**
- * Promotes a note into a DRAFT goal (goal 8 task 3): title = the
- * note's first line, brief = the full note body, on the chosen
- * project with the chosen branch. The note is stamped with the
- * goal id (frozen — no further promote) and the human lands on
- * the goal's draft page to finish it (links, base branch, start).
- */
- public class PromoteNoteAction : SealedNoteAction {
- private NoteService notes = inject<NoteService>();
- private UserService users = inject<UserService>();
- public override async DirectiveBuilder handle() throws GLib.Error {
- var guard = yield SpryAuth.require_login(directives(), request, users);
- if (guard != null) {
- return (!)guard;
- }
- int64 note_id = sealed_note_id();
- if (note_id <= 0) {
- return directives().navigate("/");
- }
- try {
- var goal = yield notes.promote_to_goal(note_id,
- Forms.integer(request, "project_id", 0),
- Forms.field(request, "branch_name"));
- return directives()
- .notify("success", @"Goal '$(goal.title)' created in draft from note #$(note_id) — set it up and press start")
- .navigate(@"/goals/$(goal.id)");
- } catch (Error e) {
- return directives()
- .notify("error", @"Could not create the goal from note #$(note_id): $(e.message)")
- .navigate("/");
- }
- }
- }
- }
|