╭ Home.zig ─────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
1 const std = @import("std"); 2 const ui = @import("../ui.zig"); 3 const xit = @import("xit"); 4 const rp = xit.repo; 5 const hash = xit.hash; 6 const xitui = xit.xitui; 7 const wgt = xitui.widget; 8 const layout = xitui.layout; 9 const inp = xitui.input; 10 const Grid = xitui.grid.Grid; 11 const Focus = xitui.focus.Focus; 12 13 pub const Users = @import("./Home/Users.zig"); 14 pub const Repos = @import("./Home/Repos.zig"); 15 pub const Header = @import("./Home/Header.zig"); 16 pub const Auth = @import("./Home/Auth.zig"); 17 18 header: Header, 19 users: Users, 20 repos: Repos, 21 auth: Auth, 22 user_id: ?[]const u8 = null, 23 24 const Self = @This(); 25 26 pub fn init( 27 comptime repo_opts: rp.RepoOpts(.xit), 28 arena: *std.heap.ArenaAllocator, 29 repo: *rp.Repo(.xit, repo_opts), 30 session: *ui.Session, 31 ) !Self { 32 const DB = rp.Repo(.xit, repo_opts).DB; 33 34 const history = try DB.ArrayList(.read_only).init(repo.core.db.rootCursor().readOnly()); 35 36 const moment_cursor = try history.getCursor(-1) orelse return error.NotFound; 37 const moment = try DB.HashMap(.read_only).init(moment_cursor); 38 39 const last_object_id_cursor = try moment.getCursor(hash.hashInt(repo_opts.hash, "haxy-last-object-id")) orelse return error.NotFound; 40 var last_object_id: [hash.byteLen(repo_opts.hash)]u8 = undefined; 41 _ = try last_object_id_cursor.readBytes(&last_object_id); 42 43 const haxy_cursor = try moment.getCursor(hash.hashInt(repo_opts.hash, "haxy")) orelse return error.NotFound; 44 const haxy = try DB.ArrayList(.read_only).init(haxy_cursor); 45 46 const haxy_moments_cursor = try haxy.getCursor(-1) orelse return error.NotFound; 47 const haxy_moments = try DB.HashMap(.read_only).init(haxy_moments_cursor); 48 49 const haxy_moment_cursor = try haxy_moments.getCursor(hash.bytesToInt(repo_opts.hash, &last_object_id)) orelse return error.NotFound; 50 const haxy_moment = try DB.HashMap(.read_only).init(haxy_moment_cursor); 51 52 // hand the cursor + arena off to the session so Login (and any future 53 // page that needs DB access at input time) can use them on demand. 54 session.arena = arena; 55 session.haxy_moment = haxy_moment; 56 57 return .{ 58 .header = Header.init(), 59 .users = try Users.init(repo_opts, arena, haxy_moment), 60 .repos = try Repos.init(repo_opts, arena, haxy_moment), 61 .auth = Auth.init(), 62 .user_id = session.user_id, 63 }; 64 } 65 66 pub const View = struct { 67 box: wgt.Box(ui.Widget), 68 data: *const Self, 69 session: *ui.Session, 70 71 const header_index: usize = 0; 72 const stack_index: usize = 1; 73 74 pub fn init(allocator: std.mem.Allocator, data: *const Self, session: *ui.Session) !View { 75 var box = try wgt.Box(ui.Widget).init(allocator, .{ .border_style = null, .direction = .vert }); 76 errdefer box.deinit(); 77 78 // build the header first so we can grab the users-tab focus id and 79 // hand it to login/logout 80 var users_tab_id: usize = undefined; 81 { 82 var header_view = try Header.View.init(allocator, &data.header, session); 83 errdefer header_view.deinit(); 84 users_tab_id = header_view.tab_ids[0]; 85 try box.children.put(allocator, header_view.getFocus().id, .{ .widget = .{ .home_header = header_view }, .rect = null, .min_size = null }); 86 } 87 88 { 89 var stack = wgt.Stack(ui.Widget).init(allocator); 90 errdefer stack.deinit(); 91 92 { 93 var users_view = try Users.View.init(allocator, &data.users); 94 errdefer users_view.deinit(); 95 try stack.children.put(stack.allocator, users_view.getFocus().id, .{ .home_users = users_view }); 96 } 97 98 { 99 var repos_view = try Repos.View.init(allocator, &data.repos); 100 errdefer repos_view.deinit(); 101 try stack.children.put(stack.allocator, repos_view.getFocus().id, .{ .home_repos = repos_view }); 102 } 103 104 { 105 var auth_view = try Auth.View.init(allocator, &data.auth, session, users_tab_id); 106 errdefer auth_view.deinit(); 107 try stack.children.put(stack.allocator, auth_view.getFocus().id, .{ .home_auth = auth_view }); 108 } 109 110 try box.children.put(allocator, stack.getFocus().id, .{ .widget = .{ .stack = stack }, .rect = null, .min_size = null }); 111 } 112 113 var self = View{ 114 .box = box, 115 .data = data, 116 .session = session, 117 }; 118 self.getFocus().child_id = box.children.keys()[header_index]; 119 return self; 120 } 121 122 pub fn deinit(self: *View) void { 123 self.box.deinit(); 124 } 125 126 pub fn build(self: *View, constraint: layout.Constraint, root_focus: *Focus) !void { 127 self.clearGrid(); 128 const header = &self.box.children.values()[header_index].widget.home_header; 129 const stack = &self.box.children.values()[stack_index].widget.stack; 130 131 // each header tab maps 1:1 to a stack child by position 132 if (header.getSelectedIndex()) |index| { 133 stack.getFocus().child_id = stack.children.keys()[index]; 134 } 135 try self.box.build(constraint, root_focus); 136 } 137 138 pub fn input(self: *View, key: inp.Key, root_focus: *Focus) !void { 139 if (self.getFocus().child_id) |child_id| { 140 if (self.box.children.getIndex(child_id)) |current_index| { 141 const child = &self.box.children.values()[current_index].widget; 142 var index = current_index; 143 144 const Direction = enum { up, down, none }; 145 const direction: Direction = switch (key) { 146 .arrow_up => .up, 147 .arrow_down => .down, 148 .mouse => |mouse| if (mouse.action == .scroll) 149 (if (mouse.action.scroll == .up) .up else .down) 150 else 151 .none, 152 else => .none, 153 }; 154 155 switch (direction) { 156 .up => { 157 switch (child.*) { 158 .home_header => { 159 try child.input(key, root_focus); 160 }, 161 .stack => { 162 if (child.stack.getSelected()) |selected_widget| { 163 const at_top = switch (selected_widget.*) { 164 .home_users => |*v| v.getSelectedIndex() == 0, 165 .home_repos => |*v| v.getSelectedIndex() == 0, 166 .home_auth => |*v| v.getSelectedIndex() == 0, 167 else => false, 168 }; 169 if (at_top) { 170 index = header_index; 171 } else { 172 try child.input(key, root_focus); 173 } 174 } 175 }, 176 else => {}, 177 } 178 }, 179 .down => { 180 switch (child.*) { 181 .home_header => { 182 index = stack_index; 183 }, 184 .stack => { 185 try child.input(key, root_focus); 186 }, 187 else => {}, 188 } 189 }, 190 .none => { 191 try child.input(key, root_focus); 192 }, 193 } 194 195 if (index != current_index) { 196 try root_focus.setFocus(self.box.children.keys()[index]); 197 } 198 } 199 } 200 } 201 202 pub fn clearGrid(self: *View) void { 203 self.box.clearGrid(); 204 } 205 206 pub fn getGrid(self: View) ?Grid { 207 return self.box.getGrid(); 208 } 209 210 pub fn getFocus(self: *View) *Focus { 211 return self.box.getFocus(); 212 } 213 }; 214