╭ changed ─────────────╮ │src/ui/Repo/Events.zig│ ╰──────────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭─────────────────────────────────────╮ │19 pub const Item = struct { │ │20 id: []const u8, │ │21 kind: evt.EventKind, │ │22 - deleted: bool, │ │22 author: ui.Author, │ │23 view_url: ?[]const u8 = null,│ │24 }; │ ╰─────────────────────────────────────╯ ╭────────────────────────────────────╮ │26 pub const Cursor = struct { │ │27 id: []const u8, │ │28 kind: evt.EventKind, │ │29 +}; │ │30 + │ │31 +pub const Window = struct { │ │32 + count: usize = 0, │ │33 + events: []const Item = &.{},│ │34 + next: ?Cursor = null, │ │35 }; │ │36 │ │37 identity: []const u8, │ ╰────────────────────────────────────╯ ╭─────────────────────────────────────╮ │33 -events: []const Item, │ │34 -next: ?Cursor, │ │38 +view: ui.RoutablePage.EventsView,│ │39 +active: Window, │ │40 +removed: Window, │ │41 header: Header, │ │42 │ │43 const Self = @This(); │ ╰─────────────────────────────────────╯ ╭───────────────────────────────────────────────────────╮ │50 io: std.Io, │ │51 admin_moment: ?evt.AdminDB.HashMap(.read_only),│ │52 identity: []const u8, │ │53 + requested_view: ui.RoutablePage.EventsView, │ │54 selected_kind: ?evt.EventKind, │ │55 selected: []const u8, │ │56 local: bool, │ ╰───────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────╮ │57 failure: ?[]const u8, │ │58 ) !Self { │ │59 const aa = arena.allocator(); │ │53 - var result = try empty(aa, identity, local, failure); │ │54 - var local_header: ?Header = null; │ │55 - if (local) { │ │56 - const header = try Header.local(repo_kind, repo_opts, aa, repo, io, failure); │ │57 - local_header = header; │ │58 - result.header = try header.withCount(aa, 0); │ │59 - } │ │60 - const strict = selected_kind != null or selected.len != 0; │ │60 + var result = try empty(aa, identity, requested_view, local, failure); │ │61 + if (local) result.header = try Header.local(repo_kind, repo_opts, aa, repo, io, failure);│ │62 + const require_selected_event = selected_kind != null or selected.len != 0; │ │63 if ((selected_kind == null) != (selected.len == 0)) return error.NotFound; │ │64 │ │65 const DB = evt.EventDB(repo_opts.hash); │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────╮ │72 return result │ │73 else │ │74 evt.currentMoment(repo_opts, repo)) catch { │ │73 - if (strict) return error.NotFound; │ │75 + if (require_selected_event) return error.NotFound;│ │76 return result; │ │77 }; │ │78 │ ╰──────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │79 const kind_map_cursor = try haxy_moment.getCursor(hash.hashInt(repo_opts.hash, evt.event_index_key)) orelse {│ │78 - if (strict) return error.NotFound; │ │80 + if (require_selected_event) return error.NotFound; │ │81 return result; │ │82 }; │ │83 const kind_map = try DB.HashMap(.read_only).init(kind_map_cursor); │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │82 - const id_set_cursor = try haxy_moment.getCursor(hash.hashInt(repo_opts.hash, evt.event_id_set_key)) orelse { │ │83 - if (strict) return error.NotFound; │ │84 - return result; │ │85 - }; │ │86 - const id_set = try DB.SortedSet(.read_only).init(id_set_cursor); │ │87 - const count: usize = @intCast(try id_set.count()); │ │88 - if (local_header) |header| │ │89 - result.header = try header.withCount(aa, count) │ │90 - else │ │91 - result.header = try Header.remote(aa, count); │ │84 + │ │85 + const active_cursor = try haxy_moment.getCursor(hash.hashInt(repo_opts.hash, evt.active_event_id_set_key)); │ │86 + const removed_cursor = try haxy_moment.getCursor(hash.hashInt(repo_opts.hash, evt.removed_event_id_set_key));│ │87 │ │88 var root_key: ?[@sizeOf(u64) + evt.event_id_size]u8 = null; │ │89 if (selected.len != 0) { │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 91 _ = std.fmt.hexToBytes(&id, selected) catch return error.NotFound; │ │ 92 if (!std.mem.eql(u8, selected, &std.fmt.bytesToHex(id, .lower))) return error.NotFound; │ │ 93 const kind = selected_kind orelse return error.NotFound; │ │ 99 - const stored_kind = (try readKind(repo_opts.hash, kind_map, &id)) orelse return error.NotFound; │ │ 94 + const stored_kind = (try evt.readEventKind(repo_opts.hash, kind_map, &id)) orelse return error.NotFound; │ │ 95 if (stored_kind != kind) return error.NotFound; │ │101 - const created_ts = try readCreatedTs(repo_opts.hash, arena, haxy_moment, kind, &id); │ │102 - const key = evt.orderKeyDesc(created_ts, &id); │ │103 - if (!try id_set.contains(&key)) return error.NotFound; │ │104 - root_key = key; │ │ 96 + const meta = try readMeta(repo_opts.hash, arena, haxy_moment, kind, &id); │ │ 97 + result.view = if (meta.removed) .removed else .active; │ │ 98 + root_key = evt.orderKeyDesc(meta.created_ts, &id); │ │ 99 } │ │106 - var iter = if (root_key) |*key| try id_set.iteratorFrom(key) else try id_set.iteratorFromIndex(0); │ │100 + │ │101 + for ([_]ui.RoutablePage.EventsView{ .active, .removed }) |view| { │ │102 + const id_set_cursor = switch (view) { │ │103 + .active => active_cursor, │ │104 + .removed => removed_cursor, │ │105 + }; │ │106 + const event_window = switch (view) { │ │107 + .active => &result.active, │ │108 + .removed => &result.removed, │ │109 + }; │ │110 + const id_set = try DB.SortedSet(.read_only).init(id_set_cursor orelse { │ │111 + if (require_selected_event and result.view == view) return error.NotFound; │ │112 + continue; │ │113 + }); │ │114 + event_window.count = @intCast(try id_set.count()); │ │115 + const selected_key = if (result.view == view) root_key else null; │ │116 + if (selected_key) |*key| if (!try id_set.contains(key)) return error.NotFound; │ │117 │ │108 - var events: std.ArrayList(Item) = .empty; │ │109 - while (try iter.next()) |cursor_value| { │ │110 - const id = try evt.readOrderKeyId(DB, cursor_value); │ │111 - const kind = (try readKind(repo_opts.hash, kind_map, &id)) orelse continue; │ │112 - if (events.items.len == page_size) { │ │113 - result.next = .{ .id = try formatId(aa, &id), .kind = kind }; │ │114 - break; │ │118 + var iter = if (selected_key) |*key| try id_set.iteratorFrom(key) else try id_set.iteratorFromIndex(0); │ │119 + var events: std.ArrayList(Item) = .empty; │ │120 + while (try iter.next()) |cursor_value| { │ │121 + const id = try evt.readOrderKeyId(DB, cursor_value); │ │122 + const kind = (try evt.readEventKind(repo_opts.hash, kind_map, &id)) orelse continue; │ │123 + if (events.items.len == page_size) { │ │124 + event_window.next = .{ .id = try formatId(aa, &id), .kind = kind }; │ │125 + break; │ │126 + } │ │127 + const item = (try readItem(repo_opts.hash, arena, haxy_moment, kind_map, admin_moment, identity, view == .active, kind, &id)) orelse continue;│ │128 + try events.append(aa, item); │ │129 } │ │116 - const item = (try readItem(repo_opts.hash, arena, haxy_moment, kind_map, admin_moment, identity, kind, &id)) orelse continue; │ │117 - try events.append(aa, item); │ │130 + event_window.events = events.items; │ │131 } │ │119 - result.events = events.items; │ │132 return result; │ │121 -} │ │122 - │ │123 -fn readKind( │ │124 - comptime hash_kind: hash.HashKind, │ │125 - kind_map: evt.EventDB(hash_kind).HashMap(.read_only), │ │126 - id: *const [evt.event_id_size]u8, │ │127 -) !?evt.EventKind { │ │128 - const cursor = try kind_map.getCursor(hash.hashInt(hash_kind, id)) orelse return null; │ │129 - var buffer: [64]u8 = undefined; │ │130 - return std.meta.stringToEnum(evt.EventKind, try cursor.readBytes(&buffer)) orelse error.InvalidEventKind; │ │133 } │ │134 │ │133 -pub fn empty(aa: std.mem.Allocator, identity: []const u8, local: bool, failure: ?[]const u8) !Self { │ │135 +pub fn empty(aa: std.mem.Allocator, identity: []const u8, view: ui.RoutablePage.EventsView, local: bool, failure: ?[]const u8) !Self { │ │136 return .{ │ │137 .identity = try aa.dupe(u8, identity), │ │136 - .events = &.{}, │ │137 - .next = null, │ │138 - .header = if (local) try (try Header.empty(aa, failure)).withCount(aa, 0) else try Header.remote(aa, 0), │ │138 + .view = view, │ │139 + .active = .{}, │ │140 + .removed = .{}, │ │141 + .header = if (local) try Header.empty(aa, failure) else Header.remote(), │ │142 + }; │ │143 +} │ │144 + │ │145 +fn window(self: *const Self, view: ui.RoutablePage.EventsView) *const Window { │ │146 + return switch (view) { │ │147 + .active => &self.active, │ │148 + .removed => &self.removed, │ │149 }; │ │150 } │ │151 │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────╮ │169 return try evt.read(T.Record, DB, hash_kind, arena, record);│ │170 } │ │171 │ │162 -fn readCreatedTs( │ │172 +const EventMeta = struct { │ │173 + created_ts: u64, │ │174 + removed: bool, │ │175 +}; │ │176 + │ │177 +fn readMeta( │ │178 comptime hash_kind: hash.HashKind, │ │179 arena: *std.heap.ArenaAllocator, │ │180 haxy_moment: evt.EventDB(hash_kind).HashMap(.read_only), │ ╰─────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯