Skip to content

Commit

Permalink
Improve the way favourites and pinned chats are handled (#6820)
Browse files Browse the repository at this point in the history
  • Loading branch information
julianjelfs authored Nov 14, 2024
1 parent 552f2e7 commit 39c7344
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 97 deletions.
3 changes: 2 additions & 1 deletion frontend/app/src/components/home/nav/LeftNav.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
communityChannelVideoCallCounts,
anonUser,
globalStateStore as globalState,
favouritesStore,
} from "openchat-client";
const client = getContext<OpenChat>("client");
Expand Down Expand Up @@ -214,7 +215,7 @@
<ForumOutline size={iconSize} color={"var(--icon-txt)"} />
</div>
</LeftNavItem>
{#if $globalState.favourites.size > 0}
{#if $favouritesStore.size > 0}
<LeftNavItem
selected={$chatListScope.kind === "favourite" && !communityExplorer}
disabled={$anonUser}
Expand Down
4 changes: 4 additions & 0 deletions frontend/openchat-client/src/liveState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import {
allChats,
currentChatMembers,
currentChatRules,
pinnedChatsStore,
} from "./stores/chat";
import { remainingStorage } from "./stores/storage";
import { userCreatedStore } from "./stores/userCreated";
Expand All @@ -67,6 +68,7 @@ import {
chatListScopeStore,
globalStateStore,
chitStateStore,
type PinnedByScope,
} from "./stores/global";
import { offlineStore } from "./stores/network";
import { type DraftMessages, draftMessagesStore } from "./stores/draftMessages";
Expand Down Expand Up @@ -116,6 +118,7 @@ export class LiveState {
communities!: CommunityMap<CommunitySummary>;
chatListScope!: ChatListScope;
globalState!: GlobalState;
pinnedChats!: PinnedByScope;
allChats!: ChatMap<ChatSummary>;
selectedCommunity!: CommunitySummary | undefined;
currentCommunityMembers!: Map<string, Member>;
Expand Down Expand Up @@ -179,6 +182,7 @@ export class LiveState {
communities.subscribe((data) => (this.communities = data));
chatListScopeStore.subscribe((scope) => (this.chatListScope = scope));
globalStateStore.subscribe((data) => (this.globalState = data));
pinnedChatsStore.subscribe((data) => (this.pinnedChats = data));
allChats.subscribe((data) => (this.allChats = data));
selectedCommunity.subscribe((data) => (this.selectedCommunity = data));
currentCommunityMembers.subscribe((data) => (this.currentCommunityMembers = data));
Expand Down
102 changes: 32 additions & 70 deletions frontend/openchat-client/src/openchat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1133,72 +1133,48 @@ export class OpenChat extends OpenChatAgentWorker {
});
}

private pinLocally(chatId: ChatIdentifier, scope: ChatListScope["kind"]): void {
globalStateStore.update((state) => {
const ids = state.pinnedChats[scope];
if (!ids.find((id) => chatIdentifiersEqual(id, chatId))) {
return {
...state,
pinnedChats: {
...state.pinnedChats,
[scope]: [chatId, ...ids],
},
};
}
return state;
});
}

private unpinLocally(chatId: ChatIdentifier, scope: ChatListScope["kind"]): void {
globalStateStore.update((state) => {
const ids = state.pinnedChats[scope];
const index = ids.findIndex((id) => chatIdentifiersEqual(id, chatId));
if (index >= 0) {
const ids_clone = [...ids];
ids_clone.splice(index, 1);
return {
...state,
pinnedChats: {
...state.pinnedChats,
[scope]: ids_clone,
},
};
}
return state;
});
}

pinned(scope: ChatListScope["kind"], chatId: ChatIdentifier): boolean {
const pinned = this._liveState.globalState.pinnedChats;
return pinned[scope].find((id) => chatIdentifiersEqual(id, chatId)) !== undefined;
const pinned = this._liveState.pinnedChats;
return pinned.get(scope)?.find((id) => chatIdentifiersEqual(id, chatId)) !== undefined;
}

pinChat(chatId: ChatIdentifier): Promise<boolean> {
const scope = this._liveState.chatListScope.kind;
this.pinLocally(chatId, scope);
localChatSummaryUpdates.pin(chatId, scope);
return this.sendRequest({
kind: "pinChat",
chatId,
favourite: scope === "favourite",
})
.then((resp) => resp === "success")
.then((resp) => {
if (resp !== "success") {
localChatSummaryUpdates.unpin(chatId, scope);
}
return resp === "success";
})
.catch(() => {
this.unpinLocally(chatId, scope);
localChatSummaryUpdates.unpin(chatId, scope);

return false;
});
}

unpinChat(chatId: ChatIdentifier): Promise<boolean> {
const scope = this._liveState.chatListScope.kind;
this.unpinLocally(chatId, scope);
localChatSummaryUpdates.unpin(chatId, scope);
return this.sendRequest({
kind: "unpinChat",
chatId,
favourite: scope === "favourite",
})
.then((resp) => resp === "success")
.then((resp) => {
if (resp !== "success") {
localChatSummaryUpdates.pin(chatId, scope);
}
return resp === "success";
})
.catch(() => {
this.pinLocally(chatId, scope);
localChatSummaryUpdates.pin(chatId, scope);
return false;
});
}
Expand Down Expand Up @@ -5719,13 +5695,13 @@ export class OpenChat extends OpenChatAgentWorker {
chatsResponse.state.communities,
chats,
chatsResponse.state.favouriteChats,
{
group_chat: chatsResponse.state.pinnedGroupChats,
direct_chat: chatsResponse.state.pinnedDirectChats,
favourite: chatsResponse.state.pinnedFavouriteChats,
community: chatsResponse.state.pinnedChannels,
none: [],
},
new Map<ChatListScope["kind"], ChatIdentifier[]>([
["group_chat", chatsResponse.state.pinnedGroupChats],
["direct_chat", chatsResponse.state.pinnedDirectChats],
["favourite", chatsResponse.state.pinnedFavouriteChats],
["community", chatsResponse.state.pinnedChannels],
["none", []],
]),
chatsResponse.state.achievements,
chatsResponse.state.chitState,
chatsResponse.state.referrals,
Expand Down Expand Up @@ -7278,46 +7254,32 @@ export class OpenChat extends OpenChatAgentWorker {
}));
}

private addToFavouritesLocally(chatId: ChatIdentifier): void {
globalStateStore.update((state) => {
state.favourites.add(chatId);
return state;
});
}

private removeFromFavouritesLocally(chatId: ChatIdentifier): void {
globalStateStore.update((state) => {
state.favourites.delete(chatId);
return state;
});
}

addToFavourites(chatId: ChatIdentifier): Promise<boolean> {
this.addToFavouritesLocally(chatId);
localChatSummaryUpdates.favourite(chatId);
return this.sendRequest({ kind: "addToFavourites", chatId })
.then((resp) => {
if (resp !== "success") {
this.removeFromFavouritesLocally(chatId);
localChatSummaryUpdates.unfavourite(chatId);
}
return resp === "success";
})
.catch(() => {
this.removeFromFavouritesLocally(chatId);
localChatSummaryUpdates.unfavourite(chatId);
return false;
});
}

removeFromFavourites(chatId: ChatIdentifier): Promise<boolean> {
this.removeFromFavouritesLocally(chatId);
localChatSummaryUpdates.unfavourite(chatId);
return this.sendRequest({ kind: "removeFromFavourites", chatId })
.then((resp) => {
if (resp !== "success") {
this.addToFavouritesLocally(chatId);
localChatSummaryUpdates.favourite(chatId);
}
return resp === "success";
})
.catch(() => {
this.addToFavouritesLocally(chatId);
localChatSummaryUpdates.favourite(chatId);
return false;
});
}
Expand Down
62 changes: 51 additions & 11 deletions frontend/openchat-client/src/stores/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,7 @@ import { setsAreEqual } from "../utils/set";
import { failedMessagesStore } from "./failedMessages";
import { proposalTallies } from "./proposalTallies";
import type { OpenChat } from "../openchat";
import {
allServerChats,
chatListScopeStore,
getAllServerChats,
globalStateStore,
pinnedChatsStore,
} from "./global";
import { allServerChats, chatListScopeStore, getAllServerChats, globalStateStore } from "./global";
import { createDerivedPropStore } from "./derived";
import { messagesRead } from "./markRead";
import { safeWritable } from "./safeWritable";
Expand Down Expand Up @@ -194,9 +188,55 @@ const currentChatBlockedOrSuspendedUsers = derived(
},
);

export const favouritesStore = derived(
[globalStateStore, localChatSummaryUpdates],
([$global, $localUpdates]) => {
const mergedFavs = $global.favourites.clone();
$localUpdates.entries().forEach(([key, val]) => {
if (val.favourited && !val.unfavourited) {
mergedFavs.add(key);
}
if (!val.favourited && val.unfavourited) {
mergedFavs.delete(key);
}
});
return mergedFavs;
},
);

export const pinnedChatsStore = derived(
[globalStateStore, localChatSummaryUpdates],
([$global, $localUpdates]) => {
const mergedPinned = new Map($global.pinnedChats);

$localUpdates.forEach((val, key) => {
if (val.pinned !== undefined) {
val.pinned.forEach((scope) => {
const ids = mergedPinned.get(scope) ?? [];
if (!ids.find((id) => chatIdentifiersEqual(id, key))) {
ids.unshift(key);
}
mergedPinned.set(scope, ids);
});
}
if (val.unpinned !== undefined) {
val.unpinned.forEach((scope) => {
const ids = mergedPinned.get(scope) ?? [];
mergedPinned.set(
scope,
ids.filter((id) => !chatIdentifiersEqual(id, key)),
);
});
}
});

return mergedPinned;
},
);

export const myServerChatSummariesStore = derived(
[globalStateStore, chatListScopeStore],
([$allState, $scope]) => {
[globalStateStore, chatListScopeStore, favouritesStore],
([$allState, $scope, $favourites]) => {
const allChats = getAllServerChats($allState);
if ($scope.kind === "community") {
const community = $allState.communities.get($scope.id);
Expand All @@ -206,7 +246,7 @@ export const myServerChatSummariesStore = derived(
} else if ($scope.kind === "direct_chat") {
return $allState.directChats;
} else if ($scope.kind === "favourite") {
return $allState.favourites.values().reduce((favs, chatId) => {
return $favourites.values().reduce((favs, chatId) => {
const chat = allChats.get(chatId);
if (chat !== undefined) {
favs.set(chat.id, chat);
Expand Down Expand Up @@ -338,7 +378,7 @@ export const chatSummariesStore: Readable<ChatMap<ChatSummary>> = derived(
// This is annoying. If only the pinnedChatIndex was stored in the chatSummary...
export const chatSummariesListStore = derived([chatSummariesStore], ([summaries]) => {
const pinnedChats = get(pinnedChatsStore);
const pinnedByScope = pinnedChats[currentScope.kind];
const pinnedByScope = pinnedChats.get(currentScope.kind) ?? [];
const pinned = pinnedByScope.reduce<ChatSummary[]>((result, id) => {
const summary = summaries.get(id);
if (summary !== undefined) {
Expand Down
23 changes: 8 additions & 15 deletions frontend/openchat-client/src/stores/global.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { messageActivityFeedReadUpToLocally, messagesRead } from "./markRead";
import { safeWritable } from "./safeWritable";
import { serverWalletConfigStore } from "./crypto";

export type PinnedByScope = Record<ChatListScope["kind"], ChatIdentifier[]>;
export type PinnedByScope = Map<ChatListScope["kind"], ChatIdentifier[]>;

// This will contain all state.
export type GlobalState = {
Expand All @@ -44,21 +44,18 @@ export const chitStateStore = immutableStore<ChitState>({
nextDailyChitClaim: 0n,
});

/**
* This is the root of the
*/
export const globalStateStore = immutableStore<GlobalState>({
communities: new CommunityMap<CommunitySummary>(),
directChats: new ChatMap<DirectChatSummary>(),
groupChats: new ChatMap<GroupChatSummary>(),
favourites: new ObjectSet<ChatIdentifier>(),
pinnedChats: {
group_chat: [],
direct_chat: [],
favourite: [],
community: [],
none: [],
},
pinnedChats: new Map<ChatListScope["kind"], ChatIdentifier[]>([
["group_chat", []],
["direct_chat", []],
["favourite", []],
["community", []],
["none", []],
]),
achievements: new Set(),
referrals: [],
messageActivitySummary: {
Expand All @@ -68,12 +65,8 @@ export const globalStateStore = immutableStore<GlobalState>({
},
});

export const pinnedChatsStore = derived(globalStateStore, ($global) => $global.pinnedChats);

export const chatListScopeStore = safeWritable<ChatListScope>({ kind: "none" }, chatScopesEqual);

export const favouritesStore = derived(globalStateStore, (state) => state.favourites);

export type CombinedUnreadCounts = {
threads: UnreadCounts;
chats: UnreadCounts;
Expand Down
Loading

0 comments on commit 39c7344

Please sign in to comment.