╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ────────╮ │src/ui/widget.zig│ ╰─────────────────╯ ╭───────────────────────────────────────────╮ │29 home: ui.Home.View, │ │30 user: ui.User.View, │ │31 repo: ui.Repo.View, │ │32 + fork: ui.Fork.View, │ │33 quit: ui.Quit.View, │ │34 unauthorized: ui.Unauthorized.View,│ │35 title: ui.Title.View, │ ╰───────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────╮ │37 home_header: ui.Home.Header.View, │ │38 user_header: ui.User.Header.View, │ │39 repo_header: ui.Repo.Header.View, │ │40 + fork_header: ui.Fork.Header.View, │ │41 repo_files_header: ui.Repo.Files.Header.View, │ │42 repo_commits_header: ui.Repo.Commits.Header.View,│ │43 repo_issues_header: ui.Repo.Issues.Header, │ ╰─────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1260 return .{ .box = box, .session = session, .choices = owned_choices }; │ │1261 } │ │1262 │ │1261 - pub fn initClone(allocator: std.mem.Allocator, session: *ui.Session, identity: []const u8) !CopyableText { │ │1263 + pub fn initClone(allocator: std.mem.Allocator, session: *ui.Session, location: ui.RoutablePage.RepoLocation) !?CopyableText {│ │1264 const aa = session.page_arena.allocator(); │ │1265 var choices: [2]Choice = undefined; │ │1266 var count: usize = 0; │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1265 - if (session.data.git_http_port) |port| { │ │1266 - const url = try std.fmt.allocPrint(aa, "http://localhost:{d}/repo/{s}", .{ port, identity }); │ │1267 - choices[count] = .{ │ │1268 - .selector = "http", │ │1269 - .text = url, │ │1270 - .copyable_text = try std.fmt.allocPrint(aa, "git clone {s}", .{url}), │ │1271 - .label = " clone ", │ │1272 - }; │ │1273 - count += 1; │ │1267 + switch (location) { │ │1268 + .repo => |identity| { │ │1269 + if (session.data.git_http_port) |port| { │ │1270 + const url = try std.fmt.allocPrint(aa, "http://localhost:{d}/repo/{s}", .{ port, identity }); │ │1271 + choices[count] = .{ │ │1272 + .selector = "http", │ │1273 + .text = url, │ │1274 + .copyable_text = try std.fmt.allocPrint(aa, "git clone {s}", .{url}), │ │1275 + .label = " clone ", │ │1276 + }; │ │1277 + count += 1; │ │1278 + } │ │1279 + if (session.data.git_ssh_port) |port| { │ │1280 + const url = try std.fmt.allocPrint(aa, "ssh://localhost:{d}/repo/{s}", .{ port, identity }); │ │1281 + choices[count] = .{ │ │1282 + .selector = "ssh", │ │1283 + .text = url, │ │1284 + .copyable_text = try std.fmt.allocPrint(aa, "{s}git clone {s}", .{ session.data.git_ssh_prefix, url }), │ │1285 + .label = " clone ", │ │1286 + }; │ │1287 + count += 1; │ │1288 + } │ │1289 + }, │ │1290 + .fork => |f| if (f.target_branch.len != 0) if (session.data.git_ssh_port) |port| { │ │1291 + const url = try std.fmt.allocPrint(aa, "ssh://localhost:{d}/repo/{s}/patch:{s}/branch:{s}", .{ port, f.identity, f.id, try ui.urlEncodeRef(aa, f.target_branch) });│ │1292 + choices[count] = .{ │ │1293 + .text = url, │ │1294 + .copyable_text = try std.fmt.allocPrint(aa, "{s}git clone {s}", .{ session.data.git_ssh_prefix, url }), │ │1295 + .label = " clone ", │ │1296 + }; │ │1297 + count += 1; │ │1298 + }, │ │1299 } │ │1275 - if (session.data.git_ssh_port) |port| { │ │1276 - const url = try std.fmt.allocPrint(aa, "ssh://localhost:{d}/repo/{s}", .{ port, identity }); │ │1277 - choices[count] = .{ │ │1278 - .selector = "ssh", │ │1279 - .text = url, │ │1280 - .copyable_text = try std.fmt.allocPrint(aa, "{s}git clone {s}", .{ session.data.git_ssh_prefix, url }), │ │1281 - .label = " clone ", │ │1282 - }; │ │1283 - count += 1; │ │1284 - } │ │1285 - if (count == 0) return error.MissingGitPort; │ │1286 - return init(allocator, session, choices[0..count]); │ │1300 + if (count == 0) return null; │ │1301 + return @as(?CopyableText, try init(allocator, session, choices[0..count])); │ │1302 } │ │1303 │ │1304 pub fn minWidth(self: *const CopyableText) usize { │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ───────────────╮ │src/ui/widget/thread.zig│ ╰────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────╮ │355 const has_status = @hasField(Event, "status"); │ │356 const supports_conflicts = Event.merge_policy == .field_conflicts; │ │357 const supports_drafts = @hasField(Self, "drafts"); │ │358 + const supports_forks = @hasField(Entry, "fork_exists"); │ │359 const FieldConflict = if (supports_conflicts) Data.FieldConflict else void;│ │360 const ViewKind = Data.ViewKind; │ │361 const thread_name = Data.thread_name; │ ╰────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────╮ │374 title_id: [view_count]?usize, │ │375 description_id: [view_count]?usize,│ │376 author_id: [view_count]?usize, │ │377 + detail_only: bool, │ │378 │ │379 const header_index: usize = 0; │ │380 const stack_index: usize = 1; │ ╰────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────╮ │434 return if (supports_drafts) entry.draft else false; │ │435 } │ │436 │ │437 + fn entryHasFork(entry: Entry) bool { │ │438 + return if (supports_forks) entry.fork_exists else false;│ │439 + } │ │440 + │ │441 const StatusChange = struct { │ │442 action: []const u8, │ │443 status: Status, │ ╰─────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────╮ │464 return if (supports_drafts) Data.draftsRoute(identity) else null; │ │465 } │ │466 │ │467 + fn forkRoute(identity: []const u8, id: []const u8) ?ui.RoutablePage { │ │468 + return if (supports_forks) Data.forkRoute(identity, id) else null; │ │469 + } │ │470 + │ │471 fn conflictsRoute(identity: []const u8, selected: []const u8) ?ui.RoutablePage { │ │472 return if (supports_conflicts) Data.conflictsRoute(identity, selected) else null;│ │473 } │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │669 .title_id = @splat(null), │ │670 .description_id = @splat(null), │ │671 .author_id = @splat(null), │ │672 + .detail_only = false, │ │673 }; │ │674 + } │ │675 + │ │676 + // build the selected thread's existing detail pane without its list or │ │677 + // thread-page header, for pages that provide their own surrounding UI. │ │678 + pub fn initDetail(allocator: std.mem.Allocator, data: *const Self, session: *ui.Session) !This { │ │679 + var self = try init(allocator, data, session); │ │680 + errdefer self.deinit(allocator); │ │681 + self.detail_only = true; │ │682 + self.box.getFocus().child_id = self.box.children.keys()[stack_index]; │ │683 + const index = self.selectedSplitIndex() orelse return error.NotFound; │ │684 + self.resultsBox(index).getFocus().child_id = self.resultsBox(index).children.keys()[detail_index];│ │685 + return self; │ │686 } │ │687 │ │688 // the master-detail split showing `status`'s window, or the conflicts │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1229 // beside it. the box drops the detail when the width can't hold │ │1230 // both minimums, so when it's that narrow we lift the cap and let │ │1231 // the list fill the whole width. │ │1209 - const both_panes_fit = if (constraint.max_size.width) |w| w >= list_max_width + detail_min_width else true; │ │1210 - self.resultsBox(i).children.values()[list_index].max_size = if (both_panes_fit) .{ .width = list_max_width, .height = null } else null; │ │1232 + if (self.detail_only) { │ │1233 + self.box.children.values()[header_index].min_size = .{ .width = null, .height = 0 }; │ │1234 + self.box.children.values()[header_index].max_size = .{ .width = null, .height = 0 }; │ │1235 + self.resultsBox(i).children.values()[list_index].min_size = .{ .width = 0, .height = null }; │ │1236 + self.resultsBox(i).children.values()[list_index].max_size = .{ .width = 0, .height = null }; │ │1237 + self.resultsBox(i).children.values()[detail_index].min_size = .{ .width = constraint.max_size.width, .height = null }; │ │1238 + } else { │ │1239 + const both_panes_fit = if (constraint.max_size.width) |w| w >= list_max_width + detail_min_width else true; │ │1240 + self.resultsBox(i).children.values()[list_index].max_size = if (both_panes_fit) .{ .width = list_max_width, .height = null } else null; │ │1241 │ │1212 - // stretch the detail pane across the rest of the width so it fills │ │1213 - // the area rather than shrinking to its content; its scroll fills │ │1214 - // the pane. │ │1215 - if (constraint.max_size.width) |w| { │ │1216 - self.resultsBox(i).children.values()[detail_index].min_size = .{ .width = if (both_panes_fit) w - list_max_width else w, .height = null }; │ │1217 - } else { │ │1218 - self.resultsBox(i).children.values()[detail_index].min_size = .{ .width = detail_min_width, .height = null }; │ │1242 + // stretch the detail pane across the rest of the width so it fills │ │1243 + // the area rather than shrinking to its content; its scroll fills │ │1244 + // the pane. │ │1245 + if (constraint.max_size.width) |w| { │ │1246 + self.resultsBox(i).children.values()[detail_index].min_size = .{ .width = if (both_panes_fit) w - list_max_width else w, .height = null };│ │1247 + } else { │ │1248 + self.resultsBox(i).children.values()[detail_index].min_size = .{ .width = detail_min_width, .height = null }; │ │1249 + } │ │1250 } │ │1251 } │ │1252 │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯