╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ─────────────╮ │src/ui/Repo/Header.zig│ ╰──────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────╮ │168 errdefer auth_tab.deinit(allocator); │ │169 auth_tab.text_box.getFocus().kind = .{ .custom = auth_link }; │ │170 try tab_ids.put(allocator, auth_tab.getFocus().id, {}); │ │171 + if (std.mem.eql(u8, auth_link, current_link)) selected_tab = auth_tab.getFocus().id;│ │172 try box.children.put(allocator, auth_tab.getFocus().id, .{ │ │173 .widget = .{ .auth_tab = auth_tab }, │ │174 .rect = null, │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────╮ │192 } │ │193 │ │194 var self = View{ .box = box, .data = data, .tab_ids = tab_ids }; │ │181 - // open on the tab named by the current route │ │182 - self.getFocus().child_id = self.tab_ids.keys()[ │ │183 - switch (session.data.current_page) { │ │184 - .repo_commits => 1, │ │185 - .repo_settings => 2, │ │186 - .repo_auth => 3, │ │187 - else => 0, │ │188 - } │ │189 - ]; │ │195 + self.getFocus().child_id = selected_tab orelse self.tab_ids.keys()[0];│ │196 return self; │ │197 } │ │198 │ ╰───────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──────────╮ │src/ui/Settings.zig│ ╰───────────────────╯ ╭─────────────────────────────────────────╮ │10 │ │11 const Self = @This(); │ │12 │ │13 -const label_on = "turn off ANSI art";│ │14 -const label_off = "turn on ANSI art";│ │15 - │ │13 pub fn init() Self { │ │14 return .{}; │ │15 } │ ╰─────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │20 session: *ui.Session, │ │21 button_id: usize, │ │22 │ │23 + // the box holds the toggle button and the logged-out message; build() shows │ │24 + // exactly one of them based on whether a user is logged in. │ │25 const button_index: usize = 0; │ │26 + const message_index: usize = 1; │ │27 │ │28 pub fn init(allocator: std.mem.Allocator, data: *const Self, session: *ui.Session) !View { │ │29 - const logged_in = session.data.user_id != null; │ │30 - │ │29 var box = try wgt.Box(ui.Widget).init(allocator, .{ .border_style = null, .rounded_corners = true, .direction = .vert }); │ │30 errdefer box.deinit(allocator); │ │33 - // when logged in, the toggle posts to a page-scoped /ansi path so the │ │34 - // server sends us back to this page's settings tab; logged out, the │ │35 - // empty action blocks the POST and the input goes to the TUI instead. │ │36 - box.getFocus().kind = .{ .custom = if (!logged_in) "form:" else switch (session.data.current_page) { │ │37 - .user, .user_settings, .user_auth => |name| try std.fmt.allocPrint(session.page_arena.allocator(), "form:/user/{s}/ansi", .{name.slice()}), │ │38 - else => "form:/ansi", │ │39 - } }; │ │31 + // on the web the toggle is an overlay form posting to a page-scoped /ansi │ │32 + // path; only mark it a form when logged in so a logged-out page stays │ │33 + // blank (the web rebuilds per request, so this is correct there; the tty │ │34 + // ignores the form kind and toggles in-process via input). │ │35 + if (session.data.user_id != null) { │ │36 + box.getFocus().kind = .{ .custom = switch (session.data.current_page) { │ │37 + .user, .user_settings, .user_auth => |name| try std.fmt.allocPrint(session.page_arena.allocator(), "form:/user/{s}/ansi", .{name.slice()}),│ │38 + else => "form:/ansi", │ │39 + } }; │ │40 + } │ │41 │ │42 var button_id: usize = undefined; │ │43 { │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │43 - var button = try wgt.TextBox(ui.Widget).init(allocator, labelFor(session), .{ .border_style = .single, .rounded_corners = true, .wrap_kind = .none }); │ │44 + var button = try wgt.TextBox(ui.Widget).init(allocator, ansiLabel(session), .{ .border_style = .single, .rounded_corners = true, .wrap_kind = .none });│ │45 errdefer button.deinit(allocator); │ │45 - button.getFocus().focusable = true; │ │46 // the renderer distinguishes plain clickables from buttons that │ │47 // should POST to a server route by this kind. │ │48 button.getFocus().kind = .{ .custom = "submit" }; │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │54 }); │ │55 } │ │56 │ │57 + // the logged-out message (shown in place of the toggle) │ │58 + { │ │59 + const logged_out_message = │ │60 + \\you must be logged in to see settings :( │ │61 + \\ │ │62 + \\instead, here's an emoticon from the early 2000s: │ │63 + \\<('o'<) ^( '-' )^ (>'o')> │ │64 + \\ │ │65 + \\hope it helps. │ │66 + ; │ │67 + │ │68 + var message = try wgt.TextBox(ui.Widget).init(allocator, logged_out_message, .{ .border_style = null, .rounded_corners = false, .wrap_kind = .word });│ │69 + errdefer message.deinit(allocator); │ │70 + try box.children.put(allocator, message.getFocus().id, .{ │ │71 + .widget = .{ .text_box = message }, │ │72 + .rect = null, │ │73 + .min_size = null, │ │74 + }); │ │75 + } │ │76 + │ │77 box.getFocus().child_id = button_id; │ │78 │ │79 return .{ │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 89 } │ │ 90 │ │ 91 pub fn build(self: *View, allocator: std.mem.Allocator, constraint: layout.Constraint, root_focus: *Focus) !void {│ │ 92 + // show the toggle when logged in, else the message. decided per-frame │ │ 93 + // since the tty doesn't rebuild the page on login. a zero max height │ │ 94 + // hides the inactive one (it builds to nothing). │ │ 95 + const logged_in = self.session.data.user_id != null; │ │ 96 const box = &self.center.child.box; │ │ 73 - if (box.children.values().len > 0) { │ │ 74 - const button = &box.children.values()[button_index].widget.text_box; │ │ 75 - button.box.children.values()[0].widget.text.content = labelFor(self.session); │ │ 76 - } │ │ 97 + const button = &box.children.values()[button_index].widget.text_box; │ │ 98 + if (logged_in) button.box.children.values()[0].widget.text.content = ansiLabel(self.session); │ │ 99 + button.getFocus().focusable = logged_in; │ │100 + const hidden = layout.MaybeSize{ .width = null, .height = 0 }; │ │101 + box.children.values()[button_index].max_size = if (logged_in) null else hidden; │ │102 + box.children.values()[message_index].max_size = if (logged_in) hidden else null; │ │103 try self.center.build(allocator, constraint, root_focus); │ │104 } │ │105 │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │106 pub fn input(self: *View, allocator: std.mem.Allocator, key: inp.Key, root_focus: *Focus) !void {│ │107 _ = allocator; │ │108 + if (self.session.data.user_id == null) return; // blank/disabled when logged out │ │109 switch (key) { │ │110 .enter => try self.toggle(), │ │111 .mouse => |mouse| { │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────╮ │147 } │ │148 }; │ │149 │ │123 -fn labelFor(session: *const ui.Session) []const u8 { │ │150 +fn ansiLabel(session: *const ui.Session) []const u8 { │ │151 + const label_on = "turn off ANSI art"; │ │152 + const label_off = "turn on ANSI art"; │ │153 return if (session.data.enable_ansi) label_on else label_off;│ │154 } │ │155 │ ╰──────────────────────────────────────────────────────────────────────╯ ╭ changed ──────╮ │src/ui/User.zig│ ╰───────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────╮ │169 if (header.getSelectedIndex()) |index| { │ │170 stack.getFocus().child_id = stack.children.keys()[index]; │ │171 const name = self.data.route_name; │ │172 - switch (index) { │ │173 - 1 => self.session.data.current_page = .{ .user_settings = name }, │ │174 - 2 => self.session.data.current_page = .{ .user_auth = name }, │ │172 + switch (std.meta.activeTag(stack.children.values()[index])) { │ │173 + .home_settings => self.session.data.current_page = .{ .user_settings = name },│ │174 + .home_auth => self.session.data.current_page = .{ .user_auth = name }, │ │175 // the quit tab is tty-only and not a route, so leave current_page │ │176 // alone (nothing to mirror into the url). │ │177 - 3 => {}, │ │177 + .quit => {}, │ │178 + // the repos list (the default tab) │ │179 else => self.session.data.current_page = .{ .user = name }, │ │180 } │ │181 } │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯