╭ changed ──────╮ │src/ui/Repo.zig│ ╰───────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭────────────────────────────────────────────────────╮ │10 const Focus = xitui.focus.Focus; │ │11 │ │12 pub const Header = @import("./Repo/Header.zig");│ │13 +pub const Files = @import("./Repo/Files.zig"); │ │14 pub const Settings = @import("./Settings.zig"); │ │15 pub const Auth = @import("./Auth.zig"); │ │16 pub const Quit = @import("./Quit.zig"); │ ╰────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────╮ │17 + │ │18 +const Array = ui.RoutablePage.Array(ui.RoutablePage.repo_route_max_len);│ │19 │ │20 header: Header, │ │21 repo: evt.Repo, │ ╰────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────╮ │22 +files: Files, │ │23 settings: Settings,│ │24 auth: Auth, │ │25 quit: Quit, │ ╰───────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────╮ │22 -route_name: ui.RoutablePage.Array(ui.RoutablePage.repo_name_max_len), │ │26 +// the full files route this page renders ("owner/name" or "owner/name/files/<dir>"),│ │27 +// mirrored into current_page when the files tab is selected. │ │28 +route_name: Array, │ │29 +// "owner/name" alone, mirrored when the settings/auth tab is selected. │ │30 +identity: Array, │ │31 │ │32 const Self = @This(); │ │33 │ ╰─────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────╮ │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 => {}, │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯