╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ──────╮ │src/ui/Repo.zig│ ╰───────────────╯ ╭───────────────────────────────────────────────────────────────────────╮ │34 pub fn init( │ │35 arena: *std.heap.ArenaAllocator, │ │28 - haxy_moment: evt.AdminDB.HashMap(.read_only), │ │29 - name: ui.RoutablePage.Array(ui.RoutablePage.repo_name_max_len),│ │36 + session: *ui.Session, │ │37 + name: Array, │ │38 ) !Self { │ │39 const DB = evt.AdminDB; │ │40 const hash_kind = evt.admin_repo_opts.hash; │ ╰───────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │41 + const haxy_moment = session.haxy_moment orelse return error.NoMoment; │ │42 │ │34 - // the route name is "username/reponame"; split it to look the repo up by │ │35 - // its owner and name. │ │36 - const path = name.slice(); │ │37 - const slash = std.mem.indexOfScalar(u8, path, '/') orelse return error.NotFound; │ │38 - const owner_name = path[0..slash]; │ │39 - const repo_name = path[slash + 1 ..]; │ │43 + // the route is "owner/name" or "owner/name/files/<dir>". │ │44 + const rf = ui.RoutablePage.RepoFiles.parse(name.slice()) orelse return error.NotFound; │ │45 │ │41 - const repo = (try evt.Repo.readByOwnerAndName(DB, hash_kind, haxy_moment, arena, owner_name, repo_name)) orelse return error.NotFound;│ │46 + const found = (try evt.Repo.readByOwnerAndName(DB, hash_kind, haxy_moment, arena, rf.owner, rf.name)) orelse return error.NotFound; │ │47 + const repo = found.repo; │ │48 │ │49 // resolve the creating user so the header can show their name to the left │ │50 // of the repo title. │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────╮ │53 return .{ │ │54 .header = try Header.init(arena, repo.name, owner.name), │ │55 .repo = repo, │ │56 + // 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),│ │58 .settings = Settings.init(), │ │59 .auth = Auth.init(), │ │60 .quit = Quit.init(), │ ╰──────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────╮ │61 .route_name = name, │ │62 + .identity = Array.from(rf.identity) orelse return error.NotFound,│ │63 }; │ │64 } │ │65 │ ╰─────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │89 var stack = wgt.Stack(ui.Widget).init(); │ │90 errdefer stack.deinit(allocator); │ │91 │ │83 - // files — the default tab. blank for now: an empty list renders │ │84 - // nothing below the header. │ │92 + // files — the default tab: the current directory's listing. │ │93 { │ │86 - var list = try ui.FlowBox.Scroll.init(allocator, .{}); │ │87 - errdefer list.deinit(allocator); │ │88 - try stack.children.put(allocator, list.getFocus().id, .{ .flow_box_scroll = list }); │ │94 + var files_view = try Files.View.init(allocator, &data.files, session); │ │95 + errdefer files_view.deinit(allocator); │ │96 + try stack.children.put(allocator, files_view.getFocus().id, .{ .repo_files = files_view });│ │97 } │ │98 │ │99 { │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────╮ │137 // rather than navigating. │ │138 if (header.getSelectedIndex()) |index| { │ │139 stack.getFocus().child_id = stack.children.keys()[index]; │ │132 - const name = self.data.route_name; │ │140 switch (index) { │ │134 - 1 => self.session.data.current_page = .{ .repo_settings = name }, │ │135 - 2 => self.session.data.current_page = .{ .repo_auth = name }, │ │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 }, │ │145 // the quit tab is tty-only and not a route, so leave current_page │ │146 // alone (nothing to mirror into the url). │ │147 3 => {}, │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────╮ │139 - else => self.session.data.current_page = .{ .repo = name }, │ │148 + else => self.session.data.current_page = .{ .repo = self.data.route_name },│ │149 } │ │150 } │ │151 try self.box.build(allocator, constraint, root_focus); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────╮ │177 .stack => { │ │178 if (child.stack.getSelected()) |selected_widget| { │ │179 const at_top = switch (selected_widget.*) { │ │171 - .flow_box_scroll => |*v| v.getSelectedIndex() == 0,│ │180 + .repo_files => |*v| v.getSelectedIndex() == 0, │ │181 .home_settings => |*v| v.getSelectedIndex() == 0, │ │182 .home_auth => |*v| v.getSelectedIndex() == 0, │ │183 .quit => |*v| v.getSelectedIndex() == 0, │ ╰────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─────────────╮ │src/ui/Repo/Header.zig│ ╰──────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │85 │ │86 // every tab link is repo-scoped so selecting one stays on this page — │ │87 // switching its stack and updating the url — instead of navigating to │ │88 - // the global settings or auth pages │ │89 - const files_link = try std.fmt.allocPrint(aa, "a:/repo/{s}/{s}", .{ data.owner_name, data.name }); │ │88 + // the global settings or auth pages. the files tab routes through the │ │89 + // shared helper so the /repo/.../files url format lives in one place. │ │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 settings_link = try std.fmt.allocPrint(aa, "a:/repo/{s}/{s}/settings", .{ data.owner_name, data.name });│ │94 const auth_link = try std.fmt.allocPrint(aa, "a:/repo/{s}/{s}/auth", .{ data.owner_name, data.name }); │ │95 │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ added ──────────────╮ │src/ui/Repo/Files.zig│ ╰─────────────────────╯ ╭──────╮ │next →│ ╰──────╯