Move left pane entirely to React

This commit is contained in:
Scott Nonnenberg
2019-01-14 13:49:58 -08:00
parent bf904ddd12
commit b3ac1373fa
142 changed files with 5016 additions and 3428 deletions

View File

@@ -6,9 +6,250 @@ import * as MIME from './MIME';
import { arrayBufferToObjectURL } from '../util/arrayBufferToObjectURL';
import { saveURLAsFile } from '../util/saveURLAsFile';
import { SignalService } from '../protobuf';
import {
isImageTypeSupported,
isVideoTypeSupported,
} from '../util/GoogleChrome';
import { LocalizerType } from './Util';
const MAX_WIDTH = 300;
const MAX_HEIGHT = MAX_WIDTH * 1.5;
const MIN_WIDTH = 200;
const MIN_HEIGHT = 50;
// Used for display
export interface AttachmentType {
caption?: string;
contentType: MIME.MIMEType;
fileName: string;
/** Not included in protobuf, needs to be pulled from flags */
isVoiceMessage?: boolean;
/** For messages not already on disk, this will be a data url */
url: string;
size?: number;
fileSize?: string;
pending?: boolean;
width?: number;
height?: number;
screenshot?: {
height: number;
width: number;
url: string;
contentType: MIME.MIMEType;
};
thumbnail?: {
height: number;
width: number;
url: string;
contentType: MIME.MIMEType;
};
}
// UI-focused functions
export function getExtensionForDisplay({
fileName,
contentType,
}: {
fileName: string;
contentType: MIME.MIMEType;
}): string | undefined {
if (fileName && fileName.indexOf('.') >= 0) {
const lastPeriod = fileName.lastIndexOf('.');
const extension = fileName.slice(lastPeriod + 1);
if (extension.length) {
return extension;
}
}
if (!contentType) {
return;
}
const slash = contentType.indexOf('/');
if (slash >= 0) {
return contentType.slice(slash + 1);
}
return;
}
export function isAudio(attachments?: Array<AttachmentType>) {
return (
attachments &&
attachments[0] &&
attachments[0].contentType &&
MIME.isAudio(attachments[0].contentType)
);
}
export function canDisplayImage(attachments?: Array<AttachmentType>) {
const { height, width } =
attachments && attachments[0] ? attachments[0] : { height: 0, width: 0 };
return (
height &&
height > 0 &&
height <= 4096 &&
width &&
width > 0 &&
width <= 4096
);
}
export function getThumbnailUrl(attachment: AttachmentType) {
if (attachment.thumbnail) {
return attachment.thumbnail.url;
}
return getUrl(attachment);
}
export function getUrl(attachment: AttachmentType) {
if (attachment.screenshot) {
return attachment.screenshot.url;
}
return attachment.url;
}
export function isImage(attachments?: Array<AttachmentType>) {
return (
attachments &&
attachments[0] &&
attachments[0].contentType &&
isImageTypeSupported(attachments[0].contentType)
);
}
export function isImageAttachment(attachment: AttachmentType) {
return (
attachment &&
attachment.contentType &&
isImageTypeSupported(attachment.contentType)
);
}
export function hasImage(attachments?: Array<AttachmentType>) {
return (
attachments &&
attachments[0] &&
(attachments[0].url || attachments[0].pending)
);
}
export function isVideo(attachments?: Array<AttachmentType>) {
return attachments && isVideoAttachment(attachments[0]);
}
export function isVideoAttachment(attachment?: AttachmentType) {
return (
attachment &&
attachment.contentType &&
isVideoTypeSupported(attachment.contentType)
);
}
export function hasVideoScreenshot(attachments?: Array<AttachmentType>) {
const firstAttachment = attachments ? attachments[0] : null;
return (
firstAttachment &&
firstAttachment.screenshot &&
firstAttachment.screenshot.url
);
}
type DimensionsType = {
height: number;
width: number;
};
export function getImageDimensions(attachment: AttachmentType): DimensionsType {
const { height, width } = attachment;
if (!height || !width) {
return {
height: MIN_HEIGHT,
width: MIN_WIDTH,
};
}
const aspectRatio = height / width;
const targetWidth = Math.max(Math.min(MAX_WIDTH, width), MIN_WIDTH);
const candidateHeight = Math.round(targetWidth * aspectRatio);
return {
width: targetWidth,
height: Math.max(Math.min(MAX_HEIGHT, candidateHeight), MIN_HEIGHT),
};
}
export function areAllAttachmentsVisual(
attachments?: Array<AttachmentType>
): boolean {
if (!attachments) {
return false;
}
const max = attachments.length;
for (let i = 0; i < max; i += 1) {
const attachment = attachments[i];
if (!isImageAttachment(attachment) && !isVideoAttachment(attachment)) {
return false;
}
}
return true;
}
export function getGridDimensions(
attachments?: Array<AttachmentType>
): null | DimensionsType {
if (!attachments || !attachments.length) {
return null;
}
if (!isImage(attachments) && !isVideo(attachments)) {
return null;
}
if (attachments.length === 1) {
return getImageDimensions(attachments[0]);
}
if (attachments.length === 2) {
return {
height: 150,
width: 300,
};
}
if (attachments.length === 4) {
return {
height: 300,
width: 300,
};
}
return {
height: 200,
width: 300,
};
}
export function getAlt(
attachment: AttachmentType,
i18n: LocalizerType
): string {
return isVideoAttachment(attachment)
? i18n('videoAttachmentAlt')
: i18n('imageAttachmentAlt');
}
// Migration-related attachment stuff
export type Attachment = {
fileName?: string | null;
fileName?: string;
flags?: SignalService.AttachmentPointer.Flags;
contentType?: MIME.MIMEType;
size?: number;
@@ -72,7 +313,7 @@ export const isVoiceMessage = (attachment: Attachment): boolean => {
const isLegacyAndroidVoiceMessage =
!is.undefined(attachment.contentType) &&
MIME.isAudio(attachment.contentType) &&
attachment.fileName === null;
!attachment.fileName;
if (isLegacyAndroidVoiceMessage) {
return true;
}
@@ -131,9 +372,11 @@ export const getSuggestedFilename = ({
return `${prefix}${suffix}${indexSuffix}${extension}`;
};
export const getFileExtension = (attachment: Attachment): string | null => {
export const getFileExtension = (
attachment: Attachment
): string | undefined => {
if (!attachment.contentType) {
return null;
return;
}
switch (attachment.contentType) {

View File

@@ -120,13 +120,13 @@ export function contactSelector(
};
}
export function getName(contact: Contact): string | null {
export function getName(contact: Contact): string | undefined {
const { name, organization } = contact;
const displayName = (name && name.displayName) || null;
const givenName = (name && name.givenName) || null;
const familyName = (name && name.familyName) || null;
const displayName = (name && name.displayName) || undefined;
const givenName = (name && name.givenName) || undefined;
const familyName = (name && name.familyName) || undefined;
const backupName =
(givenName && familyName && `${givenName} ${familyName}`) || null;
(givenName && familyName && `${givenName} ${familyName}`) || undefined;
return displayName || organization || backupName || givenName || familyName;
}

View File

@@ -1,9 +1,9 @@
import { Message } from './Message';
interface ConversationLastMessageUpdate {
lastMessage: string | null;
lastMessageStatus: string | null;
timestamp: number | null;
lastMessage: string;
lastMessageStatus?: string;
timestamp?: number;
}
export const createLastMessageUpdate = ({
@@ -13,26 +13,26 @@ export const createLastMessageUpdate = ({
lastMessageStatus,
lastMessageNotificationText,
}: {
currentLastMessageText: string | null;
currentTimestamp: number | null;
lastMessage: Message | null;
lastMessageStatus: string | null;
lastMessageNotificationText: string | null;
currentLastMessageText?: string;
currentTimestamp?: number;
lastMessage?: Message;
lastMessageStatus?: string;
lastMessageNotificationText?: string;
}): ConversationLastMessageUpdate => {
if (lastMessage === null) {
if (!lastMessage) {
return {
lastMessage: '',
lastMessageStatus: null,
timestamp: null,
};
}
const { type, expirationTimerUpdate } = lastMessage;
const isVerifiedChangeMessage = type === 'verified-change';
const isExpireTimerUpdateFromSync =
expirationTimerUpdate && expirationTimerUpdate.fromSync;
const shouldUpdateTimestamp =
!isVerifiedChangeMessage && !isExpireTimerUpdateFromSync;
const isExpireTimerUpdateFromSync = Boolean(
expirationTimerUpdate && expirationTimerUpdate.fromSync
);
const shouldUpdateTimestamp = Boolean(
!isVerifiedChangeMessage && !isExpireTimerUpdateFromSync
);
const newTimestamp = shouldUpdateTimestamp
? lastMessage.sent_at
@@ -44,7 +44,7 @@ export const createLastMessageUpdate = ({
: currentLastMessageText;
return {
lastMessage: newLastMessageText,
lastMessage: newLastMessageText || '',
lastMessageStatus,
timestamp: newTimestamp,
};

View File

@@ -36,3 +36,21 @@ export function parse(
return phoneNumber;
}
export function normalize(
phoneNumber: string,
options: { regionCode: string }
): string | undefined {
const { regionCode } = options;
try {
const parsedNumber = instance.parse(phoneNumber, regionCode);
if (instance.isValidNumber(parsedNumber)) {
return instance.format(parsedNumber, PhoneNumberFormat.E164);
}
return;
} catch (error) {
return;
}
}

View File

@@ -1,13 +1,13 @@
export type RenderTextCallback = (
export type RenderTextCallbackType = (
options: {
text: string;
key: number;
}
) => JSX.Element | string;
export type Localizer = (key: string, values?: Array<string>) => string;
export type LocalizerType = (key: string, values?: Array<string>) => string;
export type Color =
export type ColorType =
| 'gray'
| 'blue'
| 'cyan'