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