╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ───────────────╮ │src/ui/Repo/Branches.zig│ ╰────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │67 const source = ui.RepoSource{ .path = path, .repo_kind = .xit }; │ │68 if (!try source.hasBranch(io, allocator, branch)) return .not_found; │ │69 + // the default branch is HEAD, which the repo page, the commits tab and clones follow │ │70 + if (action == .default) { │ │71 + var any_repo = try rp.AnyRepo(.xit, .{}).open(io, allocator, .{ .path = path }); │ │72 + defer any_repo.deinit(io, allocator); │ │73 + switch (any_repo) { │ │74 + inline else => |*repo| try repo.resetAdd(io, .{ .ref = .{ .kind = .head, .name = branch } }), │ │75 + } │ │76 + return null; │ │77 + } │ │78 } │ │79 evt.protectBranch(io, allocator, users_dir, &owner_id, &repo_id, branch, action == .protect, actor.author) catch |err| {│ │80 if (ui.Session.FormFeedback.BranchesFailure.fromError(err)) |failure| return failure; │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────╮ │89 session: *ui.Session, │ │90 │ │91 const about_index = 0; │ │78 - const add_index = 1; │ │92 + const default_index = 1; │ │93 + const add_index = 2; │ │94 │ │95 pub fn init(allocator: std.mem.Allocator, data: *const Self, session: *ui.Session) !View {│ │96 const aa = session.page_arena.allocator(); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │101 │ │102 { │ │103 var about = try ui.widget.initAbout(allocator, &.{ │ │104 + .{ .text = "the default branch is what the repo page shows and clones check out.\n" },│ │105 .{ .text = "protected branches can't be deleted or force pushed.\n" }, │ │106 .{ .text = "owners", .style = .{ .bold = true } }, │ │107 .{ .text = " can push directly to them.\n" }, │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │111 errdefer about.deinit(allocator); │ │112 try box.children.put(allocator, about.getFocus().id, .{ .widget = .{ .box = about }, .rect = null, .min_size = null }); │ │113 } │ │ 98 - { │ │ 99 - var add = try wgt.Box(ui.Widget).init(allocator, .{ .border = null, .direction = .horiz }); │ │100 - errdefer add.deinit(allocator); │ │101 - add.getFocus().kind = .{ .custom = try aa.print("form:{s}/protect", .{url}) }; │ │102 - var name = try wgt.TextInput.init(allocator, .{ .top_label = .{ .text = " add a protected branch " }, .name = "name", .visible_width = 30, .round_corners = true, .render_content = session.is_terminal });│ │103 - errdefer name.deinit(allocator); │ │104 - name.getFocus().mode = .all; │ │105 - if (session.formFeedback(.repo_branches)) |saved| try name.setContent(allocator, saved.name); │ │106 - add.getFocus().child_id = name.getFocus().id; │ │107 - try add.children.put(allocator, name.getFocus().id, .{ .widget = .{ .text_input = name }, .rect = null, .min_size = .{ .width = null, .height = 3 } }); │ │108 - try box.children.put(allocator, add.getFocus().id, .{ .widget = .{ .box = add }, .rect = null, .min_size = .{ .width = null, .height = 3 } }); │ │109 - } │ │114 + // a failed submission keeps its text in the input it came from │ │115 + const saved = session.formFeedback(.repo_branches); │ │116 + try addFormRow(allocator, &box, session, try aa.print("form:{s}/default", .{url}), " default branch ", if (saved) |value| (if (value.default) value.name else data.default_branch) else data.default_branch); │ │117 + try addFormRow(allocator, &box, session, try aa.print("form:{s}/protect", .{url}), " add a protected branch ", if (saved) |value| (if (value.default) "" else value.name) else ""); │ │118 │ │119 // the branch rows follow the add row in the same box │ │120 for (data.names) |branch| { │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │161 return &self.scroll.child.center.child.box; │ │162 } │ │163 │ │156 - fn addBox(self: *View) *wgt.Box(ui.Widget) { │ │157 - return &self.contentBox().children.values()[add_index].widget.box; │ │164 + // a row holding one text input in its own form │ │165 + fn addFormRow(allocator: std.mem.Allocator, box: *wgt.Box(ui.Widget), session: *ui.Session, form: []const u8, label: []const u8, content: []const u8) !void { │ │166 + var row = try wgt.Box(ui.Widget).init(allocator, .{ .border = null, .direction = .horiz }); │ │167 + errdefer row.deinit(allocator); │ │168 + row.getFocus().kind = .{ .custom = form }; │ │169 + var name = try wgt.TextInput.init(allocator, .{ .top_label = .{ .text = label }, .name = "name", .visible_width = 30, .round_corners = true, .render_content = session.is_terminal });│ │170 + errdefer name.deinit(allocator); │ │171 + name.getFocus().mode = .all; │ │172 + try name.setContent(allocator, content); │ │173 + row.getFocus().child_id = name.getFocus().id; │ │174 + try row.children.put(allocator, name.getFocus().id, .{ .widget = .{ .text_input = name }, .rect = null, .min_size = .{ .width = null, .height = 3 } }); │ │175 + try box.children.put(allocator, row.getFocus().id, .{ .widget = .{ .box = row }, .rect = null, .min_size = .{ .width = null, .height = 3 } }); │ │176 } │ │177 │ │160 - fn nameInput(self: *View) *wgt.TextInput { │ │161 - return &self.addBox().children.values()[0].widget.text_input; │ │178 + fn formInput(self: *View, index: usize) *wgt.TextInput { │ │179 + return &self.contentBox().children.values()[index].widget.box.children.values()[0].widget.text_input; │ │180 } │ │181 │ │164 - fn addActive(self: *View) bool { │ │165 - return self.contentBox().getFocus().child_id == self.addBox().getFocus().id; │ │182 + // the form row with focus, if any │ │183 + fn activeForm(self: *View) ?usize { │ │184 + const rows = self.contentBox(); │ │185 + const index = rows.children.getIndex(rows.getFocus().child_id orelse return null) orelse return null; │ │186 + return if (index == default_index or index == add_index) index else null; │ │187 } │ │188 │ │189 fn aboutBox(self: *View) *wgt.Box(ui.Widget) { │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │192 │ │193 pub fn build(self: *View, allocator: std.mem.Allocator, constraint: layout.Constraint, root_focus: *Focus) !void {│ │194 self.clearGrid(); │ │174 - try self.labelAddInput(root_focus); │ │195 + try self.labelInputs(root_focus); │ │196 ui.widget.markAboutFocused(self.aboutBox(), root_focus); │ │197 // the window's height as a minimum centers rows that fit │ │198 try self.scroll.build(allocator, .{ │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │201 }, root_focus); │ │202 } │ │203 │ │183 - // label the add input for this frame and register it for the web form │ │184 - // handling │ │185 - fn labelAddInput(self: *View, root_focus: *Focus) !void { │ │186 - const name = self.nameInput(); │ │187 - const failure = if (self.session.formFeedback(.repo_branches)) |saved| saved.failure else null; │ │188 - name.options.top_label.text = if (failure) |value| switch (value) { │ │189 - .not_found => " add a protected branch (not found) ", │ │190 - .already_added => " add a protected branch (already added) ", │ │191 - .too_many => " add a protected branch (too many branches) ", │ │192 - .too_long => " add a protected branch (name too long) ", │ │193 - } else " add a protected branch "; │ │194 - name.options.bottom_label.text = if (root_focus.grandchild_id == name.getFocus().id) " press enter " else ""; │ │195 - try self.session.text_inputs.put(self.session.arena.allocator(), name.getFocus().id, name); │ │204 + // label the inputs for this frame and register them for the web form │ │205 + // handling. a failure labels the input it came from. │ │206 + fn labelInputs(self: *View, root_focus: *Focus) !void { │ │207 + const saved = self.session.formFeedback(.repo_branches); │ │208 + for ([_]usize{ default_index, add_index }) |index| { │ │209 + const name = self.formInput(index); │ │210 + const default = index == default_index; │ │211 + const failure = if (saved) |value| (if (value.default == default) value.failure else null) else null; │ │212 + name.options.top_label.text = if (failure) |value| switch (value) { │ │213 + .not_found => if (default) " default branch (not found) " else " add a protected branch (not found) ", │ │214 + .already_added => " add a protected branch (already added) ", │ │215 + .too_many => " add a protected branch (too many branches) ", │ │216 + .too_long => if (default) " default branch (name too long) " else " add a protected branch (name too long) ",│ │217 + } else if (default) " default branch " else " add a protected branch "; │ │218 + name.options.bottom_label.text = if (root_focus.grandchild_id == name.getFocus().id) " press enter " else ""; │ │219 + try self.session.text_inputs.put(self.session.arena.allocator(), name.getFocus().id, name); │ │220 + } │ │221 } │ │222 │ │223 pub fn input(self: *View, allocator: std.mem.Allocator, key: Key, root_focus: *Focus) !void { │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │199 - // the add row keeps every key but the vertical ones, which move between rows │ │200 - if (self.addActive() and inp.vertDirection(key) == .none) { │ │224 + // a form row keeps every key but the vertical ones, which move between rows │ │225 + if (self.activeForm()) |index| if (inp.vertDirection(key) == .none) { │ │226 if (key == .enter) { │ │202 - const name = try self.nameInput().text(allocator); │ │227 + const name = try self.formInput(index).text(allocator); │ │228 defer allocator.free(name); │ │204 - try self.act(allocator, .protect, name); │ │205 - } else try self.nameInput().input(allocator, key, root_focus); │ │229 + try self.act(allocator, if (index == default_index) .default else .protect, name); │ │230 + } else try self.formInput(index).input(allocator, key, root_focus); │ │231 return; │ │207 - } │ │232 + }; │ │233 │ │234 const rows = self.contentBox(); │ │235 const current = rows.children.getIndex(rows.getFocus().child_id orelse return) orelse return;│ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │240 switch (key) { │ │241 .arrow_left, .arrow_right => _ = ui.widget.moveInSelectedRow(rows, root_focus, key == .arrow_right),│ │242 else => { │ │218 - // the text and the add row sit above the rows, and a row's name is a link the host follows │ │243 + // the text and the form rows sit above the rows, and a row's name is a link the host follows │ │244 if (current <= add_index) return; │ │245 const row_box = &rows.children.values()[current].widget.box; │ │246 const button_id = row_box.getFocus().child_id orelse return; │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │257 const users_dir = self.session.users_dir orelse return; │ │258 const actor = (try self.session.authorize(self.data.identity, .owner, null)) orelse return; │ │259 if (try perform(io, allocator, users_dir, actor, self.data.identity, action, branch)) |failure| { │ │235 - self.session.data.form_feedback = .{ .repo_branches = .{ .failure = failure, .name = try self.session.arena.allocator().dupe(u8, branch) } }; │ │260 + self.session.data.form_feedback = .{ .repo_branches = .{ .failure = failure, .name = try self.session.arena.allocator().dupe(u8, branch), .default = action == .default } };│ │261 return; │ │262 } │ │263 try self.session.navigate(ui.RoutablePage.repoBranchesRoute(self.data.identity) orelse return error.RouteTooLong); │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯