111 lines
3.9 KiB
TypeScript
111 lines
3.9 KiB
TypeScript
import type { SpaceBot, SpaceGroup } from "@rakazo/contracts";
|
|
import { groupBotsForSidebar } from "@rakazo/core";
|
|
import {
|
|
adoptDeletedSpaceFallback,
|
|
type MobileBot,
|
|
type MobileGroup,
|
|
type MobileSpace,
|
|
rpc,
|
|
selectedSpaceId,
|
|
selectSpace,
|
|
} from "./api";
|
|
|
|
export type InboxSpace = Pick<
|
|
MobileSpace,
|
|
"id" | "name" | "isDefault" | "hasContent" | "canDelete" | "botSections" | "icon" | "color"
|
|
> & {
|
|
bots: (MobileBot | SpaceBot)[];
|
|
groups: (MobileGroup | SpaceGroup)[];
|
|
};
|
|
|
|
export type InboxSpaceItem =
|
|
| { type: "bot"; bot: MobileBot | SpaceBot }
|
|
| { type: "group"; group: MobileGroup | SpaceGroup }
|
|
| { type: "heading"; key: string; title: string; space?: InboxSpace };
|
|
|
|
export function canDeleteInboxSpace(
|
|
space: Pick<InboxSpace, "isDefault" | "canDelete"> & Partial<Pick<InboxSpace, "hasContent">>,
|
|
) {
|
|
return !space.isDefault && space.canDelete === true;
|
|
}
|
|
|
|
export function spaceInboxItems(spaces: InboxSpace[]): InboxSpaceItem[] {
|
|
const seenProjectNames = new Set<string>();
|
|
const canonicalProjectIds = new Set<string>();
|
|
const orderedProjects = [...spaces]
|
|
.filter((space) => !space.isDefault)
|
|
.sort((left, right) => Number(Boolean(right.hasContent)) - Number(Boolean(left.hasContent)));
|
|
for (const space of orderedProjects) {
|
|
const normalized = space.name.trim().toLowerCase();
|
|
if (normalized && seenProjectNames.has(normalized)) continue;
|
|
if (normalized) seenProjectNames.add(normalized);
|
|
canonicalProjectIds.add(space.id);
|
|
}
|
|
|
|
const visibleSpaces = spaces.filter(
|
|
(space) => space.isDefault || canonicalProjectIds.has(space.id),
|
|
);
|
|
|
|
return visibleSpaces.flatMap((space): InboxSpaceItem[] => {
|
|
const normalizedSpaceName = space.name.trim().toLowerCase();
|
|
const chats = [
|
|
// A main bot is reached through its space's main conversation, not listed again.
|
|
...space.bots
|
|
.filter((chat) => chat.kind !== "main")
|
|
.map((chat) => ({ type: "bot" as const, bot: chat, ...chat })),
|
|
...space.groups
|
|
.filter((chat) => {
|
|
const normalizedGroupName = chat.name.trim().toLowerCase();
|
|
if (!space.isDefault && normalizedGroupName === normalizedSpaceName) return false;
|
|
if (space.isDefault && chat.kind !== "main" && seenProjectNames.has(normalizedGroupName))
|
|
return false;
|
|
return true;
|
|
})
|
|
.map((chat) => ({ type: "group" as const, group: chat, ...chat })),
|
|
];
|
|
const items: InboxSpaceItem[] = [];
|
|
if (visibleSpaces.length > 1 || !space.isDefault || chats.length === 0) {
|
|
items.push({ type: "heading", key: space.id, title: space.name, space });
|
|
}
|
|
for (const group of groupBotsForSidebar(chats, space.botSections)) {
|
|
if (group.title) {
|
|
items.push({ type: "heading", key: `${space.id}:${group.key}`, title: group.title });
|
|
}
|
|
items.push(...group.bots);
|
|
}
|
|
return items;
|
|
});
|
|
}
|
|
|
|
export async function selectInboxSpace(spaceId: string, refresh: () => Promise<void>) {
|
|
if (!(await selectSpace(spaceId).catch(() => false))) return false;
|
|
await refresh();
|
|
return true;
|
|
}
|
|
|
|
export async function retryInboxSpaceFallback(spaceId: string, refresh: () => Promise<void>) {
|
|
if (!(await adoptDeletedSpaceFallback(spaceId))) return false;
|
|
await refresh();
|
|
return true;
|
|
}
|
|
|
|
// Return the selection to retry if native storage fails after a successful deletion.
|
|
// The inbox must not refresh against the deleted selection in that case.
|
|
export async function removeInboxSpace(
|
|
spaceId: string,
|
|
refresh: () => Promise<void>,
|
|
): Promise<string | null> {
|
|
const response = await rpc<{ ok: true; activeSpaceId: string }>("spaces/remove", { spaceId });
|
|
if (selectedSpaceId() === spaceId) {
|
|
if (await selectSpace(response.activeSpaceId).catch(() => false)) {
|
|
await refresh();
|
|
return null;
|
|
}
|
|
const durable = await adoptDeletedSpaceFallback(response.activeSpaceId);
|
|
await refresh();
|
|
return durable ? null : response.activeSpaceId;
|
|
}
|
|
await refresh();
|
|
return null;
|
|
}
|