╭ changed ──────────────────╮ │src/ui/Repo/Discussions.zig│ ╰───────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │59 pub const Event = evt.Discussion; │ │60 pub const Status = enum { recent }; │ │61 pub const ViewKind = ui.RoutablePage.DiscussionsView; │ │62 +pub const thread_name = "discussion"; │ │63 +pub const header_widget_name = "repo_discussions_header"; │ │64 + │ │65 +pub fn listRoute(identity: []const u8, _: Status, tag: []const u8, selected: []const u8) ?ui.RoutablePage {│ │66 + return ui.RoutablePage.repoDiscussionsRoute(identity, tag, selected); │ │67 +} │ │68 │ │69 pub fn selectedThread(self: *const Self) ?*const DiscussionWithId { │ │70 if (self.selected_id.len == 0) return null; │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─────────────╮ │src/ui/Repo/Issues.zig│ ╰──────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │113 pub const Event = evt.Issue; │ │114 pub const Status = evt.Issue.Status; │ │115 pub const ViewKind = ui.RoutablePage.IssuesView; │ │116 +pub const thread_name = "issue"; │ │117 +pub const header_widget_name = "repo_issues_header"; │ │118 + │ │119 +pub fn listRoute(identity: []const u8, status: Status, tag: []const u8, selected: []const u8) ?ui.RoutablePage {│ │120 + return ui.RoutablePage.repoIssuesRoute(identity, status, tag, selected); │ │121 +} │ │122 + │ │123 +pub fn conflictsRoute(identity: []const u8, selected: []const u8) ?ui.RoutablePage { │ │124 + return ui.RoutablePage.repoIssuesConflictsRoute(identity, selected); │ │125 +} │ │126 │ │127 +pub fn resolveRoute(identity: []const u8, selected: []const u8, picks: []const u8) ?ui.RoutablePage { │ │128 + return ui.RoutablePage.repoIssuesResolveRoute(identity, selected, picks); │ │129 +} │ │130 + │ │131 // `status`'s windowed listing. │ │132 pub fn window(self: *const Self, status: evt.Issue.Status) *const Window { │ │133 return switch (status) { │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──────────────╮ │src/ui/Repo/Patches.zig│ ╰───────────────────────╯ ╭─────────────────────────────────────────────────╮ │ 6 const rp = xit.repo; │ │ 7 const hash = xit.hash; │ │ 8 const rf = xit.ref; │ │ 9 +const xitui = xit.xitui; │ │10 +const wgt = xitui.widget; │ │11 const diff3 = @import("../../diff3.zig"); │ │12 const Comment = @import("Comment.zig"); │ │13 const Attachment = @import("Attachment.zig");│ ╰─────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │128 pub const Event = evt.Patch; │ │129 pub const Status = evt.Patch.Status; │ │130 pub const ViewKind = ui.RoutablePage.PatchesView; │ │131 +pub const thread_name = "patch"; │ │132 +pub const header_widget_name = "repo_patches_header"; │ │133 + │ │134 +pub fn listRoute(identity: []const u8, status: Status, tag: []const u8, selected: []const u8) ?ui.RoutablePage {│ │135 + return ui.RoutablePage.repoPatchesRoute(identity, status, tag, selected); │ │136 +} │ │137 + │ │138 +pub fn draftsRoute(identity: []const u8) ?ui.RoutablePage { │ │139 + return ui.RoutablePage.repoPatchesDraftsRoute(identity); │ │140 +} │ │141 + │ │142 +pub fn conflictsRoute(identity: []const u8, selected: []const u8) ?ui.RoutablePage { │ │143 + return ui.RoutablePage.repoPatchesConflictsRoute(identity, selected); │ │144 +} │ │145 │ │146 +pub fn resolveRoute(identity: []const u8, selected: []const u8, picks: []const u8) ?ui.RoutablePage { │ │147 + return ui.RoutablePage.repoPatchesResolveRoute(identity, selected, picks); │ │148 +} │ │149 + │ │150 pub fn createDraft( │ │151 data: *const Self, │ │152 session: *ui.Session, │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │567 │ │568 pub const Header = Threads.Header; │ │569 │ │550 -pub fn cloneDirectoryName(allocator: std.mem.Allocator, title: []const u8) ![]const u8 { │ │570 +pub fn appendDraftDetails(self: *const Self, allocator: std.mem.Allocator, box: *wgt.Box(ui.Widget), session: *ui.Session, entry: Entry) !void { │ │571 + const aa = session.page_arena.allocator(); │ │572 + var fields: [2]ui.CopyableText.Choice = undefined; │ │573 + var field_count: usize = 0; │ │574 + │ │575 + if (entry.target_branch.len != 0) if (session.data.git_ssh_port) |port| { │ │576 + const url = try std.fmt.allocPrint(aa, "ssh://localhost:{d}/repo/{s}/patch:{s}/branch:{s}", .{ port, self.identity, entry.id, entry.target_branch }); │ │577 + const push_command = try std.fmt.allocPrint(aa, "git push {s} HEAD:patch", .{url}); │ │578 + const clone_name = try cloneDirectoryName(aa, entry.record.event.title); │ │579 + const clone_command = if (clone_name.len == 0) │ │580 + try std.fmt.allocPrint(aa, "git clone {s}", .{url}) │ │581 + else │ │582 + try std.fmt.allocPrint(aa, "git clone {s} {s}", .{ url, clone_name }); │ │583 + const choices: [2]ui.CopyableText.Choice = .{ │ │584 + .{ │ │585 + .selector = "push", │ │586 + .text = push_command, │ │587 + .copyable_text = try std.fmt.allocPrint(aa, "{s}{s}", .{ session.data.git_ssh_prefix, push_command }), │ │588 + .label = " push to this patch from existing repo ", │ │589 + }, │ │590 + .{ │ │591 + .selector = "clone", │ │592 + .text = clone_command, │ │593 + .copyable_text = try std.fmt.allocPrint(aa, "{s}{s}", .{ session.data.git_ssh_prefix, clone_command }), │ │594 + .label = " clone this patch ", │ │595 + }, │ │596 + }; │ │597 + var copyable_text = try ui.CopyableText.View.init(allocator, session, &choices); │ │598 + errdefer copyable_text.deinit(allocator); │ │599 + try box.children.put(allocator, copyable_text.getFocus().id, .{ .widget = .{ .copyable_text = copyable_text }, .rect = null, .min_size = null }); │ │600 + try addDetailGap(allocator, box); │ │601 + }; │ │602 + if (entry.fork_oid.len != 0) { │ │603 + fields[field_count] = .{ │ │604 + .text = entry.fork_oid, │ │605 + .label = if (entry.no_changes) " object id of this patch (no changes) " else " object id of this patch ", │ │606 + }; │ │607 + field_count += 1; │ │608 + } │ │609 + if (entry.target_branch.len != 0) { │ │610 + fields[field_count] = .{ │ │611 + .text = entry.target_branch, │ │612 + .label = " target branch this patch will go to ", │ │613 + .bottom_label = " (set by the url you push to above) ", │ │614 + }; │ │615 + field_count += 1; │ │616 + } │ │617 + │ │618 + for (fields[0..field_count], 0..) |field, i| { │ │619 + var copyable_text = try ui.CopyableText.View.init(allocator, session, &.{field}); │ │620 + errdefer copyable_text.deinit(allocator); │ │621 + try box.children.put(allocator, copyable_text.getFocus().id, .{ .widget = .{ .copyable_text = copyable_text }, .rect = null, .min_size = null }); │ │622 + if (i + 1 < field_count) try addDetailGap(allocator, box); │ │623 + } │ │624 + if (entry.target_branch.len != 0) try addDetailGap(allocator, box); │ │625 +} │ │626 + │ │627 +fn addDetailGap(allocator: std.mem.Allocator, box: *wgt.Box(ui.Widget)) !void { │ │628 + var spacer = try ui.Spacer.init(allocator); │ │629 + errdefer spacer.deinit(allocator); │ │630 + try box.children.put(allocator, spacer.getFocus().id, .{ .widget = .{ .spacer = spacer }, .rect = null, .min_size = .{ .width = null, .height = 1 }, .max_size = .{ .width = null, .height = 1 } });│ │631 +} │ │632 + │ │633 +fn cloneDirectoryName(allocator: std.mem.Allocator, title: []const u8) ![]const u8 { │ │634 var name: [64]u8 = undefined; │ │635 var len: usize = 0; │ │636 var pending: ?u8 = null; │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──────────────╮ │src/ui/Repo/Threads.zig│ ╰───────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────╮ │345 const Event = Data.Event; │ │346 const Status = Data.Status; │ │347 const has_status = @hasField(Event, "status"); │ │348 - const has_conflicts = Event.merge_policy == .field_conflicts; │ │349 - const has_drafts = @hasField(Self, "drafts"); │ │350 - const FieldConflict = if (has_conflicts) Data.FieldConflict else void; │ │348 + const supports_conflicts = Event.merge_policy == .field_conflicts; │ │349 + const supports_drafts = @hasField(Self, "drafts"); │ │350 + const FieldConflict = if (supports_conflicts) Data.FieldConflict else void;│ │351 const ViewKind = Data.ViewKind; │ │352 - const thread_name = switch (kind) { │ │353 - .issue => "issue", │ │354 - .patch => "patch", │ │355 - .discuss => "discussion", │ │356 - else => @compileError("unsupported thread event kind"), │ │357 - }; │ │352 + const thread_name = Data.thread_name; │ │353 │ │354 return struct { │ │355 const This = @This(); │ ╰────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────╮ │377 // the conflicts split; the tab and stack child exist only when the repo │ │378 // has conflicted threads. │ │379 const drafts_view_index: usize = form_view_index + 1; │ │385 - const conflict_view_index: usize = drafts_view_index + @intFromBool(has_drafts); │ │386 - const view_count: usize = conflict_view_index + @intFromBool(has_conflicts); │ │380 + const conflict_view_index: usize = drafts_view_index + @intFromBool(supports_drafts);│ │381 + const view_count: usize = conflict_view_index + @intFromBool(supports_conflicts); │ │382 // indices within a split (the horizontal box inside the stack). │ │383 const list_index: usize = 0; │ │384 const detail_index: usize = 1; │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────╮ │413 } │ │414 │ │415 fn entryConflicted(entry: Entry) bool { │ │421 - return if (has_conflicts) entry.conflicted else false; │ │416 + return if (supports_conflicts) entry.conflicted else false;│ │417 } │ │418 │ │419 fn entryStatus(entry: Entry) Status { │ ╰────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────╮ │421 } │ │422 │ │423 fn entryDraft(entry: Entry) bool { │ │429 - return if (has_drafts) entry.draft else false; │ │424 + return if (supports_drafts) entry.draft else false;│ │425 } │ │426 │ │427 const StatusChange = struct { │ ╰────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │443 } │ │444 │ │445 fn listRoute(identity: []const u8, status: Status, tag: []const u8, selected: []const u8) ?ui.RoutablePage {│ │451 - return switch (kind) { │ │452 - .issue => ui.RoutablePage.repoIssuesRoute(identity, status, tag, selected), │ │453 - .patch => ui.RoutablePage.repoPatchesRoute(identity, status, tag, selected), │ │454 - .discuss => ui.RoutablePage.repoDiscussionsRoute(identity, tag, selected), │ │455 - else => @compileError("unsupported thread event kind"), │ │456 - }; │ │446 + return Data.listRoute(identity, status, tag, selected); │ │447 } │ │448 │ │449 fn draftsRoute(identity: []const u8) ?ui.RoutablePage { │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯