|
@@ -9,6 +9,13 @@ using ClankerSaddle.Services;
|
|
|
|
|
|
|
|
namespace ClankerSaddle {
|
|
namespace ClankerSaddle {
|
|
|
|
|
|
|
|
|
|
+ /** A capability chip on a saddle card, with its remove action. */
|
|
|
|
|
+ public class SaddleCapabilityView : Object {
|
|
|
|
|
+ public int64 link_id { get; set; }
|
|
|
|
|
+ public string name { get; set; default = ""; }
|
|
|
|
|
+ public Statum.Model.ActionDto? detach { get; set; }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
/** A saddle card on the management page, with its current task and API token. */
|
|
/** A saddle card on the management page, with its current task and API token. */
|
|
|
public class SaddleRowView : Object {
|
|
public class SaddleRowView : Object {
|
|
|
public int64 id { get; set; }
|
|
public int64 id { get; set; }
|
|
@@ -21,21 +28,40 @@ namespace ClankerSaddle {
|
|
|
public string token { get; set; default = ""; }
|
|
public string token { get; set; default = ""; }
|
|
|
public Statum.Model.ActionDto? unregister { get; set; }
|
|
public Statum.Model.ActionDto? unregister { get; set; }
|
|
|
public Statum.Model.ActionDto? refresh { get; set; }
|
|
public Statum.Model.ActionDto? refresh { get; set; }
|
|
|
|
|
+ public Statum.Model.ActionDto? attach_capability { get; set; }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** A capability row in the saddles page's capabilities section. */
|
|
|
|
|
+ public class CapabilityRowView : Object {
|
|
|
|
|
+ public int64 id { get; set; }
|
|
|
|
|
+ public string name { get; set; default = ""; }
|
|
|
|
|
+ public int64 saddle_count { get; set; }
|
|
|
|
|
+ public int64 task_count { get; set; }
|
|
|
|
|
+ public Statum.Model.ActionDto? delete { get; set; }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** An option in the attach selects (saddle cards and the task page). */
|
|
|
|
|
+ public class CapabilityOptionView : Object {
|
|
|
|
|
+ public int64 id { get; set; }
|
|
|
|
|
+ public string name { get; set; default = ""; }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/** Public data of the `saddles` slot. */
|
|
/** Public data of the `saddles` slot. */
|
|
|
public class SaddlesPublic : Object {
|
|
public class SaddlesPublic : Object {
|
|
|
public Statum.Model.ActionDto register { get; set; }
|
|
public Statum.Model.ActionDto register { get; set; }
|
|
|
|
|
+ public Statum.Model.ActionDto create_capability { get; set; }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * Entrypoint for /saddles: the saddle inventory. Admin-only (the
|
|
|
|
|
- * authoritative guard); each card carries its current task and the
|
|
|
|
|
- * unregister/refresh actions.
|
|
|
|
|
|
|
+ * Entrypoint for /saddles: the saddle inventory plus the capability
|
|
|
|
|
+ * tag catalogue. Admin-only (the authoritative guard); each card
|
|
|
|
|
+ * carries its current task, capability chips (with remove actions),
|
|
|
|
|
+ * the attach-capability select, and the unregister/refresh actions.
|
|
|
*/
|
|
*/
|
|
|
public class SaddlesEntrypoint : StatumEntrypoint {
|
|
public class SaddlesEntrypoint : StatumEntrypoint {
|
|
|
|
|
|
|
|
private SaddleService saddle_service = Inversion.inject<SaddleService>();
|
|
private SaddleService saddle_service = Inversion.inject<SaddleService>();
|
|
|
|
|
+ private CapabilityService capability_service = Inversion.inject<CapabilityService>();
|
|
|
private InvercargillSql.Orm.OrmSession db = Inversion.inject<InvercargillSql.Orm.OrmSession>();
|
|
private InvercargillSql.Orm.OrmSession db = Inversion.inject<InvercargillSql.Orm.OrmSession>();
|
|
|
private UserService users = Inversion.inject<UserService>();
|
|
private UserService users = Inversion.inject<UserService>();
|
|
|
|
|
|
|
@@ -46,8 +72,46 @@ namespace ClankerSaddle {
|
|
|
return (!)guard;
|
|
return (!)guard;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ var capabilities = yield capability_service.list_capabilities();
|
|
|
|
|
+ var capability_names = new Dictionary<string, string>();
|
|
|
|
|
+ foreach (var capability in capabilities) {
|
|
|
|
|
+ capability_names.set(capability.id.to_string(), capability.name);
|
|
|
|
|
+ }
|
|
|
|
|
+ var saddle_links = yield capability_service.all_saddle_attachments();
|
|
|
|
|
+ var task_links = yield capability_service.all_task_attachments();
|
|
|
|
|
+
|
|
|
|
|
+ var options = new Vector<CapabilityOptionView>();
|
|
|
|
|
+ foreach (var capability in capabilities) {
|
|
|
|
|
+ options.add(new CapabilityOptionView() {
|
|
|
|
|
+ id = capability.id,
|
|
|
|
|
+ name = capability.name
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Per-saddle chips keyed by saddle id, attached to the row
|
|
|
|
|
+ // models via list_rich below (slot lists cannot nest).
|
|
|
|
|
+ var chips_by_saddle = new Dictionary<string, Vector<SaddleCapabilityView>>();
|
|
|
|
|
+ foreach (var link in saddle_links) {
|
|
|
|
|
+ string name = capability_names.get_or_default(link.capability_id.to_string()) ?? "?";
|
|
|
|
|
+ var sealed_link = new SaddleCapabilityActionPrivate() {
|
|
|
|
|
+ saddle_id = link.saddle_id,
|
|
|
|
|
+ link_id = link.id
|
|
|
|
|
+ };
|
|
|
|
|
+ var chips = chips_by_saddle.get_or_default(link.saddle_id.to_string());
|
|
|
|
|
+ if (chips == null) {
|
|
|
|
|
+ chips = new Vector<SaddleCapabilityView>();
|
|
|
|
|
+ chips_by_saddle.set(link.saddle_id.to_string(), chips);
|
|
|
|
|
+ }
|
|
|
|
|
+ ((!)chips).add(new SaddleCapabilityView() {
|
|
|
|
|
+ link_id = link.id,
|
|
|
|
|
+ name = name,
|
|
|
|
|
+ detach = action_registry.author_private<DetachSaddleCapabilityAction, SaddleCapabilityActionPrivate>(sealed_link)
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
var rows = new Vector<SaddleRowView>();
|
|
var rows = new Vector<SaddleRowView>();
|
|
|
foreach (var saddle in yield saddle_service.list_saddles()) {
|
|
foreach (var saddle in yield saddle_service.list_saddles()) {
|
|
|
|
|
+ var sealed = new SaddleActionPrivate() { saddle_id = saddle.id };
|
|
|
var view = new SaddleRowView() {
|
|
var view = new SaddleRowView() {
|
|
|
id = saddle.id,
|
|
id = saddle.id,
|
|
|
name = saddle.name,
|
|
name = saddle.name,
|
|
@@ -55,7 +119,10 @@ namespace ClankerSaddle {
|
|
|
status = saddle.status,
|
|
status = saddle.status,
|
|
|
current_task_id = saddle.current_task_id,
|
|
current_task_id = saddle.current_task_id,
|
|
|
allocated_display = saddle.allocated_at != null
|
|
allocated_display = saddle.allocated_at != null
|
|
|
- ? ((!)saddle.allocated_at).format("%Y-%m-%d %H:%M") : ""
|
|
|
|
|
|
|
+ ? ((!)saddle.allocated_at).format("%Y-%m-%d %H:%M") : "",
|
|
|
|
|
+ unregister = action_registry.author_private<UnregisterSaddleAction, SaddleActionPrivate>(sealed),
|
|
|
|
|
+ refresh = action_registry.author_private<RefreshSaddleStatusAction, SaddleActionPrivate>(sealed),
|
|
|
|
|
+ attach_capability = action_registry.author_private<AttachSaddleCapabilityAction, SaddleActionPrivate>(sealed)
|
|
|
};
|
|
};
|
|
|
string? token = yield saddle_service.token_for_saddle(saddle.id);
|
|
string? token = yield saddle_service.token_for_saddle(saddle.id);
|
|
|
view.token = token ?? "";
|
|
view.token = token ?? "";
|
|
@@ -65,17 +132,48 @@ namespace ClankerSaddle {
|
|
|
.first_async();
|
|
.first_async();
|
|
|
view.current_task_title = task != null ? ((!)task).title : "";
|
|
view.current_task_title = task != null ? ((!)task).title : "";
|
|
|
}
|
|
}
|
|
|
- var sealed = new SaddleActionPrivate() { saddle_id = saddle.id };
|
|
|
|
|
- view.unregister = action_registry.author_private<UnregisterSaddleAction, SaddleActionPrivate>(sealed);
|
|
|
|
|
- view.refresh = action_registry.author_private<RefreshSaddleStatusAction, SaddleActionPrivate>(sealed);
|
|
|
|
|
rows.add(view);
|
|
rows.add(view);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ var capability_rows = new Vector<CapabilityRowView>();
|
|
|
|
|
+ foreach (var capability in capabilities) {
|
|
|
|
|
+ int64 saddle_count = 0;
|
|
|
|
|
+ foreach (var link in saddle_links) {
|
|
|
|
|
+ if (link.capability_id == capability.id) {
|
|
|
|
|
+ saddle_count++;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ int64 task_count = 0;
|
|
|
|
|
+ foreach (var link in task_links) {
|
|
|
|
|
+ if (link.capability_id == capability.id) {
|
|
|
|
|
+ task_count++;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ var sealed_capability = new CapabilityActionPrivate() { capability_id = capability.id };
|
|
|
|
|
+ capability_rows.add(new CapabilityRowView() {
|
|
|
|
|
+ id = capability.id,
|
|
|
|
|
+ name = capability.name,
|
|
|
|
|
+ saddle_count = saddle_count,
|
|
|
|
|
+ task_count = task_count,
|
|
|
|
|
+ delete = action_registry.author_private<DeleteCapabilityAction, CapabilityActionPrivate>(sealed_capability)
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
var pub = new SaddlesPublic();
|
|
var pub = new SaddlesPublic();
|
|
|
pub.register = action_registry.author<RegisterSaddleAction>();
|
|
pub.register = action_registry.author<RegisterSaddleAction>();
|
|
|
|
|
+ pub.create_capability = action_registry.author<CreateCapabilityAction>();
|
|
|
|
|
|
|
|
var props = (PropertyDictionary) GObjectMapping.to_properties(pub);
|
|
var props = (PropertyDictionary) GObjectMapping.to_properties(pub);
|
|
|
- props["saddles"] = StateJson.list<SaddleRowView>(rows.to_immutable_buffer());
|
|
|
|
|
|
|
+ props["saddles"] = StateJson.list_rich<SaddleRowView>(rows.to_immutable_buffer(),
|
|
|
|
|
+ (item, dict) => {
|
|
|
|
|
+ var chips = chips_by_saddle.get_or_default(((SaddleRowView) item).id.to_string());
|
|
|
|
|
+ dict["capabilities"] = chips != null
|
|
|
|
|
+ ? StateJson.list<SaddleCapabilityView>(((!)chips).to_immutable_buffer())
|
|
|
|
|
+ : StateJson.list<SaddleCapabilityView>(
|
|
|
|
|
+ new Vector<SaddleCapabilityView>().to_immutable_buffer());
|
|
|
|
|
+ });
|
|
|
|
|
+ props["capabilities"] = StateJson.list<CapabilityRowView>(capability_rows.to_immutable_buffer());
|
|
|
|
|
+ props["capability_options"] = StateJson.list<CapabilityOptionView>(options.to_immutable_buffer());
|
|
|
|
|
|
|
|
var slot = state_service.new_slot(Scope.PAGE, new State() {
|
|
var slot = state_service.new_slot(Scope.PAGE, new State() {
|
|
|
type_name = "saddles",
|
|
type_name = "saddles",
|