| 1 | package main |
| 2 | |
| 3 | import ( |
| 4 | "sort" |
| 5 | "strings" |
| 6 | |
| 7 | "reasonix/desktop/internal/workspacestate" |
| 8 | "reasonix/internal/session" |
| 9 | ) |
| 10 | |
| 11 | // Group and pin membership need no session header. Apply them before Stat so |
| 12 | // opening a small group cannot load every unrelated current-format manifest. |
| 13 | // The ordinary unfiltered view retains the existing header path. |
| 14 | func admittedWorkspaceTopicMembers(req ProjectTopicPageRequest, state workspacestate.State, workspace workspacestate.Workspace) []string { |
| 15 | grouped := req.GroupFilter == "group" || req.GroupFilter == "ungrouped" |
| 16 | if !grouped && !req.pinnedOnly && !req.ExcludePinned { |
| 17 | return workspace.SessionIDs |
| 18 | } |
| 19 | members := map[string]bool{} |
| 20 | groups := req.groupAll |
| 21 | if req.GroupFilter == "group" { |
| 22 | groups = nil |
| 23 | if req.groupSelected != nil { |
| 24 | groups = []desktopGroup{*req.groupSelected} |
| 25 | } |
| 26 | } |
| 27 | for _, group := range groups { |
| 28 | for _, key := range group.SessionKeys { |
| 29 | members[strings.TrimSpace(key)] = true |
| 30 | } |
| 31 | } |
| 32 | ids := make([]string, 0) |
| 33 | for _, id := range workspace.SessionIDs { |
| 34 | pin := state.Presentation[id].Pinned |
| 35 | if req.pinnedOnly && !pin || req.ExcludePinned && pin { |
| 36 | continue |
| 37 | } |
| 38 | member := members[workspacestate.SessionKey(id)] |
| 39 | if req.GroupFilter == "group" && !member || req.GroupFilter == "ungrouped" && member { |
| 40 | continue |
| 41 | } |
| 42 | ids = append(ids, id) |
| 43 | } |
| 44 | return ids |
| 45 | } |
| 46 | |
| 47 | // Sorting is owned by the immutable read snapshot. Do not retain a second |
| 48 | // cache of mutable materialized rows with an independent eviction policy. |
| 49 | func (a *App) indexedWorkspaceTopics(req ProjectTopicPageRequest, state workspacestate.State, workspace workspacestate.Workspace, org workspacestate.Organization, infos map[string]session.SessionInfo, sources []ProjectNode) []ProjectNode { |
| 50 | created := loadTopicCreatedAts(topicTitleRoot(req.Scope, req.WorkspaceRoot)) |
| 51 | all := req |
| 52 | all.Query, all.TimeFilter, all.GroupFilter = "", "", "all" |
| 53 | all.ExcludePinned, all.groupSelected, all.groupAll = false, nil, nil |
| 54 | nodes := a.canonicalTopicNodes(all, state, workspace, infos, sources, created) |
| 55 | nodes = filterWorkspaceSessionNodes(req, org, state, workspace.ID, nodes) |
| 56 | sort.SliceStable(nodes, func(i, j int) bool { return projectTopicLess(nodes[i], nodes[j], req.SortMode, org.ManualOrderEnabled) }) |
| 57 | return nodes |
| 58 | } |
| 59 | |
| 60 | func cloneTopicPage(nodes []ProjectNode) []ProjectNode { |
| 61 | result := make([]ProjectNode, len(nodes)) |
| 62 | for i, node := range nodes { |
| 63 | node.Children = cloneTopicPage(node.Children) |
| 64 | node.IdentityAliases = append([]string(nil), node.IdentityAliases...) |
| 65 | if node.Session != nil { |
| 66 | ref := *node.Session |
| 67 | node.Session = &ref |
| 68 | } |
| 69 | if node.ParentSession != nil { |
| 70 | ref := *node.ParentSession |
| 71 | node.ParentSession = &ref |
| 72 | } |
| 73 | if node.Source != nil { |
| 74 | ref := *node.Source |
| 75 | node.Source = &ref |
| 76 | } |
| 77 | if node.Remote != nil { |
| 78 | ref := *node.Remote |
| 79 | node.Remote = &ref |
| 80 | } |
| 81 | result[i] = node |
| 82 | } |
| 83 | return result |
| 84 | } |
| 85 |