|
|
@@ -34,6 +34,38 @@ namespace ClankerSaddle {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Shared base for goal-page actions that answer with the
|
|
|
+ * refreshed goal slot instead of a navigate (goal 2 QoL): owns
|
|
|
+ * the service set {@link GoalState.build} needs, so a mutation
|
|
|
+ * rebuilds the page view with ONE call - {@link goal_detail_slot}
|
|
|
+ * - instead of every action copy-pasting the injections and the
|
|
|
+ * build chain. Success paths answer notify(...).set(slot); flows
|
|
|
+ * whose result genuinely IS navigation keep navigating.
|
|
|
+ */
|
|
|
+ public abstract class GoalDetailRefreshingAction : SealedGoalAction {
|
|
|
+
|
|
|
+ protected GoalService goal_service = inject<GoalService>();
|
|
|
+ protected TaskService task_service = inject<TaskService>();
|
|
|
+ protected ProjectService project_service = inject<ProjectService>();
|
|
|
+ protected SaddleService saddle_service = inject<SaddleService>();
|
|
|
+ protected SaddlePollState poll_state = inject<SaddlePollState>();
|
|
|
+ protected RuleService rule_service = inject<RuleService>();
|
|
|
+ protected InvercargillSql.Orm.OrmSession db = Inversion.inject<InvercargillSql.Orm.OrmSession>();
|
|
|
+ protected UserService users = inject<UserService>();
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Rebuilds the goal detail slot from current data - the ONE
|
|
|
+ * call a mutation needs to answer with refreshed state (the
|
|
|
+ * same build the page's refresh action runs).
|
|
|
+ */
|
|
|
+ protected async Statum.Slot goal_detail_slot(int64 goal_id) throws GLib.Error {
|
|
|
+ var ctx = new GoalViewContext(goal_service, task_service, project_service,
|
|
|
+ db, action_registry, saddle_service, poll_state, rule_service);
|
|
|
+ return yield GoalState.build(goal_id, ctx, state_service);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* Creates a goal from the goals form (project, title, brief and a
|
|
|
* required branch name; base branch optional — the project's
|
|
|
@@ -146,10 +178,9 @@ namespace ClankerSaddle {
|
|
|
}
|
|
|
|
|
|
/** Pauses an active goal: supervision freezes, events queue. */
|
|
|
- public class PauseGoalAction : SealedGoalAction {
|
|
|
+ public class PauseGoalAction : GoalDetailRefreshingAction {
|
|
|
|
|
|
private GoalService goals = inject<GoalService>();
|
|
|
- private UserService users = inject<UserService>();
|
|
|
|
|
|
public override async DirectiveBuilder handle() throws GLib.Error {
|
|
|
var guard = yield SpryAuth.require_login(directives(), request, users);
|
|
|
@@ -159,7 +190,7 @@ namespace ClankerSaddle {
|
|
|
try {
|
|
|
var goal = yield goals.pause_goal(sealed_goal_id());
|
|
|
return directives().notify("success", @"Goal '$(goal.title)' paused")
|
|
|
- .navigate(@"/goals/$(goal.id)");
|
|
|
+ .set(yield goal_detail_slot(goal.id));
|
|
|
} catch (Error e) {
|
|
|
return directives().notify("error", @"Could not pause the goal: $(e.message)").navigate("/goals");
|
|
|
}
|
|
|
@@ -167,10 +198,9 @@ namespace ClankerSaddle {
|
|
|
}
|
|
|
|
|
|
/** Resumes a paused goal: queued events coalesce into the next wake. */
|
|
|
- public class ResumeGoalAction : SealedGoalAction {
|
|
|
+ public class ResumeGoalAction : GoalDetailRefreshingAction {
|
|
|
|
|
|
private GoalService goals = inject<GoalService>();
|
|
|
- private UserService users = inject<UserService>();
|
|
|
|
|
|
public override async DirectiveBuilder handle() throws GLib.Error {
|
|
|
var guard = yield SpryAuth.require_login(directives(), request, users);
|
|
|
@@ -180,7 +210,7 @@ namespace ClankerSaddle {
|
|
|
try {
|
|
|
var goal = yield goals.resume_goal(sealed_goal_id());
|
|
|
return directives().notify("success", @"Goal '$(goal.title)' resumed")
|
|
|
- .navigate(@"/goals/$(goal.id)");
|
|
|
+ .set(yield goal_detail_slot(goal.id));
|
|
|
} catch (Error e) {
|
|
|
return directives().notify("error", @"Could not resume the goal: $(e.message)").navigate("/goals");
|
|
|
}
|
|
|
@@ -317,10 +347,9 @@ namespace ClankerSaddle {
|
|
|
* Posts a comment on a goal (the goal page's thread; a selection
|
|
|
* quote rides along when the popover opened one).
|
|
|
*/
|
|
|
- public class GoalCommentAction : SealedGoalAction {
|
|
|
+ public class GoalCommentAction : GoalDetailRefreshingAction {
|
|
|
|
|
|
private GoalService goals = inject<GoalService>();
|
|
|
- private UserService users = inject<UserService>();
|
|
|
|
|
|
public override async DirectiveBuilder handle() throws GLib.Error {
|
|
|
var guard = yield SpryAuth.require_login(directives(), request, users);
|
|
|
@@ -340,7 +369,7 @@ namespace ClankerSaddle {
|
|
|
yield goals.add_human_comment(sealed_goal_id(), author, body,
|
|
|
Forms.optional(request, "quote"));
|
|
|
return directives().notify("success", "Comment added")
|
|
|
- .navigate(@"/goals/$(sealed_goal_id())");
|
|
|
+ .set(yield goal_detail_slot(sealed_goal_id()));
|
|
|
} catch (Error e) {
|
|
|
return directives().notify("error", @"Could not add the comment: $(e.message)")
|
|
|
.navigate(@"/goals/$(sealed_goal_id())");
|
|
|
@@ -439,16 +468,7 @@ namespace ClankerSaddle {
|
|
|
* re-hydrates the goal slot with fresh narrative/task/review
|
|
|
* state, exactly like the task page's refresh.
|
|
|
*/
|
|
|
- public class RefreshGoalDataAction : SealedGoalAction {
|
|
|
-
|
|
|
- private GoalService goal_service = inject<GoalService>();
|
|
|
- private TaskService task_service = inject<TaskService>();
|
|
|
- private ProjectService project_service = inject<ProjectService>();
|
|
|
- private SaddleService saddle_service = inject<SaddleService>();
|
|
|
- private SaddlePollState poll_state = inject<SaddlePollState>();
|
|
|
- private RuleService rule_service = inject<RuleService>();
|
|
|
- private InvercargillSql.Orm.OrmSession db = Inversion.inject<InvercargillSql.Orm.OrmSession>();
|
|
|
- private UserService users = inject<UserService>();
|
|
|
+ public class RefreshGoalDataAction : GoalDetailRefreshingAction {
|
|
|
|
|
|
public override async DirectiveBuilder handle() throws GLib.Error {
|
|
|
var guard = yield SpryAuth.require_login(directives(), request, users);
|
|
|
@@ -461,18 +481,14 @@ namespace ClankerSaddle {
|
|
|
return directives();
|
|
|
}
|
|
|
|
|
|
- var ctx = new GoalViewContext(goal_service, task_service, project_service,
|
|
|
- db, action_registry, saddle_service, poll_state, rule_service);
|
|
|
- var state = yield GoalState.build(goal_id, ctx, state_service);
|
|
|
- return directives().set(state);
|
|
|
+ return directives().set(yield goal_detail_slot(goal_id));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/** Adds a project link (co-developed/reference) to a goal. */
|
|
|
- public class AddGoalLinkAction : SealedGoalAction {
|
|
|
+ public class AddGoalLinkAction : GoalDetailRefreshingAction {
|
|
|
|
|
|
private GoalService goals = inject<GoalService>();
|
|
|
- private UserService users = inject<UserService>();
|
|
|
|
|
|
public override async DirectiveBuilder handle() throws GLib.Error {
|
|
|
var guard = yield SpryAuth.require_login(directives(), request, users);
|
|
|
@@ -484,7 +500,7 @@ namespace ClankerSaddle {
|
|
|
Forms.integer(request, "linked_project_id", 0),
|
|
|
Forms.field(request, "relationship"));
|
|
|
return directives().notify("success", "Project linked")
|
|
|
- .navigate(@"/goals/$(sealed_goal_id())");
|
|
|
+ .set(yield goal_detail_slot(sealed_goal_id()));
|
|
|
} catch (Error e) {
|
|
|
return directives().notify("error", @"Could not link the project: $(e.message)")
|
|
|
.navigate(@"/goals/$(sealed_goal_id())");
|
|
|
@@ -493,10 +509,9 @@ namespace ClankerSaddle {
|
|
|
}
|
|
|
|
|
|
/** Removes the goal link identified by `link_id`. */
|
|
|
- public class RemoveGoalLinkAction : SealedGoalAction {
|
|
|
+ public class RemoveGoalLinkAction : GoalDetailRefreshingAction {
|
|
|
|
|
|
private GoalService goals = inject<GoalService>();
|
|
|
- private UserService users = inject<UserService>();
|
|
|
|
|
|
public override async DirectiveBuilder handle() throws GLib.Error {
|
|
|
var guard = yield SpryAuth.require_login(directives(), request, users);
|
|
|
@@ -507,7 +522,7 @@ namespace ClankerSaddle {
|
|
|
try {
|
|
|
yield goals.remove_link(link_id);
|
|
|
return directives().notify("success", "Link removed")
|
|
|
- .navigate(@"/goals/$(sealed_goal_id())");
|
|
|
+ .set(yield goal_detail_slot(sealed_goal_id()));
|
|
|
} catch (Error e) {
|
|
|
return directives().notify("error", @"Could not remove the link: $(e.message)")
|
|
|
.navigate(@"/goals/$(sealed_goal_id())");
|