Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
0393703
Prototype some minimongo helpers
tassoevan Dec 16, 2020
07d0610
Refactor some modules
tassoevan Dec 17, 2020
022ab5d
Expose createFilterFromQuery helper
tassoevan Dec 17, 2020
a648649
Expose createComparatorFromSort
tassoevan Dec 17, 2020
f5232e0
Fix unit test
tassoevan Jan 5, 2021
77da569
Fix types
tassoevan Jan 5, 2021
12d7078
Fix variable initialization
tassoevan Dec 30, 2020
099839c
Move async state logic to helpers
tassoevan Dec 19, 2020
3b5b087
Add IMessage['tlm']
tassoevan Jan 6, 2021
dc9ba63
Prototype message lists
tassoevan Jan 6, 2021
3ec0b9a
Fix useAsyncState
tassoevan Jan 7, 2021
e639b0b
Merge branch 'develop' of github.com:RocketChat/Rocket.Chat into feat…
tassoevan Jan 7, 2021
b159939
Make RecordList stateful
tassoevan Jan 7, 2021
470b938
Change signature of ServerContext's useStream
tassoevan Jan 8, 2021
c4cf843
Update index.js
ggazzo Jan 8, 2021
836dfe4
Prototype hooks for threads list
tassoevan Jan 8, 2021
017ec5c
Merge branch 'develop' into feat/message-list
tassoevan Jan 8, 2021
c7a2f86
Add useMessageListInRoom hook
tassoevan Jan 11, 2021
60b7326
Merge branch 'develop' of github.com:RocketChat/Rocket.Chat into feat…
tassoevan Jan 11, 2021
9101a64
Split hooks
tassoevan Jan 11, 2021
15c567c
Get initial list size
tassoevan Jan 12, 2021
0671d50
Apply MessageList to threads and discussions
tassoevan Jan 12, 2021
d03ec44
Merge branch 'develop' into feat/message-list
tassoevan Jan 12, 2021
e6e67ee
Move definition of initialItemCount
tassoevan Jan 12, 2021
6843d95
Suppress React warnings from Translate icon from room header
tassoevan Jan 12, 2021
68f95e0
Fix review - 1
ggazzo Jan 12, 2021
3491cd3
Handle getConfig with number value
tassoevan Jan 12, 2021
9a6c202
Fix filter application
tassoevan Jan 12, 2021
271ab11
Embed itemCount into RecordList
tassoevan Jan 13, 2021
cf730ca
Merge branch 'develop' of github.com:RocketChat/Rocket.Chat into feat…
tassoevan Jan 13, 2021
461663f
Chain record list mutations
tassoevan Jan 13, 2021
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 .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,9 @@
"indent": "off",
"no-extra-parens": "off",
"no-spaced-func": "off",
"no-unused-vars": "off",
"no-useless-constructor": "off",
"no-use-before-define": "off",
"react/jsx-uses-react": "error",
"react/jsx-uses-vars": "error",
"react/jsx-no-undef": "error",
Expand All @@ -99,6 +101,10 @@
"SwitchCase": 1
}
],
"@typescript-eslint/interface-name-prefix": [
"error",
"always"
],
"@typescript-eslint/no-extra-parens": [
"error",
"all",
Expand All @@ -111,10 +117,9 @@
}
],
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/interface-name-prefix": [
"error",
"always"
]
"@typescript-eslint/no-unused-vars": ["error", {
"argsIgnorePattern": "^_"
}]
},
"env": {
"browser": true,
Expand Down
1 change: 1 addition & 0 deletions app/ui-utils/client/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Meteor } from 'meteor/meteor';

const url = new URL(window.location);
const keys = new Set();

export const getConfig = (key) => {
keys.add(key);
return url.searchParams.get(key) || Meteor._localStorage.getItem(`rc-config-${ key }`);
Expand Down
17 changes: 6 additions & 11 deletions client/contexts/ServerContext.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
import { createContext, useCallback, useContext, useMemo } from 'react';

interface IServerStream {
on(eventName: string, callback: (data: any) => void): void;
off(eventName: string, callback: (data: any) => void): void;
}

type ServerContextValue = {
info: {};
absoluteUrl: (path: string) => string;
callMethod: (methodName: string, ...args: any[]) => Promise<any>;
callEndpoint: (httpMethod: 'GET' | 'POST' | 'DELETE', endpoint: string, ...args: any[]) => Promise<any>;
uploadToEndpoint: (endpoint: string, params: any, formData: any) => Promise<void>;
getStream: (streamName: string, options?: {}) => IServerStream;
getStream: (streamName: string, options?: {}) => <T>(eventName: string, callback: (data: T) => void) => () => void;
};

export const ServerContext = createContext<ServerContextValue>({
Expand All @@ -20,10 +15,7 @@ export const ServerContext = createContext<ServerContextValue>({
callMethod: async () => undefined,
callEndpoint: async () => undefined,
uploadToEndpoint: async () => undefined,
getStream: () => ({
on: (): void => undefined,
off: (): void => undefined,
}),
getStream: () => () => (): void => undefined,
});

export const useServerInformation = (): {} => useContext(ServerContext).info;
Expand All @@ -45,7 +37,10 @@ export const useUpload = (endpoint: string): (params: any, formData: any) => Pro
return useCallback((params, formData: any) => uploadToEndpoint(endpoint, params, formData), [endpoint, uploadToEndpoint]);
};

export const useStream = (streamName: string, options?: {}): IServerStream => {
export const useStream = (
streamName: string,
options?: {},
): <T>(eventName: string, callback: (data: T) => void) => (() => void) => {
const { getStream } = useContext(ServerContext);
return useMemo(() => getStream(streamName, options), [getStream, streamName, options]);
};
65 changes: 65 additions & 0 deletions client/hooks/lists/useRecordList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { useEffect, useState } from 'react';

import { AsyncStatePhase } from '../../lib/asyncState';
import { RecordList } from '../../lib/lists/RecordList';
import { IRocketChatRecord } from '../../../definition/IRocketChatRecord';

type RecordListValue<T> = {
phase: AsyncStatePhase;
items: T[];
itemCount: number;
error: Error | undefined;
}

export const useRecordList = <T extends IRocketChatRecord>(
recordList: RecordList<T>,
): RecordListValue<T> => {
const [state, setState] = useState<RecordListValue<T>>(() => ({
phase: recordList.phase,
items: recordList.items,
itemCount: recordList.itemCount,
error: undefined,
}));

useEffect(() => {
const disconnectMutatingEvent = recordList.on('mutating', () => {
setState(() => ({
phase: recordList.phase,
items: recordList.items,
itemCount: recordList.itemCount,
error: undefined,
}));
});

const disconnectMutatedEvent = recordList.on('mutated', () => {
setState((prevState) => ({
phase: recordList.phase,
items: recordList.items,
itemCount: recordList.itemCount,
error: prevState.error,
}));
});

const disconnectClearedEvent = recordList.on('cleared', () => {
setState(() => ({
phase: recordList.phase,
items: recordList.items,
itemCount: recordList.itemCount,
error: undefined,
}));
});

const disconnectErroredEvent = recordList.on('errored', (error) => {
setState((state) => ({ ...state, error }));
});

return (): void => {
disconnectMutatingEvent();
disconnectMutatedEvent();
disconnectClearedEvent();
disconnectErroredEvent();
};
}, [recordList]);

return state;
};
30 changes: 30 additions & 0 deletions client/hooks/lists/useScrollableMessageList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { useCallback } from 'react';

import { IMessage } from '../../../definition/IMessage';
import { MessageList } from '../../lib/lists/MessageList';
import { ObjectFromApi } from '../../../definition/ObjectFromApi';
import { useScrollableRecordList } from './useScrollableRecordList';
import { RecordListBatchChanges } from '../../lib/lists/RecordList';

const convertMessageFromApi = (apiMessage: ObjectFromApi<IMessage>): IMessage => ({
...apiMessage,
_updatedAt: new Date(apiMessage._updatedAt),
ts: new Date(apiMessage.ts),
...apiMessage.tlm && { tlm: new Date(apiMessage.tlm) },
});

export const useScrollableMessageList = (
messageList: MessageList,
fetchMessages: (start: number, end: number) => Promise<RecordListBatchChanges<ObjectFromApi<IMessage>>>,
initialItemCount?: number,
): ReturnType<typeof useScrollableRecordList> => {
const fetchItems = useCallback(async (start: number, end: number): Promise<RecordListBatchChanges<IMessage>> => {
const batchChanges = await fetchMessages(start, end);
return {
...batchChanges.items && { items: batchChanges.items.map(convertMessageFromApi) },
...batchChanges.itemCount && { itemCount: batchChanges.itemCount },
};
}, [fetchMessages]);

return useScrollableRecordList(messageList, fetchItems, initialItemCount);
};
28 changes: 28 additions & 0 deletions client/hooks/lists/useScrollableRecordList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { useCallback, useEffect } from 'react';

import { RecordList, RecordListBatchChanges } from '../../lib/lists/RecordList';
import { IRocketChatRecord } from '../../../definition/IRocketChatRecord';

const INITIAL_ITEM_COUNT = 25;

export const useScrollableRecordList = <T extends IRocketChatRecord>(
recordList: RecordList<T>,
fetchBatchChanges: (start: number, end: number) => Promise<RecordListBatchChanges<T>>,
initialItemCount: number = INITIAL_ITEM_COUNT,
): {
loadMoreItems: (start: number, end: number) => void;
initialItemCount: number;
} => {
const loadMoreItems = useCallback(
(start: number, end: number) => {
recordList.batchHandle(() => fetchBatchChanges(start, end));
},
[recordList, fetchBatchChanges],
);

useEffect(() => {
loadMoreItems(0, initialItemCount ?? INITIAL_ITEM_COUNT);
}, [loadMoreItems, initialItemCount]);

return { loadMoreItems, initialItemCount };
};
80 changes: 80 additions & 0 deletions client/hooks/lists/useStreamUpdatesForMessageList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { useEffect } from 'react';

import { useStream } from '../../contexts/ServerContext';
import { IMessage } from '../../../definition/IMessage';
import {
createFilterFromQuery,
FieldExpression,
Query,
} from '../../lib/minimongo';
import { MessageList } from '../../lib/lists/MessageList';
import { IRoom } from '../../../definition/IRoom';
import { IUser } from '../../../definition/IUser';

type RoomMessagesRidEvent = IMessage;

type NotifyRoomRidDeleteMessageEvent = { _id: IMessage['_id'] };

type NotifyRoomRidDeleteMessageBulkEvent = {
rid: IMessage['rid'];
excludePinned: boolean;
ignoreDiscussion: boolean;
ts: FieldExpression<Date>;
users: string[];
};

const createDeleteCriteria = (
params: NotifyRoomRidDeleteMessageBulkEvent,
): ((message: IMessage) => boolean) => {
const query: Query<IMessage> = { ts: params.ts };

if (params.excludePinned) {
query.pinned = { $ne: true };
}

if (params.ignoreDiscussion) {
query.drid = { $exists: false };
}
if (params.users && params.users.length) {
query['u.username'] = { $in: params.users };
}

return createFilterFromQuery<IMessage>(query);
};

export const useStreamUpdatesForMessageList = (messageList: MessageList, uid: IUser['_id'] | null, rid: IRoom['_id'] | null): void => {
const subscribeToRoomMessages = useStream('room-messages');
const subscribeToNotifyRoom = useStream('notify-room');

useEffect(() => {
if (!uid || !rid) {
messageList.clear();
return;
}

const unsubscribeFromRoomMessages = subscribeToRoomMessages<RoomMessagesRidEvent>(rid, (message) => {
messageList.handle(message);
});

const unsubscribeFromDeleteMessage = subscribeToNotifyRoom<NotifyRoomRidDeleteMessageEvent>(
`${ rid }/deleteMessage`,
({ _id: mid }) => {
messageList.remove(mid);
},
);

const unsubscribeFromDeleteMessageBulk = subscribeToNotifyRoom<NotifyRoomRidDeleteMessageBulkEvent>(
`${ rid }/deleteMessageBulk`,
(params) => {
const matchDeleteCriteria = createDeleteCriteria(params);
messageList.prune(matchDeleteCriteria);
},
);

return (): void => {
unsubscribeFromRoomMessages();
unsubscribeFromDeleteMessage();
unsubscribeFromDeleteMessageBulk();
};
}, [subscribeToRoomMessages, subscribeToNotifyRoom, uid, rid, messageList]);
};
Loading