╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ User.zig ─────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
206 // clicking a repo opens its page; the "a:" prefix makes the web 207 // renderer emit an <a href="/repo/alice/foo"> anchor. 208 try items.append(aa, .{ 209 .text = try std.fmt.allocPrint(aa, "{s} - {s}", .{ repo.event.name, repo.event.description }), 210 .link = try std.fmt.allocPrint(aa, "a:/repo/{s}/{s}", .{ data.user.name, repo.event.name }), 211 }); 212 if (data.repos_next_start) |next_start| 213 try items.append(aa, .{ .text = "next →", .link = try std.fmt.allocPrint(aa, "a:/user/{s}/repos/start:{d}", .{ data.user.name, next_start }) }); 214 try list.setItems(allocator, items.items); 215 216 try stack.children.put(allocator, list.getFocus().id, .{ .flow_box_scroll = list }); 217 } 218 219 // forks list 220 { 221 var list = try ui.widget.FlowBox.Scroll.init(allocator, .{}, !session.is_terminal); 222 errdefer list.deinit(allocator); 223 224 var item_arena = std.heap.ArenaAllocator.init(allocator); 225 defer item_arena.deinit(); 226 const aa = item_arena.allocator(); 227 var items: std.ArrayList(ui.widget.FlowBox.Item) = .empty; 228 if (data.forks_start > 0) 229 try items.append(aa, .{ .text = "← previous", .link = try std.fmt.allocPrint(aa, "a:/user/{s}/forks/start:{d}", .{ data.user.name, data.forks_start -| page_size }) }); 230 for (data.forks) |fork_item| { 231 const route = ui.RoutablePage.forkPatchRoute(fork_item.target, fork_item.id) orelse return error.RouteTooLong; 232 try items.append(aa, .{ 233 .text = try std.fmt.allocPrint(aa, "{s}\n{s}", .{ fork_item.target, fork_item.title }), 234 .link = try std.fmt.allocPrint(aa, "a:{s}", .{try route.toUrl(session.page_arena)}), 235 }); 236 } 237 if (data.forks_next_start) |next_start| 238 try items.append(aa, .{ .text = "next →", .link = try std.fmt.allocPrint(aa, "a:/user/{s}/forks/start:{d}", .{ data.user.name, next_start }) }); 239 try list.setItems(allocator, items.items); 240 try stack.children.put(allocator, list.getFocus().id, .{ .flow_box_scroll = list }); 241 } 242 243 // the header has no settings tab without a login, so keep the 244 // stack's children 1:1 with the tabs by skipping the view too 245 if (session.data.user_id != null) { 246 var settings_view = try Settings.View.init(allocator, session); 247 errdefer settings_view.deinit(allocator); 248 try stack.children.put(allocator, settings_view.getFocus().id, .{ .home_settings = settings_view }); 249 } 250 251 { 252 var auth_view = try Auth.View.init(allocator, &data.auth, session); 253 errdefer auth_view.deinit(allocator); 254 try stack.children.put(allocator, auth_view.getFocus().id, .{ .home_auth = auth_view }); 255 } 256 257 if (session.is_terminal) { 258 var quit_view = try Quit.View.init(allocator, session); 259 errdefer quit_view.deinit(allocator); 260 try stack.children.put(allocator, quit_view.getFocus().id, .{ .quit = quit_view }); 261 } 262 263 try box.children.put(allocator, stack.getFocus().id, .{ .widget = .{ .stack = stack }, .rect = null, .min_size = null }); 264 } 265 266 var self = View{ .box = box }; 267 self.getFocus().child_id = box.children.keys()[header_index]; 268 return self; 269 } 270 271 pub fn deinit(self: *View, allocator: std.mem.Allocator) void { 272 self.box.deinit(allocator); 273 } 274 275 pub fn build(self: *View, allocator: std.mem.Allocator, constraint: layout.Constraint, root_focus: *Focus) !void { 276 self.clearGrid(); 277 const header = &self.box.children.values()[header_index].widget.user_header; 278 const stack = &self.box.children.values()[stack_index].widget.stack; 279 280 // each header tab maps 1:1 to a stack child by position 281 if (header.getSelectedIndex()) |index| 282 stack.getFocus().child_id = stack.children.keys()[index]; 283 try self.box.build(allocator, constraint, root_focus); 284 } 285 286 pub fn input(self: *View, allocator: std.mem.Allocator, key: Key, root_focus: *Focus) !void { 287 if (self.getFocus().child_id) |child_id| { 288 if (self.box.children.getIndex(child_id)) |current_index| { 289 const child = &self.box.children.values()[current_index].widget; 290 var index = current_index; 291 292 const direction = inp.vertDirection(key); 293 294 switch (direction) { 295 .up => { 296 switch (child.*) { 297 .user_header => { 298 try child.input(allocator, key, root_focus); 299 }, 300 .stack => { 301 if (child.stack.getSelected()) |selected_widget| { 302 if (selected_widget.atTop(root_focus)) { 303 index = header_index; 304 } else { 305 try child.input(allocator, key, root_focus); 306 } 307 } 308 }, 309 else => {}, 310 } 311 }, 312 .down => { 313 switch (child.*) { 314 .user_header => { 315 index = stack_index; 316 }, 317 .stack => { 318 try child.input(allocator, key, root_focus); 319 }, 320 else => {}, 321 } 322 }, 323 .none => { 324 try child.input(allocator, key, root_focus); 325 }, 326 } 327 328 if (index != current_index) { 329 root_focus.setFocus(self.box.children.keys()[index]); 330 } 331 } 332 } 333 } 334 335 pub fn clearGrid(self: *View) void { 336 self.box.clearGrid(); 337 } 338 339 pub fn getGrid(self: View) ?Grid { 340 return self.box.getGrid(); 341 } 342 343 pub fn getFocus(self: *View) *Focus { 344 return self.box.getFocus(); 345 } 346 }; 347