╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ─────────────╮ │src/ui/Repo/Issues.zig│ ╰──────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │442 var form = try initIssueForm(allocator, action, issue); │ │443 errdefer form.deinit(allocator); │ │444 try stack.children.put(allocator, form.getFocus().id, .{ .box = form }); │ │445 + } else { │ │446 + var message = try ui.Unauthorized.View.init(allocator); │ │447 + errdefer message.deinit(allocator); │ │448 + try stack.children.put(allocator, message.getFocus().id, .{ .unauthorized = message });│ │449 } │ │450 │ │451 // the stack starts on the page's view. │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────╮ │615 return &self.viewStack().children.values()[tags_view_index].tag_flow;│ │616 } │ │617 │ │613 - // the new-issue (or edit) form inside the stack. │ │614 - fn issueForm(self: *View) *wgt.Box(ui.Widget) { │ │615 - return &self.viewStack().children.values()[form_view_index].box; │ │618 + // the new-issue (or edit) form inside the stack, or null when the │ │619 + // unauthorized view stands in for it. │ │620 + fn issueForm(self: *View) ?*wgt.Box(ui.Widget) { │ │621 + return switch (self.viewStack().children.values()[form_view_index]) {│ │622 + .box => |*box| box, │ │623 + else => null, │ │624 + }; │ │625 } │ │626 │ │627 fn listScroll(self: *View, index: usize) *wgt.Scroll(ui.Widget) { │ ╰──────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │781 // refresh the form inputs' entries in the session's focus-id -> input │ │782 // map with this frame's addresses, so the web/wasm form handling can │ │783 // find them by focus id │ │775 - const form = self.issueForm(); │ │776 - const inputs_arena = self.session.arena.allocator(); │ │777 - for (form.children.values()) |*child| switch (child.widget) { │ │778 - .text_input => |*ti| try self.session.text_inputs.put(inputs_arena, ti.getFocus().id, ti), │ │779 - else => {}, │ │780 - }; │ │784 + if (self.issueForm()) |form| { │ │785 + const inputs_arena = self.session.arena.allocator(); │ │786 + for (form.children.values()) |*child| switch (child.widget) { │ │787 + .text_input => |*ti| try self.session.text_inputs.put(inputs_arena, ti.getFocus().id, ti), │ │788 + else => {}, │ │789 + }; │ │790 │ │782 - // on an edit page, register the issue's content as the inputs' │ │783 - // page-constant initial values, which the web overlay renders │ │784 - // into them. │ │785 - if (self.data.view == .edit) { │ │786 - if (self.data.selectedIssue()) |entry| { │ │787 - const fields = form.children.values(); │ │788 - try self.session.input_values.put(inputs_arena, fields[title_field_index].widget.text_input.getFocus().id, entry.issue.event.title); │ │789 - try self.session.input_values.put(inputs_arena, fields[tags_field_index].widget.text_input.getFocus().id, entry.issue.event.tags); │ │790 - try self.session.input_values.put(inputs_arena, fields[description_field_index].widget.text_input.getFocus().id, entry.issue.event.description); │ │791 + // on an edit page, register the issue's content as the inputs' │ │792 + // page-constant initial values, which the web overlay renders │ │793 + // into them. │ │794 + if (self.data.view == .edit) { │ │795 + if (self.data.selectedIssue()) |entry| { │ │796 + const fields = form.children.values(); │ │797 + try self.session.input_values.put(inputs_arena, fields[title_field_index].widget.text_input.getFocus().id, entry.issue.event.title); │ │798 + try self.session.input_values.put(inputs_arena, fields[tags_field_index].widget.text_input.getFocus().id, entry.issue.event.tags); │ │799 + try self.session.input_values.put(inputs_arena, fields[description_field_index].widget.text_input.getFocus().id, entry.issue.event.description);│ │800 + } │ │801 } │ │802 } │ │803 │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │833 row.getFocus().kind = .container; │ │834 } │ │835 if (!description_page) { │ │826 - const action: []const u8 = switch (entry.issue.event.status) { │ │827 - .open => "close", │ │828 - .closed => "open", │ │829 - }; │ │836 const row = self.toolRow(index); │ │837 const pa = self.session.page_arena.allocator(); │ │832 - row.getFocus().kind = .{ .custom = if (self.data.identity.len == 0) │ │833 - try std.fmt.allocPrint(pa, "form:/issues/{s}/{s}", .{ entry.id, action }) │ │834 - else │ │835 - try std.fmt.allocPrint(pa, "form:/repo/{s}/issues/{s}/{s}", .{ self.data.identity, entry.id, action }) };│ │838 │ │839 { │ │840 var spacer = try ui.Spacer.init(allocator); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │842 try row.children.put(allocator, spacer.getFocus().id, .{ .widget = .{ .spacer = spacer }, .rect = null, .min_size = null }); │ │843 } │ │844 │ │843 - { │ │845 + // a logged-out session can't flip the status, so it gets no │ │846 + // open/close button (and no form action on the row) │ │847 + if (self.session.data.is_local or self.session.data.user_id != null) { │ │848 + const action: []const u8 = switch (entry.issue.event.status) { │ │849 + .open => "close", │ │850 + .closed => "open", │ │851 + }; │ │852 + row.getFocus().kind = .{ .custom = if (self.data.identity.len == 0) │ │853 + try std.fmt.allocPrint(pa, "form:/issues/{s}/{s}", .{ entry.id, action }) │ │854 + else │ │855 + try std.fmt.allocPrint(pa, "form:/repo/{s}/issues/{s}/{s}", .{ self.data.identity, entry.id, action }) }; │ │856 + │ │857 var button = try wgt.TextBox(ui.Widget).init(allocator, action, .{ .border_style = .single, .rounded_corners = true, .wrap_kind = .none });│ │858 errdefer button.deinit(allocator); │ │859 button.getFocus().focusable = true; │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │873 try row.children.put(allocator, button.getFocus().id, .{ .widget = .{ .text_box = button }, .rect = null, .min_size = .{ .width = "edit".len + 2, .height = null } });│ │874 } │ │875 │ │876 + // the open/close button when present, the edit button otherwise │ │877 row.getFocus().child_id = row.children.keys()[button_in_row_index]; │ │878 } │ │879 │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │965 if (inp.vertDirection(key) == .down) { │ │966 const enterable = if (self.viewStack().getSelected()) |selected| switch (selected.*) {│ │967 .tag_flow => |*tf| tf.text_boxes.items.len > 0, │ │968 + // nothing focusable in the unauthorized view │ │969 + .unauthorized => false, │ │970 else => true, │ │971 } else false; │ │972 if (enterable) root_focus.setFocus(self.box.children.keys()[stack_index]); │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1045 // arrows cross between the buttons and to the neighboring widgets. │ │1046 fn toolRowInput(self: *View, allocator: std.mem.Allocator, index: usize, key: Key, root_focus: *Focus) !void { │ │1047 const row = self.toolRow(index); │ │1032 - const on_edit = if (row.getFocus().child_id) |cid| row.children.getIndex(cid) == edit_in_row_index else false; │ │1048 + // the edit button is always the row's last child; the open/close │ │1049 + // button before it is only there when the session may author events │ │1050 + const edit_index = row.children.count() - 1; │ │1051 + const on_edit = if (row.getFocus().child_id) |cid| row.children.getIndex(cid) == edit_index else false; │ │1052 switch (key) { │ │1034 - .arrow_left => if (on_edit) root_focus.setFocus(row.children.keys()[button_in_row_index]) else try self.focusList(index, root_focus), │ │1035 - .arrow_right => if (!on_edit) root_focus.setFocus(row.children.keys()[edit_in_row_index]), │ │1053 + .arrow_left => if (on_edit and self.statusButton(index) != null) root_focus.setFocus(row.children.keys()[button_in_row_index]) else try self.focusList(index, root_focus),│ │1054 + .arrow_right => if (!on_edit) root_focus.setFocus(row.children.keys()[edit_index]), │ │1055 .arrow_up => self.focusHeader(root_focus), │ │1056 .arrow_down => self.focusTitle(index, root_focus), │ │1057 .enter => if (!on_edit) try self.toggleIssueStatus(allocator, index), │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1143 // title crosses into the header tabs. the multiline description keeps │ │1144 // enter and any up/down that has a row to move to. │ │1145 fn formInput(self: *View, allocator: std.mem.Allocator, key: Key, root_focus: *Focus) !void {│ │1127 - const form = self.issueForm(); │ │1146 + const form = self.issueForm() orelse return; │ │1147 const cid = form.getFocus().child_id orelse return; │ │1148 const cur = form.children.getIndex(cid) orelse return; │ │1149 │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1196 const src = self.data.repo_source orelse return; │ │1197 const author_email = (try self.session.eventAuthorEmail()) orelse return; │ │1198 │ │1180 - const form = self.issueForm(); │ │1199 + const form = self.issueForm() orelse return; │ │1200 const title_input = &form.children.values()[title_field_index].widget.text_input; │ │1201 const tags_input = &form.children.values()[tags_field_index].widget.text_input; │ │1202 const description_input = &form.children.values()[description_field_index].widget.text_input;│ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯