Show 'accept invite UI' for re-invite, calm progress spinner

This commit is contained in:
Scott Nonnenberg 2020-10-26 07:39:45 -07:00
parent 5c0fcad6b1
commit fa2d300714
8 changed files with 40 additions and 35 deletions

View file

@ -692,12 +692,6 @@ export class ConversationController {
await Promise.all( await Promise.all(
this._conversations.map(async conversation => { this._conversations.map(async conversation => {
try { try {
// This call is important to allow Conversation models not to generate their
// cached props on initial construction if we're in the middle of the load
// from the database. Then we come back to the models when it is safe and
// generate those props.
conversation.generateProps();
if (!conversation.get('lastMessage')) { if (!conversation.get('lastMessage')) {
await conversation.updateLastMessage(); await conversation.updateLastMessage();
} }

View file

@ -749,12 +749,16 @@ type WhatIsThis = typeof window.WhatIsThis;
conversationRemoved(id); conversationRemoved(id);
}); });
convoCollection.on('add', conversation => { convoCollection.on('add', conversation => {
const { id, cachedProps } = conversation || {}; if (!conversation) {
conversationAdded(id, cachedProps); return;
}
conversationAdded(conversation.id, conversation.format());
}); });
convoCollection.on('change', conversation => { convoCollection.on('change', conversation => {
const { id, cachedProps } = conversation || {}; if (!conversation) {
conversationChanged(id, cachedProps); return;
}
conversationChanged(conversation.id, conversation.format());
}); });
convoCollection.on('reset', removeAllConversations); convoCollection.on('reset', removeAllConversations);

View file

@ -23,6 +23,7 @@ import { EmojiPickDataType } from './emoji/EmojiPicker';
export type OwnProps = { export type OwnProps = {
readonly i18n: LocalizerType; readonly i18n: LocalizerType;
readonly areWePending?: boolean;
readonly groupVersion?: 1 | 2; readonly groupVersion?: 1 | 2;
readonly isMissingMandatoryProfileSharing?: boolean; readonly isMissingMandatoryProfileSharing?: boolean;
readonly messageRequestsEnabled?: boolean; readonly messageRequestsEnabled?: boolean;
@ -115,6 +116,7 @@ export const CompositionArea = ({
clearShowPickerHint, clearShowPickerHint,
// Message Requests // Message Requests
acceptedMessageRequest, acceptedMessageRequest,
areWePending,
conversationType, conversationType,
groupVersion, groupVersion,
isBlocked, isBlocked,
@ -331,7 +333,10 @@ export const CompositionArea = ({
}; };
}, [setLarge]); }, [setLarge]);
if (messageRequestsEnabled && (!acceptedMessageRequest || isBlocked)) { if (
messageRequestsEnabled &&
(!acceptedMessageRequest || isBlocked || areWePending)
) {
return ( return (
<MessageRequestActions <MessageRequestActions
i18n={i18n} i18n={i18n}

View file

@ -89,11 +89,6 @@ export class ConversationModel extends window.Backbone.Model<
debouncedUpdateLastMessage?: () => void; debouncedUpdateLastMessage?: () => void;
// backbone ensures this exists in initialize()
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
generateProps: () => void;
// backbone ensures this exists // backbone ensures this exists
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore
@ -237,19 +232,11 @@ export class ConversationModel extends window.Backbone.Model<
this.typingRefreshTimer = null; this.typingRefreshTimer = null;
this.typingPauseTimer = null; this.typingPauseTimer = null;
// Keep props ready // We clear our cached props whenever we change so that the next call to format() will
this.generateProps = () => { // result in refresh via a getProps() call. See format() below.
// This is to prevent race conditions on startup; Conversation models are created this.on('change', () => {
// but the full window.ConversationController.load() sequence isn't complete. this.cachedProps = null;
if (!window.ConversationController.isFetchComplete()) { });
return;
}
this.cachedProps = this.getProps();
};
this.on('change', this.generateProps);
this.generateProps();
} }
isMe(): boolean { isMe(): boolean {
@ -282,9 +269,7 @@ export class ConversationModel extends window.Backbone.Model<
isMemberPending(conversationId: string): boolean { isMemberPending(conversationId: string): boolean {
if (!this.isGroupV2()) { if (!this.isGroupV2()) {
throw new Error( return false;
`isPendingMember: Called for non-GroupV2 conversation ${this.idForLogging()}`
);
} }
const pendingMembersV2 = this.get('pendingMembersV2'); const pendingMembersV2 = this.get('pendingMembersV2');
@ -1064,6 +1049,7 @@ export class ConversationModel extends window.Backbone.Model<
const messageRequestsEnabled = window.Signal.RemoteConfig.isEnabled( const messageRequestsEnabled = window.Signal.RemoteConfig.isEnabled(
'desktop.messageRequests' 'desktop.messageRequests'
); );
const ourConversationId = window.ConversationController.getOurConversationId();
let groupVersion: undefined | 1 | 2; let groupVersion: undefined | 1 | 2;
if (this.isGroupV1()) { if (this.isGroupV1()) {
@ -1081,6 +1067,9 @@ export class ConversationModel extends window.Backbone.Model<
acceptedMessageRequest: this.getAccepted(), acceptedMessageRequest: this.getAccepted(),
activeAt: this.get('active_at')!, activeAt: this.get('active_at')!,
areWePending: Boolean(
ourConversationId && this.isMemberPending(ourConversationId)
),
avatarPath: this.getAvatarPath()!, avatarPath: this.getAvatarPath()!,
color, color,
draftPreview, draftPreview,

View file

@ -42,6 +42,7 @@ export type ConversationType = {
firstName?: string; firstName?: string;
profileName?: string; profileName?: string;
avatarPath?: string; avatarPath?: string;
areWePending?: boolean;
color?: ColorType; color?: ColorType;
isArchived?: boolean; isArchived?: boolean;
isBlocked?: boolean; isBlocked?: boolean;

View file

@ -19,6 +19,7 @@ import { makeLookup } from './makeLookup';
import { migrateColor } from './migrateColor'; import { migrateColor } from './migrateColor';
import { missingCaseError } from './missingCaseError'; import { missingCaseError } from './missingCaseError';
import { parseRemoteClientExpiration } from './parseRemoteClientExpiration'; import { parseRemoteClientExpiration } from './parseRemoteClientExpiration';
import { sleep } from './sleep';
import * as zkgroup from './zkgroup'; import * as zkgroup from './zkgroup';
export { export {
@ -41,5 +42,6 @@ export {
missingCaseError, missingCaseError,
parseRemoteClientExpiration, parseRemoteClientExpiration,
Registration, Registration,
sleep,
zkgroup, zkgroup,
}; };

View file

@ -13129,7 +13129,7 @@
"rule": "DOM-innerHTML", "rule": "DOM-innerHTML",
"path": "ts/components/CompositionArea.tsx", "path": "ts/components/CompositionArea.tsx",
"line": " el.innerHTML = '';", "line": " el.innerHTML = '';",
"lineNumber": 81, "lineNumber": 82,
"reasonCategory": "usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2020-06-03T19:23:21.195Z", "updated": "2020-06-03T19:23:21.195Z",
"reasonDetail": "Our code, no user input, only clearing out the dom" "reasonDetail": "Our code, no user input, only clearing out the dom"
@ -13548,4 +13548,4 @@
"reasonCategory": "falseMatch", "reasonCategory": "falseMatch",
"updated": "2020-09-08T23:07:22.682Z" "updated": "2020-09-08T23:07:22.682Z"
} }
] ]

View file

@ -660,8 +660,10 @@ Whisper.ConversationView = Whisper.View.extend({
}): Promise<void> { }): Promise<void> {
const idLog = `${name}/${this.model.idForLogging()}`; const idLog = `${name}/${this.model.idForLogging()}`;
const ONE_SECOND = 1000; const ONE_SECOND = 1000;
const TWO_SECONDS = 2000;
let progressView: any | undefined; let progressView: any | undefined;
let spinnerStart;
let progressTimeout: NodeJS.Timeout | undefined = setTimeout(() => { let progressTimeout: NodeJS.Timeout | undefined = setTimeout(() => {
window.log.info(`longRunningTaskWrapper/${idLog}: Creating spinner`); window.log.info(`longRunningTaskWrapper/${idLog}: Creating spinner`);
@ -671,7 +673,8 @@ Whisper.ConversationView = Whisper.View.extend({
className: 'progress-modal-wrapper', className: 'progress-modal-wrapper',
Component: window.Signal.Components.ProgressModal, Component: window.Signal.Components.ProgressModal,
}); });
}, ONE_SECOND); spinnerStart = Date.now();
}, TWO_SECONDS);
// Note: any task we put here needs to have its own safety valve; this function will // Note: any task we put here needs to have its own safety valve; this function will
// show a spinner until it's done // show a spinner until it's done
@ -687,6 +690,13 @@ Whisper.ConversationView = Whisper.View.extend({
progressTimeout = undefined; progressTimeout = undefined;
} }
if (progressView) { if (progressView) {
const now = Date.now();
if (spinnerStart && now - spinnerStart < ONE_SECOND) {
window.log.info(
`longRunningTaskWrapper/${idLog}: Spinner shown for less than second, showing for another second`
);
await window.Signal.Util.sleep(ONE_SECOND);
}
progressView.remove(); progressView.remove();
progressView = undefined; progressView = undefined;
} }