stream-chat 8.54.1 → 8.56.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser.es.js +4048 -2245
- package/dist/browser.es.js.map +1 -1
- package/dist/browser.full-bundle.min.js +1 -1
- package/dist/browser.full-bundle.min.js.map +1 -1
- package/dist/browser.js +4057 -2244
- package/dist/browser.js.map +1 -1
- package/dist/index.es.js +4048 -2245
- package/dist/index.es.js.map +1 -1
- package/dist/index.js +4057 -2244
- package/dist/index.js.map +1 -1
- package/dist/types/channel.d.ts.map +1 -1
- package/dist/types/channel_manager.d.ts +111 -0
- package/dist/types/channel_manager.d.ts.map +1 -0
- package/dist/types/client.d.ts +65 -2
- package/dist/types/client.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/insights.d.ts +2 -2
- package/dist/types/search_controller.d.ts +174 -0
- package/dist/types/search_controller.d.ts.map +1 -0
- package/dist/types/store.d.ts +3 -1
- package/dist/types/store.d.ts.map +1 -1
- package/dist/types/types.d.ts +11 -0
- package/dist/types/types.d.ts.map +1 -1
- package/dist/types/utils.d.ts +118 -1
- package/dist/types/utils.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/channel.ts +6 -7
- package/src/channel_manager.ts +581 -0
- package/src/client.ts +27 -3
- package/src/index.ts +2 -0
- package/src/search_controller.ts +489 -0
- package/src/store.ts +4 -3
- package/src/types.ts +12 -0
- package/src/utils.ts +356 -0
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
import { debounce, DebouncedFunc } from './utils';
|
|
2
|
+
import { StateStore } from './store';
|
|
3
|
+
import type { Channel } from './channel';
|
|
4
|
+
import type { StreamChat } from './client';
|
|
5
|
+
import type {
|
|
6
|
+
ChannelFilters,
|
|
7
|
+
ChannelOptions,
|
|
8
|
+
ChannelSort,
|
|
9
|
+
DefaultGenerics,
|
|
10
|
+
ExtendableGenerics,
|
|
11
|
+
MessageFilters,
|
|
12
|
+
MessageResponse,
|
|
13
|
+
SearchMessageSort,
|
|
14
|
+
SearchOptions,
|
|
15
|
+
UserFilters,
|
|
16
|
+
UserOptions,
|
|
17
|
+
UserResponse,
|
|
18
|
+
UserSort,
|
|
19
|
+
} from './types';
|
|
20
|
+
|
|
21
|
+
export type SearchSourceType = 'channels' | 'users' | 'messages' | (string & {});
|
|
22
|
+
export type QueryReturnValue<T> = { items: T[]; next?: string };
|
|
23
|
+
export type DebounceOptions = {
|
|
24
|
+
debounceMs: number;
|
|
25
|
+
};
|
|
26
|
+
type DebouncedExecQueryFunction = DebouncedFunc<(searchString?: string) => Promise<void>>;
|
|
27
|
+
|
|
28
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
29
|
+
export interface SearchSource<T = any> {
|
|
30
|
+
activate(): void;
|
|
31
|
+
deactivate(): void;
|
|
32
|
+
readonly hasNext: boolean;
|
|
33
|
+
readonly hasResults: boolean;
|
|
34
|
+
readonly initialState: SearchSourceState<T>;
|
|
35
|
+
readonly isActive: boolean;
|
|
36
|
+
readonly isLoading: boolean;
|
|
37
|
+
readonly items: T[] | undefined;
|
|
38
|
+
readonly lastQueryError: Error | undefined;
|
|
39
|
+
readonly next: string | undefined;
|
|
40
|
+
readonly offset: number | undefined;
|
|
41
|
+
resetState(): void;
|
|
42
|
+
search(text?: string): void;
|
|
43
|
+
searchDebounced: DebouncedExecQueryFunction;
|
|
44
|
+
readonly searchQuery: string;
|
|
45
|
+
setDebounceOptions(options: DebounceOptions): void;
|
|
46
|
+
readonly state: StateStore<SearchSourceState<T>>;
|
|
47
|
+
readonly type: SearchSourceType;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
51
|
+
export type SearchSourceState<T = any> = {
|
|
52
|
+
hasNext: boolean;
|
|
53
|
+
isActive: boolean;
|
|
54
|
+
isLoading: boolean;
|
|
55
|
+
items: T[] | undefined;
|
|
56
|
+
searchQuery: string;
|
|
57
|
+
lastQueryError?: Error;
|
|
58
|
+
next?: string;
|
|
59
|
+
offset?: number;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export type SearchSourceOptions = {
|
|
63
|
+
/** The number of milliseconds to debounce the search query. The default interval is 300ms. */
|
|
64
|
+
debounceMs?: number;
|
|
65
|
+
pageSize?: number;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const DEFAULT_SEARCH_SOURCE_OPTIONS: Required<SearchSourceOptions> = {
|
|
69
|
+
debounceMs: 300,
|
|
70
|
+
pageSize: 10,
|
|
71
|
+
} as const;
|
|
72
|
+
|
|
73
|
+
export abstract class BaseSearchSource<T> implements SearchSource<T> {
|
|
74
|
+
state: StateStore<SearchSourceState<T>>;
|
|
75
|
+
protected pageSize: number;
|
|
76
|
+
abstract readonly type: SearchSourceType;
|
|
77
|
+
searchDebounced!: DebouncedExecQueryFunction;
|
|
78
|
+
|
|
79
|
+
protected constructor(options?: SearchSourceOptions) {
|
|
80
|
+
const { debounceMs, pageSize } = { ...DEFAULT_SEARCH_SOURCE_OPTIONS, ...options };
|
|
81
|
+
this.pageSize = pageSize;
|
|
82
|
+
this.state = new StateStore<SearchSourceState<T>>(this.initialState);
|
|
83
|
+
this.setDebounceOptions({ debounceMs });
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
get lastQueryError() {
|
|
87
|
+
return this.state.getLatestValue().lastQueryError;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
get hasNext() {
|
|
91
|
+
return this.state.getLatestValue().hasNext;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
get hasResults() {
|
|
95
|
+
return Array.isArray(this.state.getLatestValue().items);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
get isActive() {
|
|
99
|
+
return this.state.getLatestValue().isActive;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
get isLoading() {
|
|
103
|
+
return this.state.getLatestValue().isLoading;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
get initialState() {
|
|
107
|
+
return {
|
|
108
|
+
hasNext: true,
|
|
109
|
+
isActive: false,
|
|
110
|
+
isLoading: false,
|
|
111
|
+
items: undefined,
|
|
112
|
+
lastQueryError: undefined,
|
|
113
|
+
next: undefined,
|
|
114
|
+
offset: 0,
|
|
115
|
+
searchQuery: '',
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
get items() {
|
|
120
|
+
return this.state.getLatestValue().items;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
get next() {
|
|
124
|
+
return this.state.getLatestValue().next;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
get offset() {
|
|
128
|
+
return this.state.getLatestValue().offset;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
get searchQuery() {
|
|
132
|
+
return this.state.getLatestValue().searchQuery;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
protected abstract query(searchQuery: string): Promise<QueryReturnValue<T>>;
|
|
136
|
+
|
|
137
|
+
protected abstract filterQueryResults(items: T[]): T[] | Promise<T[]>;
|
|
138
|
+
|
|
139
|
+
setDebounceOptions = ({ debounceMs }: DebounceOptions) => {
|
|
140
|
+
this.searchDebounced = debounce(this.executeQuery.bind(this), debounceMs);
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
activate = () => {
|
|
144
|
+
if (this.isActive) return;
|
|
145
|
+
this.state.partialNext({ isActive: true });
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
deactivate = () => {
|
|
149
|
+
if (!this.isActive) return;
|
|
150
|
+
this.state.partialNext({ isActive: false });
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
async executeQuery(newSearchString?: string) {
|
|
154
|
+
const hasNewSearchQuery = typeof newSearchString !== 'undefined';
|
|
155
|
+
const searchString = newSearchString ?? this.searchQuery;
|
|
156
|
+
if (!this.isActive || this.isLoading || (!this.hasNext && !hasNewSearchQuery) || !searchString) return;
|
|
157
|
+
|
|
158
|
+
if (hasNewSearchQuery) {
|
|
159
|
+
this.state.next({
|
|
160
|
+
...this.initialState,
|
|
161
|
+
isActive: this.isActive,
|
|
162
|
+
isLoading: true,
|
|
163
|
+
searchQuery: newSearchString ?? '',
|
|
164
|
+
});
|
|
165
|
+
} else {
|
|
166
|
+
this.state.partialNext({ isLoading: true });
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const stateUpdate: Partial<SearchSourceState<T>> = {};
|
|
170
|
+
try {
|
|
171
|
+
const results = await this.query(searchString);
|
|
172
|
+
if (!results) return;
|
|
173
|
+
const { items, next } = results;
|
|
174
|
+
|
|
175
|
+
if (next) {
|
|
176
|
+
stateUpdate.next = next;
|
|
177
|
+
stateUpdate.hasNext = !!next;
|
|
178
|
+
} else {
|
|
179
|
+
stateUpdate.offset = (this.offset ?? 0) + items.length;
|
|
180
|
+
stateUpdate.hasNext = items.length === this.pageSize;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
stateUpdate.items = await this.filterQueryResults(items);
|
|
184
|
+
} catch (e) {
|
|
185
|
+
stateUpdate.lastQueryError = e as Error;
|
|
186
|
+
} finally {
|
|
187
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
188
|
+
this.state.next(({ lastQueryError, ...current }: SearchSourceState<T>) => ({
|
|
189
|
+
...current,
|
|
190
|
+
...stateUpdate,
|
|
191
|
+
isLoading: false,
|
|
192
|
+
items: [...(current.items ?? []), ...(stateUpdate.items || [])],
|
|
193
|
+
}));
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
search = (searchQuery?: string) => {
|
|
198
|
+
this.searchDebounced(searchQuery);
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
resetState() {
|
|
202
|
+
this.state.next(this.initialState);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export class UserSearchSource<StreamChatGenerics extends ExtendableGenerics = DefaultGenerics> extends BaseSearchSource<
|
|
207
|
+
UserResponse<StreamChatGenerics>
|
|
208
|
+
> {
|
|
209
|
+
readonly type = 'users';
|
|
210
|
+
private client: StreamChat<StreamChatGenerics>;
|
|
211
|
+
filters: UserFilters<StreamChatGenerics> | undefined;
|
|
212
|
+
sort: UserSort<StreamChatGenerics> | undefined;
|
|
213
|
+
searchOptions: Omit<UserOptions, 'limit' | 'offset'> | undefined;
|
|
214
|
+
|
|
215
|
+
constructor(client: StreamChat<StreamChatGenerics>, options?: SearchSourceOptions) {
|
|
216
|
+
super(options);
|
|
217
|
+
this.client = client;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
protected async query(searchQuery: string) {
|
|
221
|
+
const filters = {
|
|
222
|
+
$or: [{ id: { $autocomplete: searchQuery } }, { name: { $autocomplete: searchQuery } }],
|
|
223
|
+
...this.filters,
|
|
224
|
+
} as UserFilters<StreamChatGenerics>;
|
|
225
|
+
const sort = { id: 1, ...this.sort } as UserSort<StreamChatGenerics>;
|
|
226
|
+
const options = { ...this.searchOptions, limit: this.pageSize, offset: this.offset };
|
|
227
|
+
const { users } = await this.client.queryUsers(filters, sort, options);
|
|
228
|
+
return { items: users };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
protected filterQueryResults(items: UserResponse<StreamChatGenerics>[]) {
|
|
232
|
+
return items.filter((u) => u.id !== this.client.user?.id);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export class ChannelSearchSource<
|
|
237
|
+
StreamChatGenerics extends ExtendableGenerics = DefaultGenerics
|
|
238
|
+
> extends BaseSearchSource<Channel<StreamChatGenerics>> {
|
|
239
|
+
readonly type = 'channels';
|
|
240
|
+
private client: StreamChat<StreamChatGenerics>;
|
|
241
|
+
filters: ChannelFilters<StreamChatGenerics> | undefined;
|
|
242
|
+
sort: ChannelSort<StreamChatGenerics> | undefined;
|
|
243
|
+
searchOptions: Omit<ChannelOptions, 'limit' | 'offset'> | undefined;
|
|
244
|
+
|
|
245
|
+
constructor(client: StreamChat<StreamChatGenerics>, options?: SearchSourceOptions) {
|
|
246
|
+
super(options);
|
|
247
|
+
this.client = client;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
protected async query(searchQuery: string) {
|
|
251
|
+
const filters = {
|
|
252
|
+
members: { $in: [this.client.userID] },
|
|
253
|
+
name: { $autocomplete: searchQuery },
|
|
254
|
+
...this.filters,
|
|
255
|
+
} as ChannelFilters<StreamChatGenerics>;
|
|
256
|
+
const sort = this.sort ?? {};
|
|
257
|
+
const options = { ...this.searchOptions, limit: this.pageSize, offset: this.offset };
|
|
258
|
+
const items = await this.client.queryChannels(filters, sort, options);
|
|
259
|
+
return { items };
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
protected filterQueryResults(items: Channel<StreamChatGenerics>[]) {
|
|
263
|
+
return items;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export class MessageSearchSource<
|
|
268
|
+
StreamChatGenerics extends ExtendableGenerics = DefaultGenerics
|
|
269
|
+
> extends BaseSearchSource<MessageResponse<StreamChatGenerics>> {
|
|
270
|
+
readonly type = 'messages';
|
|
271
|
+
private client: StreamChat<StreamChatGenerics>;
|
|
272
|
+
messageSearchChannelFilters: ChannelFilters<StreamChatGenerics> | undefined;
|
|
273
|
+
messageSearchFilters: MessageFilters<StreamChatGenerics> | undefined;
|
|
274
|
+
messageSearchSort: SearchMessageSort<StreamChatGenerics> | undefined;
|
|
275
|
+
channelQueryFilters: ChannelFilters<StreamChatGenerics> | undefined;
|
|
276
|
+
channelQuerySort: ChannelSort<StreamChatGenerics> | undefined;
|
|
277
|
+
channelQueryOptions: Omit<ChannelOptions, 'limit' | 'offset'> | undefined;
|
|
278
|
+
|
|
279
|
+
constructor(client: StreamChat<StreamChatGenerics>, options?: SearchSourceOptions) {
|
|
280
|
+
super(options);
|
|
281
|
+
this.client = client;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
protected async query(searchQuery: string) {
|
|
285
|
+
if (!this.client.userID) return { items: [] };
|
|
286
|
+
|
|
287
|
+
const channelFilters: ChannelFilters<StreamChatGenerics> = {
|
|
288
|
+
members: { $in: [this.client.userID] },
|
|
289
|
+
...this.messageSearchChannelFilters,
|
|
290
|
+
} as ChannelFilters<StreamChatGenerics>;
|
|
291
|
+
|
|
292
|
+
const messageFilters: MessageFilters<StreamChatGenerics> = {
|
|
293
|
+
text: searchQuery,
|
|
294
|
+
type: 'regular', // FIXME: type: 'reply' resp. do not filter by type and allow to jump to a message in a thread - missing support
|
|
295
|
+
...this.messageSearchFilters,
|
|
296
|
+
} as MessageFilters<StreamChatGenerics>;
|
|
297
|
+
|
|
298
|
+
const sort: SearchMessageSort<StreamChatGenerics> = {
|
|
299
|
+
created_at: -1,
|
|
300
|
+
...this.messageSearchSort,
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
const options = {
|
|
304
|
+
limit: this.pageSize,
|
|
305
|
+
next: this.next,
|
|
306
|
+
sort,
|
|
307
|
+
} as SearchOptions<StreamChatGenerics>;
|
|
308
|
+
|
|
309
|
+
const { next, results } = await this.client.search(channelFilters, messageFilters, options);
|
|
310
|
+
const items = results.map(({ message }) => message);
|
|
311
|
+
|
|
312
|
+
const cids = Array.from(
|
|
313
|
+
items.reduce((acc, message) => {
|
|
314
|
+
if (message.cid && !this.client.activeChannels[message.cid]) acc.add(message.cid);
|
|
315
|
+
return acc;
|
|
316
|
+
}, new Set<string>()), // keep the cids unique
|
|
317
|
+
);
|
|
318
|
+
const allChannelsLoadedLocally = cids.length === 0;
|
|
319
|
+
if (!allChannelsLoadedLocally) {
|
|
320
|
+
await this.client.queryChannels(
|
|
321
|
+
{
|
|
322
|
+
cid: { $in: cids },
|
|
323
|
+
...this.channelQueryFilters,
|
|
324
|
+
} as ChannelFilters<StreamChatGenerics>,
|
|
325
|
+
{
|
|
326
|
+
last_message_at: -1,
|
|
327
|
+
...this.channelQuerySort,
|
|
328
|
+
},
|
|
329
|
+
this.channelQueryOptions,
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return { items, next };
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
protected filterQueryResults(items: MessageResponse<StreamChatGenerics>[]) {
|
|
337
|
+
return items;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export type DefaultSearchSources<StreamChatGenerics extends ExtendableGenerics = DefaultGenerics> = [
|
|
342
|
+
UserSearchSource<StreamChatGenerics>,
|
|
343
|
+
ChannelSearchSource<StreamChatGenerics>,
|
|
344
|
+
MessageSearchSource<StreamChatGenerics>,
|
|
345
|
+
];
|
|
346
|
+
|
|
347
|
+
export type SearchControllerState = {
|
|
348
|
+
isActive: boolean;
|
|
349
|
+
searchQuery: string;
|
|
350
|
+
sources: SearchSource[];
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
export type InternalSearchControllerState<StreamChatGenerics extends ExtendableGenerics = DefaultGenerics> = {
|
|
354
|
+
// FIXME: focusedMessage should live in a MessageListController class that does not exist yet.
|
|
355
|
+
// This state prop should be then removed
|
|
356
|
+
focusedMessage?: MessageResponse<StreamChatGenerics>;
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
export type SearchControllerConfig = {
|
|
360
|
+
// The controller will make sure there is always exactly one active source. Enabled by default.
|
|
361
|
+
keepSingleActiveSource: boolean;
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
export type SearchControllerOptions = {
|
|
365
|
+
config?: Partial<SearchControllerConfig>;
|
|
366
|
+
sources?: SearchSource[];
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
export class SearchController<StreamChatGenerics extends ExtendableGenerics = DefaultGenerics> {
|
|
370
|
+
/**
|
|
371
|
+
* Not intended for direct use by integrators, might be removed without notice resulting in
|
|
372
|
+
* broken integrations.
|
|
373
|
+
*/
|
|
374
|
+
_internalState: StateStore<InternalSearchControllerState<StreamChatGenerics>>;
|
|
375
|
+
state: StateStore<SearchControllerState>;
|
|
376
|
+
config: SearchControllerConfig;
|
|
377
|
+
|
|
378
|
+
constructor({ config, sources }: SearchControllerOptions = {}) {
|
|
379
|
+
this.state = new StateStore<SearchControllerState>({
|
|
380
|
+
isActive: false,
|
|
381
|
+
searchQuery: '',
|
|
382
|
+
sources: sources ?? [],
|
|
383
|
+
});
|
|
384
|
+
this._internalState = new StateStore<InternalSearchControllerState<StreamChatGenerics>>({});
|
|
385
|
+
this.config = { keepSingleActiveSource: true, ...config };
|
|
386
|
+
}
|
|
387
|
+
get hasNext() {
|
|
388
|
+
return this.sources.some((source) => source.hasNext);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
get sources() {
|
|
392
|
+
return this.state.getLatestValue().sources;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
get activeSources() {
|
|
396
|
+
return this.state.getLatestValue().sources.filter((s) => s.isActive);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
get isActive() {
|
|
400
|
+
return this.state.getLatestValue().isActive;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
get searchQuery() {
|
|
404
|
+
return this.state.getLatestValue().searchQuery;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
get searchSourceTypes(): Array<SearchSource['type']> {
|
|
408
|
+
return this.sources.map((s) => s.type);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
addSource = (source: SearchSource) => {
|
|
412
|
+
this.state.partialNext({
|
|
413
|
+
sources: [...this.sources, source],
|
|
414
|
+
});
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
getSource = (sourceType: SearchSource['type']) => this.sources.find((s) => s.type === sourceType);
|
|
418
|
+
|
|
419
|
+
removeSource = (sourceType: SearchSource['type']) => {
|
|
420
|
+
const newSources = this.sources.filter((s) => s.type !== sourceType);
|
|
421
|
+
if (newSources.length === this.sources.length) return;
|
|
422
|
+
this.state.partialNext({ sources: newSources });
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
activateSource = (sourceType: SearchSource['type']) => {
|
|
426
|
+
const source = this.getSource(sourceType);
|
|
427
|
+
if (!source || source.isActive) return;
|
|
428
|
+
if (this.config.keepSingleActiveSource) {
|
|
429
|
+
this.sources.forEach((s) => {
|
|
430
|
+
if (s.type !== sourceType) {
|
|
431
|
+
s.deactivate();
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
source.activate();
|
|
436
|
+
this.state.partialNext({ sources: [...this.sources] });
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
deactivateSource = (sourceType: SearchSource['type']) => {
|
|
440
|
+
const source = this.getSource(sourceType);
|
|
441
|
+
if (!source?.isActive) return;
|
|
442
|
+
if (this.activeSources.length === 1) return;
|
|
443
|
+
source.deactivate();
|
|
444
|
+
this.state.partialNext({ sources: [...this.sources] });
|
|
445
|
+
};
|
|
446
|
+
|
|
447
|
+
activate = () => {
|
|
448
|
+
if (!this.activeSources.length) {
|
|
449
|
+
const sourcesToActivate = this.config.keepSingleActiveSource ? this.sources.slice(0, 1) : this.sources;
|
|
450
|
+
sourcesToActivate.forEach((s) => s.activate());
|
|
451
|
+
}
|
|
452
|
+
if (this.isActive) return;
|
|
453
|
+
this.state.partialNext({ isActive: true });
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
search = async (searchQuery?: string) => {
|
|
457
|
+
const searchedSources = this.activeSources;
|
|
458
|
+
this.state.partialNext({
|
|
459
|
+
searchQuery,
|
|
460
|
+
});
|
|
461
|
+
await Promise.all(searchedSources.map((source) => source.search(searchQuery)));
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
cancelSearchQueries = () => {
|
|
465
|
+
this.activeSources.forEach((s) => s.searchDebounced.cancel());
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
clear = () => {
|
|
469
|
+
this.cancelSearchQueries();
|
|
470
|
+
this.sources.forEach((source) => source.state.next({ ...source.initialState, isActive: source.isActive }));
|
|
471
|
+
this.state.next((current) => ({
|
|
472
|
+
...current,
|
|
473
|
+
isActive: true,
|
|
474
|
+
queriesInProgress: [],
|
|
475
|
+
searchQuery: '',
|
|
476
|
+
}));
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
exit = () => {
|
|
480
|
+
this.cancelSearchQueries();
|
|
481
|
+
this.sources.forEach((source) => source.state.next({ ...source.initialState, isActive: source.isActive }));
|
|
482
|
+
this.state.next((current) => ({
|
|
483
|
+
...current,
|
|
484
|
+
isActive: false,
|
|
485
|
+
queriesInProgress: [],
|
|
486
|
+
searchQuery: '',
|
|
487
|
+
}));
|
|
488
|
+
};
|
|
489
|
+
}
|
package/src/store.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
export type Patch<T> = (value: T) => T;
|
|
2
|
+
export type ValueOrPatch<T> = T | Patch<T>;
|
|
2
3
|
export type Handler<T> = (nextValue: T, previousValue: T | undefined) => void;
|
|
3
4
|
export type Unsubscribe = () => void;
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
export const isPatch = <T>(value: ValueOrPatch<T>): value is Patch<T> => {
|
|
6
7
|
return typeof value === 'function';
|
|
7
|
-
}
|
|
8
|
+
};
|
|
8
9
|
|
|
9
10
|
export class StateStore<T extends Record<string, unknown>> {
|
|
10
11
|
private handlerSet = new Set<Handler<T>>();
|
|
@@ -13,7 +14,7 @@ export class StateStore<T extends Record<string, unknown>> {
|
|
|
13
14
|
|
|
14
15
|
constructor(private value: T) {}
|
|
15
16
|
|
|
16
|
-
public next = (newValueOrPatch:
|
|
17
|
+
public next = (newValueOrPatch: ValueOrPatch<T>): void => {
|
|
17
18
|
// newValue (or patch output) should never be mutated previous value
|
|
18
19
|
const newValue = isPatch(newValueOrPatch) ? newValueOrPatch(this.value) : newValueOrPatch;
|
|
19
20
|
|
package/src/types.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { AxiosRequestConfig, AxiosResponse } from 'axios';
|
|
|
2
2
|
import { StableWSConnection } from './connection';
|
|
3
3
|
import { EVENT_MAP } from './events';
|
|
4
4
|
import { Role } from './permissions';
|
|
5
|
+
import type { Channel } from './channel';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Utility Types
|
|
@@ -3796,3 +3797,14 @@ export type VelocityFilterConfig = {
|
|
|
3796
3797
|
rules: VelocityFilterConfigRule[];
|
|
3797
3798
|
async?: boolean;
|
|
3798
3799
|
};
|
|
3800
|
+
|
|
3801
|
+
export type PromoteChannelParams<SCG extends ExtendableGenerics = DefaultGenerics> = {
|
|
3802
|
+
channels: Array<Channel<SCG>>;
|
|
3803
|
+
channelToMove: Channel<SCG>;
|
|
3804
|
+
sort: ChannelSort<SCG>;
|
|
3805
|
+
/**
|
|
3806
|
+
* If the index of the channel within `channels` list which is being moved upwards
|
|
3807
|
+
* (`channelToMove`) is known, you can supply it to skip extra calculation.
|
|
3808
|
+
*/
|
|
3809
|
+
channelToMoveIndexWithinChannels?: number;
|
|
3810
|
+
};
|