Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions apps/mobile/src/features/home/HomeHeader.android.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { NativeStackScreenOptions } from "../../native/StackHeader";
import { MaterialThreadListToolbar } from "./MaterialThreadListToolbar";
import type { HomeHeaderProps } from "./HomeHeader.types";
import { isDefaultThreadListPages, threadListPageLabel } from "../threads/threadListV2";
import { filterChoiceTitle } from "./home-list-filter-menu";

export type { HomeHeaderEnvironment } from "./HomeHeader.types";

Expand Down Expand Up @@ -33,7 +34,7 @@ export function HomeHeader(props: HomeHeaderProps) {
},
...props.environments.map((environment) => ({
id: `environment:${environment.environmentId}`,
title: environment.label,
title: filterChoiceTitle(environment.label, environment.count),
state: checkedMenuState(props.selectedEnvironmentId === environment.environmentId),
})),
],
Expand All @@ -52,7 +53,7 @@ export function HomeHeader(props: HomeHeaderProps) {
},
...props.organisations.map((organisation) => ({
id: `organisation:${organisation.key}`,
title: organisation.label,
title: filterChoiceTitle(organisation.label, organisation.count),
state: checkedMenuState(props.organisationKeys.includes(organisation.key)),
})),
],
Expand All @@ -72,7 +73,7 @@ export function HomeHeader(props: HomeHeaderProps) {
},
...props.projects.map((project) => ({
id: `project:${project.key}`,
title: project.label,
title: filterChoiceTitle(project.label, project.count),
state: checkedMenuState(props.selectedProjectKey === project.key),
})),
],
Expand All @@ -84,7 +85,10 @@ export function HomeHeader(props: HomeHeaderProps) {
subactions: [
...props.availablePages.map((page) => ({
id: `page:${page}`,
title: threadListPageLabel(page, props.groups),
title: filterChoiceTitle(
threadListPageLabel(page, props.groups),
props.pageCounts?.get(page),
),
state: checkedMenuState(props.pages.includes(page)),
})),
...(props.onCreateGroup ? [{ id: "new-group", title: "New group…" }] : []),
Expand All @@ -98,6 +102,7 @@ export function HomeHeader(props: HomeHeaderProps) {
props.onCreateGroup,
props.organisationKeys,
props.organisations,
props.pageCounts,
props.pages,
props.projects,
props.selectedEnvironmentId,
Expand Down
19 changes: 14 additions & 5 deletions apps/mobile/src/features/home/HomeHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { withNativeGlassHeaderItem } from "../layout/native-glass-header-items";
import { createNativeMailSearchToolbarItem } from "../layout/native-mail-search-toolbar";
import { useNativeMailSearchToolbar } from "../../native/use-native-mail-search-toolbar";
import { isDefaultThreadListPages, threadListPageLabel } from "../threads/threadListV2";
import { buildHomeListFilterMenu } from "./home-list-filter-menu";
import { buildHomeListFilterMenu, filterChoiceTitle } from "./home-list-filter-menu";
import { createSidebarHeaderItems } from "../threads/sidebar-native-header-items";
import type { HomeHeaderProps } from "./HomeHeader.types";
import { useAdaptiveWorkspaceLayout } from "../layout/AdaptiveWorkspaceLayout";
Expand Down Expand Up @@ -195,7 +195,9 @@ export function HomeHeader(props: HomeHeaderProps) {
isOn={props.selectedEnvironmentId === environment.environmentId}
onPress={() => props.onEnvironmentChange(environment.environmentId)}
>
<NativeHeaderToolbar.Label>{environment.label}</NativeHeaderToolbar.Label>
<NativeHeaderToolbar.Label>
{filterChoiceTitle(environment.label, environment.count)}
</NativeHeaderToolbar.Label>
</NativeHeaderToolbar.MenuAction>
))}
</NativeHeaderToolbar.Menu>
Expand All @@ -216,7 +218,9 @@ export function HomeHeader(props: HomeHeaderProps) {
isOn={props.organisationKeys.includes(organisation.key)}
onPress={() => props.onToggleOrganisation(organisation.key)}
>
<NativeHeaderToolbar.Label>{organisation.label}</NativeHeaderToolbar.Label>
<NativeHeaderToolbar.Label>
{filterChoiceTitle(organisation.label, organisation.count)}
</NativeHeaderToolbar.Label>
</NativeHeaderToolbar.MenuAction>
))}
</NativeHeaderToolbar.Menu>
Expand All @@ -238,7 +242,9 @@ export function HomeHeader(props: HomeHeaderProps) {
isOn={props.selectedProjectKey === project.key}
onPress={() => props.onProjectChange(project.key)}
>
<NativeHeaderToolbar.Label>{project.label}</NativeHeaderToolbar.Label>
<NativeHeaderToolbar.Label>
{filterChoiceTitle(project.label, project.count)}
</NativeHeaderToolbar.Label>
</NativeHeaderToolbar.MenuAction>
))}
</NativeHeaderToolbar.Menu>
Expand All @@ -252,7 +258,10 @@ export function HomeHeader(props: HomeHeaderProps) {
onPress={() => props.onTogglePage(page)}
>
<NativeHeaderToolbar.Label>
{threadListPageLabel(page, props.groups)}
{filterChoiceTitle(
threadListPageLabel(page, props.groups),
props.pageCounts?.get(page),
)}
</NativeHeaderToolbar.Label>
</NativeHeaderToolbar.MenuAction>
))}
Expand Down
2 changes: 2 additions & 0 deletions apps/mobile/src/features/home/HomeHeader.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export interface HomeHeaderProps {
readonly pages: ReadonlyArray<ThreadListPage>;
readonly availablePages: ReadonlyArray<ThreadListPage>;
readonly onTogglePage: (page: ThreadListPage) => void;
/** Threads each page would show under the other filters. */
readonly pageCounts?: ReadonlyMap<ThreadListPage, number>;
readonly groups: ThreadGroups;
/** Adds "New group…" to Show; omitted when no environment supports groups. */
readonly onCreateGroup?: () => void;
Expand Down
33 changes: 28 additions & 5 deletions apps/mobile/src/features/home/HomeRouteScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { NativeHeaderToolbar, NativeStackScreenOptions } from "../../native/Stac
import { useProjects } from "../../state/entities";
import { useThreadListPages } from "../threads/use-thread-list-pages";
import { useThreadListOrganisations } from "./use-thread-list-organisations";
import { useThreadListFilterFacets } from "./use-thread-list-filter-facets";
import { environmentThreadShells } from "../../state/threads";
import { usePendingNewTasks } from "../../state/use-pending-new-tasks";
import { useWorkspaceState } from "../../state/workspace";
Expand Down Expand Up @@ -191,6 +192,25 @@ export function HomeRouteScreen() {
)
? resolvedProjectKey
: null;
const filterFacets = useThreadListFilterFacets({
threads: visibleThreads,
projects,
projectGroupingMode: listOptions.projectGroupingMode,
organisations,
environmentId: selectedEnvironmentId,
projectKey: selectedProjectKey,
organisationKeys,
pages,
searchQuery,
});
const filterEnvironments = useMemo(
() =>
environments.map((environment) => ({
...environment,
count: filterFacets.environmentCounts.get(environment.environmentId) ?? 0,
})),
[environments, filterFacets.environmentCounts],
);

// In split layouts the persistent sidebar IS the thread list — Home becomes
// an empty detail pane so selecting a thread never transitions layouts.
Expand Down Expand Up @@ -271,8 +291,8 @@ export function HomeRouteScreen() {
)}
<HomeHeader
usesWorkspaceToolbar={workspace !== null}
environments={environments}
projects={projectFilterOptions}
environments={filterEnvironments}
projects={filterFacets.projects}
searchQuery={searchQuery}
selectedEnvironmentId={selectedEnvironmentId}
selectedProjectKey={selectedProjectKey}
Expand All @@ -281,9 +301,10 @@ export function HomeRouteScreen() {
pages={pages}
availablePages={availablePages}
onTogglePage={togglePage}
pageCounts={filterFacets.pageCounts}
groups={groups}
onCreateGroup={groupsSupported ? createThreadGroup : undefined}
organisations={organisations}
organisations={filterFacets.organisations}
organisationKeys={organisationKeys}
onToggleOrganisation={toggleOrganisation}
onClearOrganisations={clearOrganisations}
Expand All @@ -305,7 +326,7 @@ export function HomeRouteScreen() {

<HomeScreen
catalogState={catalogState}
environments={environments}
environments={filterEnvironments}
onAddConnection={() =>
navigation.navigate("SettingsSheet", {
screen: "SettingsContent",
Expand Down Expand Up @@ -360,7 +381,9 @@ export function HomeRouteScreen() {
selectedEnvironmentId={selectedEnvironmentId}
selectedProjectKey={selectedProjectKey}
organisationKeys={organisationKeys}
organisations={organisations}
organisations={filterFacets.organisations}
filterProjects={filterFacets.projects}
pageCounts={filterFacets.pageCounts}
onToggleOrganisation={toggleOrganisation}
onClearOrganisations={clearOrganisations}
pages={pages}
Expand Down
11 changes: 9 additions & 2 deletions apps/mobile/src/features/home/HomeScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,10 @@ import {
type ThreadListOrganisation,
} from "./thread-list-organisations";
import { useThreadListV2ShelfPreferences } from "../threads/use-thread-list-v2-shelf-preferences";
import type { HomeListFilterMenuEnvironment } from "./home-list-filter-menu";
import type {
HomeListFilterMenuEnvironment,
HomeListFilterMenuProject,
} from "./home-list-filter-menu";
import {
buildHomeProjectScopes,
findHomeProjectScope,
Expand Down Expand Up @@ -105,6 +108,9 @@ interface HomeScreenProps {
/** Picked Organisations filter keys; empty means every organisation. */
readonly organisationKeys: ReadonlyArray<string>;
readonly organisations: ReadonlyArray<ThreadListOrganisation>;
/** The Project filter's choices, with counts; see buildThreadListFilterFacets. */
readonly filterProjects: ReadonlyArray<HomeListFilterMenuProject>;
readonly pageCounts: ReadonlyMap<ThreadListPage, number>;
readonly onToggleOrganisation: (key: string) => void;
readonly onClearOrganisations: () => void;
/** Filter-menu pages: which groups the list shows. */
Expand Down Expand Up @@ -1080,14 +1086,15 @@ export function HomeScreen(props: HomeScreenProps) {
{Platform.OS === "ios" && workspace ? (
<ThreadListFilters
environments={props.environments}
projects={projectScopes.map((scope) => ({ key: scope.key, label: scope.title }))}
projects={props.filterProjects}
selectedEnvironmentId={props.selectedEnvironmentId}
selectedProjectKey={props.selectedProjectKey}
onEnvironmentChange={props.onEnvironmentChange}
onProjectChange={props.onProjectChange}
pages={props.pages}
availablePages={props.availablePages}
onTogglePage={props.onTogglePage}
pageCounts={props.pageCounts}
groups={props.groups}
onCreateGroup={props.onCreateGroup}
organisations={props.organisations}
Expand Down
13 changes: 9 additions & 4 deletions apps/mobile/src/features/home/ThreadListFilters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Pressable, ScrollView } from "react-native";
import { SymbolView } from "../../components/AppSymbol";
import { ListFilterMenu } from "../workspace/ListFilterMenu";
import { isDefaultThreadListPages, threadListPageLabel } from "../threads/threadListV2";
import { filterChoiceTitle } from "./home-list-filter-menu";
import type { HomeHeaderProps } from "./HomeHeader.types";

export function ThreadListFilters(
Expand All @@ -16,6 +17,7 @@ export function ThreadListFilters(
| "pages"
| "availablePages"
| "onTogglePage"
| "pageCounts"
| "groups"
| "onCreateGroup"
| "organisations"
Expand Down Expand Up @@ -48,7 +50,7 @@ export function ThreadListFilters(
},
...props.environments.map((environment) => ({
id: JSON.stringify(environment.environmentId),
title: environment.label,
title: filterChoiceTitle(environment.label, environment.count),
state:
environment.environmentId === props.selectedEnvironmentId
? ("on" as const)
Expand Down Expand Up @@ -77,7 +79,7 @@ export function ThreadListFilters(
},
...props.projects.map((project) => ({
id: JSON.stringify(project.key),
title: project.label,
title: filterChoiceTitle(project.label, project.count),
state: project.key === props.selectedProjectKey ? ("on" as const) : ("off" as const),
})),
]}
Expand Down Expand Up @@ -112,7 +114,7 @@ export function ThreadListFilters(
},
...props.organisations.map((organisation) => ({
id: JSON.stringify(organisation.key),
title: organisation.label,
title: filterChoiceTitle(organisation.label, organisation.count),
state: props.organisationKeys.includes(organisation.key)
? ("on" as const)
: ("off" as const),
Expand All @@ -136,7 +138,10 @@ export function ThreadListFilters(
actions={[
...props.availablePages.map((page) => ({
id: JSON.stringify(page),
title: threadListPageLabel(page, props.groups),
title: filterChoiceTitle(
threadListPageLabel(page, props.groups),
props.pageCounts?.get(page),
),
state: props.pages.includes(page) ? ("on" as const) : ("off" as const),
})),
...(props.onCreateGroup ? [{ id: "new-group", title: "New group…" }] : []),
Expand Down
22 changes: 18 additions & 4 deletions apps/mobile/src/features/home/home-list-filter-menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,20 @@ import type { ThreadListOrganisation } from "./thread-list-organisations";
export interface HomeListFilterMenuEnvironment {
readonly environmentId: EnvironmentId;
readonly label: string;
/** Threads picking it would show under the other filters; see buildThreadListFilterFacets. */
readonly count?: number;
}

export interface HomeListFilterMenuProject {
readonly key: string;
readonly label: string;
readonly count?: number;
}

/** A filter choice's menu title, with how many threads picking it would show. Native menus
have no trailing column on every platform, so the count follows the label. */
export function filterChoiceTitle(label: string, count: number | undefined): string {
return count === undefined ? label : `${label} (${count})`;
}

type HomeListFilterMenuAction = {
Expand Down Expand Up @@ -45,6 +54,8 @@ export function buildHomeListFilterMenu(props: {
/** Pages offered in the Show submenu, from availableThreadListPages. */
readonly availablePages: ReadonlyArray<ThreadListPage>;
readonly onTogglePage: (page: ThreadListPage) => void;
/** Threads each page would show under the other filters. */
readonly pageCounts?: ReadonlyMap<ThreadListPage, number>;
/** Registered groups, for the emoji prefix on group pages. */
readonly groups?: ThreadGroups;
/** Adds "New group…" to Show; omitted when no environment supports groups. */
Expand All @@ -70,7 +81,7 @@ export function buildHomeListFilterMenu(props: {
},
...props.environments.map((environment) => ({
type: "action" as const,
title: environment.label,
title: filterChoiceTitle(environment.label, environment.count),
state:
props.selectedEnvironmentId === environment.environmentId
? ("on" as const)
Expand All @@ -94,7 +105,7 @@ export function buildHomeListFilterMenu(props: {
},
...props.organisations.map((organisation) => ({
type: "action" as const,
title: organisation.label,
title: filterChoiceTitle(organisation.label, organisation.count),
state: props.organisationKeys.includes(organisation.key)
? ("on" as const)
: ("off" as const),
Expand All @@ -118,7 +129,7 @@ export function buildHomeListFilterMenu(props: {
},
...props.projects.map((project) => ({
type: "action" as const,
title: project.label,
title: filterChoiceTitle(project.label, project.count),
state: props.selectedProjectKey === project.key ? ("on" as const) : ("off" as const),
onPress: () => props.onProjectChange(project.key),
})),
Expand All @@ -133,7 +144,10 @@ export function buildHomeListFilterMenu(props: {
items: [
...props.availablePages.map((page) => ({
type: "action" as const,
title: threadListPageLabel(page, props.groups),
title: filterChoiceTitle(
threadListPageLabel(page, props.groups),
props.pageCounts?.get(page),
),
state: props.pages.includes(page) ? ("on" as const) : ("off" as const),
onPress: () => props.onTogglePage(page),
})),
Expand Down
Loading