╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ────╮ │src/serve.zig│ ╰─────────────╯ ╭────────────────────────────────────────────────────────────────────────────────╮ │89 try err.print("serving web UI on http://{s}/\n", .{options.wui_listen});│ │90 try err.flush(); │ │91 │ │86 - web.run(io, allocator, &wui_server, &tasks, err); │ │92 + web.run(io, allocator, &wui_server, &tasks, admin_repo_path, err); │ │93 │ │88 - if (options.tui) { │ │89 - try ui.run(io, allocator); │ │94 + if (@TypeOf(runnable) != void) { │ │95 + try runnable.run(); │ │96 } else { │ │97 try tasks.await(io); │ │98 } │ ╰────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──╮ │src/try.zig│ ╰───────────╯ ╭──────────────────────────────────────────────────────╮ │ 8 const evt = hx.event; │ │ 9 const xit = hx.xit; │ │10 const rp = xit.repo; │ │11 +const Page = hx.page.Page; │ │12 │ │13 pub fn main() !void { │ │14 var gpa: std.heap.DebugAllocator(.{}) = .init;│ ╰──────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │35 const cwd_path = try std.process.currentPathAlloc(io, allocator); │ │36 defer allocator.free(cwd_path); │ │37 │ │37 - const server_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "server" }); │ │38 - defer allocator.free(server_path); │ │38 + var page_arena = std.heap.ArenaAllocator.init(allocator); │ │39 + defer page_arena.deinit(); │ │40 │ │40 - // create the admin repo │ │41 - { │ │41 + // create the admin repo and build the Page from it │ │42 + const page: Page = blk: { │ │43 const work_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "server", "admin" });│ │44 defer allocator.free(work_path); │ │45 │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────╮ │163 │ │164 // consume events into the database │ │165 try evt.consume(repo_opts, io, allocator, &repo, .{ .kind = .head, .name = "haxy/meta" });│ │165 - } │ │166 │ │167 - // start server │ │167 + break :blk .{ .user_repo = try .init(repo_opts, &page_arena, &repo) }; │ │168 + }; │ │169 + │ │170 + // start the server │ │171 │ │172 var null_writer = std.Io.Writer.Discarding.init(&.{}); │ │173 var environ_map = std.process.Environ.Map.init(allocator); │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │174 defer environ_map.deinit(); │ │175 const run_opts = hx.main.RunOpts{ .out = &null_writer.writer, .err = &null_writer.writer, .environ_map = &environ_map };│ │176 + │ │177 + const server_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "server" }); │ │178 + defer allocator.free(server_path); │ │179 + │ │180 + const Runnable = struct { │ │181 + io: std.Io, │ │182 + allocator: std.mem.Allocator, │ │183 + page: *const Page, │ │184 + │ │185 + pub fn run(self: @This()) !void { │ │186 + // launch the TUI │ │187 + try hx.ui.run(self.io, self.allocator, self.page); │ │188 + } │ │189 + }; │ │190 │ │191 try srv.run(.xit, .{}, io, allocator, cwd_path, .{ │ │192 .data_dir = server_path, │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────╮ │176 - .tui = true, │ │177 - }, run_opts.err); │ │193 + }, run_opts.err, Runnable{ .io = io, .allocator = allocator, .page = &page });│ │194 } │ │195 │ ╰───────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────╮ │ 7 const inp = xitui.input; │ │ 8 const Grid = xitui.grid.Grid; │ │ 9 const Focus = xitui.focus.Focus; │ │10 +const evt = @import("./event.zig"); │ │11 +const pg = @import("./page.zig"); │ │12 │ │11 -pub fn run(io: std.Io, allocator: std.mem.Allocator) !void { │ │12 - var root = try initRoot(allocator); │ │13 +pub fn run(io: std.Io, allocator: std.mem.Allocator, page: *const pg.Page) !void {│ │14 + var root = try initRoot(allocator, page); │ │15 defer root.deinit(); │ │16 │ │17 var terminal = try term.Terminal.init(io, allocator); │ ╰──────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────╮ │50 } │ │51 } │ │52 │ │51 -pub fn initRoot(allocator: std.mem.Allocator) !Widget { │ │52 - var root = Widget{ .widget_list = try WidgetList.init(allocator) }; │ │53 +pub fn initRoot(allocator: std.mem.Allocator, page: *const pg.Page) !Widget { │ │54 + var root: Widget = switch (page.*) { │ │55 + .user_repo => |*p| .{ .user_repo_view = try UserRepoView.init(allocator, p) },│ │56 + }; │ │57 errdefer root.deinit(); │ │58 │ │59 try root.build(.{ │ ╰──────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 96 } │ │ 97 } │ │ 98 } │ │ 99 + │ │100 +pub const Widget = union(enum) { │ │101 + text: wgt.Text(Widget), │ │102 + box: wgt.Box(Widget), │ │103 + text_box: wgt.TextBox(Widget), │ │104 + scroll: wgt.Scroll(Widget), │ │105 + selectable_list: SelectableList, │ │106 + user_repo_view: UserRepoView, │ │107 + │ │108 + pub fn deinit(self: *Widget) void { │ │109 + switch (self.*) { │ │110 + inline else => |*case| case.deinit(), │ │111 + } │ │112 + } │ │113 │ │ 96 -const WidgetList = struct { │ │114 + pub fn build(self: *Widget, constraint: layout.Constraint, root_focus: *Focus) anyerror!void { │ │115 + switch (self.*) { │ │116 + inline else => |*case| try case.build(constraint, root_focus), │ │117 + } │ │118 + } │ │119 + │ │120 + pub fn input(self: *Widget, key: inp.Key, root_focus: *Focus) anyerror!void { │ │121 + switch (self.*) { │ │122 + inline else => |*case| try case.input(key, root_focus), │ │123 + } │ │124 + } │ │125 + │ │126 + pub fn clearGrid(self: *Widget) void { │ │127 + switch (self.*) { │ │128 + inline else => |*case| case.clearGrid(), │ │129 + } │ │130 + } │ │131 + │ │132 + pub fn getGrid(self: Widget) ?Grid { │ │133 + switch (self) { │ │134 + inline else => |*case| return case.getGrid(), │ │135 + } │ │136 + } │ │137 + │ │138 + pub fn getFocus(self: *Widget) *Focus { │ │139 + switch (self.*) { │ │140 + inline else => |*case| return case.getFocus(), │ │141 + } │ │142 + } │ │143 +}; │ │144 + │ │145 +// a scrollable list of selectable single-line items. content is replaced │ │146 +// via setItems, which dupes the strings into the widget's own allocation. │ │147 +const SelectableList = struct { │ │148 allocator: std.mem.Allocator, │ │149 scroll: wgt.Scroll(Widget), │ │150 + lines: std.ArrayList([]const u8), │ │151 │ │100 - pub fn init(allocator: std.mem.Allocator) !WidgetList { │ │152 + pub fn init(allocator: std.mem.Allocator) !SelectableList { │ │153 var self = blk: { │ │102 - var inner_box = try wgt.Box(Widget).init(allocator, .{ .border_style = null, .direction = .vert }); │ │154 + var inner_box = try wgt.Box(Widget).init(allocator, .{ .border_style = null, .rounded_corners = true, .direction = .vert });│ │155 errdefer inner_box.deinit(); │ │156 │ │157 var scroll = try wgt.Scroll(Widget).init(allocator, .{ .box = inner_box }, .vert); │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────╮ │158 errdefer scroll.deinit(); │ │159 │ │108 - break :blk WidgetList{ │ │160 + break :blk SelectableList{ │ │161 .allocator = allocator,│ │162 .scroll = scroll, │ │163 + .lines = .empty, │ │164 }; │ │165 }; │ │166 errdefer self.deinit(); │ ╰────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯