╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ──────╮ │src/ui/Repo.zig│ ╰───────────────╯ ╭───────────────────────────────────────────────────╮ │39 pub fn init( │ │40 arena: *std.heap.ArenaAllocator, │ │41 session: *ui.Session, │ │37 - name: Array, │ │42 + route: ui.RoutablePage, │ │43 ) !Self { │ │44 const DB = evt.AdminDB; │ │45 const hash_kind = evt.admin_repo_opts.hash;│ ╰───────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │46 const haxy_moment = session.haxy_moment orelse return error.NoMoment; │ │47 │ │43 - // the route is "owner/name" or "owner/name/files/<dir>". │ │48 + // every repo route's stored string starts with "owner/name"; the files │ │49 + // route additionally encodes a directory, the commits route a start oid. │ │50 + const name = switch (route) { │ │51 + .repo, .repo_commits, .repo_settings, .repo_auth => |n| n, │ │52 + else => return error.UnexpectedRoute, │ │53 + }; │ │54 const rf = ui.RoutablePage.RepoFiles.parse(name.slice()) orelse return error.NotFound; │ │55 + const tag = std.meta.activeTag(route); │ │56 + │ │57 + // page state lives in the route only for the tab it targets; the other tabs │ │58 + // open at their root/first page. │ │59 + const files_dir = if (tag == .repo) rf.dir else ""; │ │60 + const commits_start = if (tag == .repo_commits) ui.RoutablePage.repoCommitsStart(name.slice()) else ""; │ │61 │ │62 const found = (try evt.Repo.readByOwnerAndName(DB, hash_kind, haxy_moment, arena, rf.owner, rf.name)) orelse return error.NotFound;│ │63 const repo = found.repo; │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │65 // resolve the creating user so the header can show their name to the left │ │66 // of the repo title. │ │67 const owner = (try evt.User.readById(DB, hash_kind, haxy_moment, arena, repo.user_id)) orelse return error.NotFound; │ │68 + │ │69 + // each tab mirror carries this page's route for that tab; tabs not targeted │ │70 + // by the incoming route fall back to their root/first-page route. │ │71 + const route_name = if (tag == .repo) name else (ui.RoutablePage.repoFilesRoute(rf.identity, "") orelse return error.NotFound).repo; │ │72 + const commits_route_name = if (tag == .repo_commits) name else (ui.RoutablePage.repoCommitsRoute(rf.identity, "") orelse return error.NotFound).repo_commits;│ │73 │ │74 return .{ │ │75 .header = try Header.init(arena, repo.name, owner.name), │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────╮ │76 .repo = repo, │ │77 // the repo's event id is the on-disk repo directory name │ │57 - .files = try Files.init(arena, session, &found.event_id, rf.identity, rf.dir), │ │78 + .files = try Files.init(arena, session, &found.event_id, rf.identity, files_dir), │ │79 + .commits = try Commits.init(arena, session, &found.event_id, rf.identity, commits_start),│ │80 .settings = Settings.init(), │ │81 .auth = Auth.init(), │ │82 .quit = Quit.init(), │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────╮ │61 - .route_name = name, │ │83 + .route_name = route_name, │ │84 + .commits_route_name = commits_route_name, │ │85 .identity = Array.from(rf.identity) orelse return error.NotFound,│ │86 }; │ │87 } │ ╰─────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │119 try stack.children.put(allocator, files_view.getFocus().id, .{ .repo_files = files_view }); │ │120 } │ │121 │ │122 + // commits — the current page of the commit log. │ │123 { │ │124 + var commits_view = try Commits.View.init(allocator, &data.commits, session); │ │125 + errdefer commits_view.deinit(allocator); │ │126 + try stack.children.put(allocator, commits_view.getFocus().id, .{ .repo_commits = commits_view }); │ │127 + } │ │128 + │ │129 + { │ │130 var settings_view = try Settings.View.init(allocator, &data.settings, session); │ │131 errdefer settings_view.deinit(allocator); │ │132 try stack.children.put(allocator, settings_view.getFocus().id, .{ .home_settings = settings_view });│ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │168 if (header.getSelectedIndex()) |index| { │ │169 stack.getFocus().child_id = stack.children.keys()[index]; │ │170 switch (index) { │ │141 - // settings/auth carry only "owner/name"; files carries this │ │142 - // page's directory (route_name) so the url keeps the dir. │ │143 - 1 => self.session.data.current_page = .{ .repo_settings = self.data.identity }, │ │144 - 2 => self.session.data.current_page = .{ .repo_auth = self.data.identity }, │ │171 + // files/commits carry this page's content route (directory / log │ │172 + // page); settings/auth carry only "owner/name". │ │173 + 1 => self.session.data.current_page = .{ .repo_commits = self.data.commits_route_name },│ │174 + 2 => self.session.data.current_page = .{ .repo_settings = self.data.identity }, │ │175 + 3 => self.session.data.current_page = .{ .repo_auth = self.data.identity }, │ │176 // the quit tab is tty-only and not a route, so leave current_page │ │177 // alone (nothing to mirror into the url). │ │147 - 3 => {}, │ │178 + 4 => {}, │ │179 else => self.session.data.current_page = .{ .repo = self.data.route_name }, │ │180 } │ │181 } │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────╮ │209 if (child.stack.getSelected()) |selected_widget| { │ │210 const at_top = switch (selected_widget.*) { │ │211 .repo_files => |*v| v.getSelectedIndex() == 0, │ │212 + .repo_commits => |*v| v.getSelectedIndex() == 0, │ │213 .home_settings => |*v| v.getSelectedIndex() == 0,│ │214 .home_auth => |*v| v.getSelectedIndex() == 0, │ │215 .quit => |*v| v.getSelectedIndex() == 0, │ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─────────────╮ │src/ui/Repo/Header.zig│ ╰──────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │90 const identity = try std.fmt.allocPrint(aa, "{s}/{s}", .{ data.owner_name, data.name }); │ │91 const files_route = ui.RoutablePage.repoFilesRoute(identity, "") orelse return error.RouteTooLong; │ │92 const files_link = try std.fmt.allocPrint(aa, "a:{s}", .{try files_route.urlAlloc(session.page_arena)}); │ │93 + const commits_route = ui.RoutablePage.repoCommitsRoute(identity, "") orelse return error.RouteTooLong; │ │94 + const commits_link = try std.fmt.allocPrint(aa, "a:{s}", .{try commits_route.urlAlloc(session.page_arena)}); │ │95 const settings_link = try std.fmt.allocPrint(aa, "a:/repo/{s}/{s}/settings", .{ data.owner_name, data.name });│ │96 const auth_link = try std.fmt.allocPrint(aa, "a:/repo/{s}/{s}/auth", .{ data.owner_name, data.name }); │ │97 │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │109 }); │ │110 } │ │111 │ │112 + // commits tab │ │113 + { │ │114 + var text_box = try wgt.TextBox(ui.Widget).init(allocator, "commits", .{ .border_style = .single, .rounded_corners = true, .wrap_kind = .none });│ │115 + errdefer text_box.deinit(allocator); │ │116 + text_box.getFocus().focusable = true; │ │117 + text_box.getFocus().kind = .{ .custom = commits_link }; │ │118 + try tab_ids.put(allocator, text_box.getFocus().id, {}); │ │119 + try box.children.put(allocator, text_box.getFocus().id, .{ │ │120 + .widget = .{ .text_box = text_box }, │ │121 + .rect = null, │ │122 + .min_size = .{ .width = "commits".len + 2, .height = null }, │ │123 + }); │ │124 + } │ │125 + │ │126 // spacer pushes settings + auth to the right │ │127 { │ │128 var spacer = ui.Spacer.init(); │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯