diff --git a/ts/components/Avatar.tsx b/ts/components/Avatar.tsx index 6adb05d16..4c85c5e65 100644 --- a/ts/components/Avatar.tsx +++ b/ts/components/Avatar.tsx @@ -1,9 +1,9 @@ import React from 'react'; import classNames from 'classnames'; -import { JazzIcon } from './JazzIcon'; import { getInitials } from '../util/getInitials'; import { LocalizerType } from '../types/Util'; +import { AvatarPlaceHolder } from './AvatarPlaceHolder'; interface Props { avatarPath?: string; @@ -48,30 +48,25 @@ export class Avatar extends React.PureComponent { } public renderIdenticon() { - const { phoneNumber, borderColor, borderWidth, size } = this.props; + const { phoneNumber, size, name, profileName } = this.props; if (!phoneNumber) { - return this.renderNoImage(); + throw new Error('Empty phoneNumber for identifcon'); } - const borderStyle = this.getBorderStyle(borderColor, borderWidth); - - // Generate the seed - const hash = phoneNumber.substring(0, 12); - const seed = parseInt(hash, 16) || 1234; - - return ; + const userName = profileName || name; + return ( + + ); } public renderImage() { - const { - avatarPath, - name, - phoneNumber, - profileName, - borderColor, - borderWidth, - } = this.props; + const { avatarPath, name, phoneNumber, profileName } = this.props; const { imageBroken } = this.state; if (!avatarPath || imageBroken) { @@ -82,11 +77,8 @@ export class Avatar extends React.PureComponent { !name && profileName ? ` ~${profileName}` : '' }`; - const borderStyle = this.getBorderStyle(borderColor, borderWidth); - return ( {window.i18n('contactAvatarAlt', { } public renderNoImage() { - const { - conversationType, - name, - noteToSelf, - size, - borderColor, - borderWidth, - } = this.props; + const { conversationType, name, noteToSelf, size } = this.props; const initials = getInitials(name); const isGroup = conversationType === 'group'; @@ -119,8 +104,6 @@ export class Avatar extends React.PureComponent { ); } - const borderStyle = this.getBorderStyle(borderColor, borderWidth); - if (!isGroup && initials) { return (
{ 'module-avatar__label', `module-avatar__label--${size}` )} - style={borderStyle} > {initials}
@@ -142,24 +124,17 @@ export class Avatar extends React.PureComponent { `module-avatar__icon--${conversationType}`, `module-avatar__icon--${size}` )} - style={borderStyle} /> ); } public render() { - const { - avatarPath, - color, - size, - noteToSelf, - conversationType, - } = this.props; + const { avatarPath, color, size, conversationType } = this.props; const { imageBroken } = this.state; // If it's a direct conversation then we must have an identicon const hasAvatar = avatarPath || conversationType === 'direct'; - const hasImage = !noteToSelf && hasAvatar && !imageBroken; + const hasImage = hasAvatar && !imageBroken; if ( size !== 28 && @@ -207,17 +182,9 @@ export class Avatar extends React.PureComponent { : this.renderIdenticon(); } - private getBorderStyle(_color?: string, _width?: number) { - //const borderWidth = typeof width === 'number' ? width : 3; - - // no border at all for now - return undefined; - /* return color - ? { - borderColor: color, - borderStyle: 'solid', - borderWidth: borderWidth, - } - : undefined; */ + private getAvatarColors(): Array { + // const theme = window.Events.getThemedSettings(); + // defined in session-android as `profile_picture_placeholder_colors` + return ['#5ff8b0', '#26cdb9', '#f3c615', '#fcac5a']; } } diff --git a/ts/components/AvatarPlaceHolder/AvatarPlaceHolder.tsx b/ts/components/AvatarPlaceHolder/AvatarPlaceHolder.tsx new file mode 100644 index 000000000..da9929a43 --- /dev/null +++ b/ts/components/AvatarPlaceHolder/AvatarPlaceHolder.tsx @@ -0,0 +1,101 @@ +import React from 'react'; +import { getInitials } from '../../util/getInitials'; + +interface Props { + diameter: number; + phoneNumber: string; + colors: Array; + name?: string; +} + +interface State { + sha512Seed?: string; +} + +export class AvatarPlaceHolder extends React.PureComponent { + public constructor(props: Props) { + super(props); + + this.state = { + sha512Seed: undefined, + }; + } + + public componentDidMount() { + void this.sha512(this.props.phoneNumber).then((sha512Seed: string) => { + this.setState({ sha512Seed }); + }); + } + + public componentDidUpdate(prevProps: Props, prevState: State) { + if (this.props.phoneNumber === prevProps.phoneNumber) { + return; + } + void this.sha512(this.props.phoneNumber).then((sha512Seed: string) => { + this.setState({ sha512Seed }); + }); + } + + public render() { + if (!this.state.sha512Seed) { + return <>; + } + + const { colors, diameter, phoneNumber, name } = this.props; + const r = diameter / 2; + const initial = + getInitials(name)?.toLocaleUpperCase() || + getInitials(phoneNumber)?.toLocaleUpperCase() || + '0'; + const viewBox = `0 0 ${diameter} ${diameter}`; + const fontSize = diameter * 0.5; + + // Generate the seed simulate the .hashCode as Java + const hash = parseInt(this.state.sha512Seed.substring(0, 12), 16) || 0; + + const bgColorIndex = hash % colors.length; + + const bgColor = colors[bgColorIndex]; + + return ( + + + + + {initial} + + + + ); + } + + private async sha512(str: string) { + // tslint:disable-next-line: await-promise + const buf = await crypto.subtle.digest( + 'SHA-512', + new TextEncoder().encode(str) + ); + + // tslint:disable: prefer-template restrict-plus-operands + return Array.prototype.map + .call(new Uint8Array(buf), (x: any) => ('00' + x.toString(16)).slice(-2)) + .join(''); + } +} diff --git a/ts/components/AvatarPlaceHolder/index.ts b/ts/components/AvatarPlaceHolder/index.ts new file mode 100644 index 000000000..5b9cfd23a --- /dev/null +++ b/ts/components/AvatarPlaceHolder/index.ts @@ -0,0 +1 @@ +export { AvatarPlaceHolder } from './AvatarPlaceHolder'; diff --git a/ts/components/JazzIcon/JazzIcon.tsx b/ts/components/JazzIcon/JazzIcon.tsx deleted file mode 100644 index 3cc17044b..000000000 --- a/ts/components/JazzIcon/JazzIcon.tsx +++ /dev/null @@ -1,166 +0,0 @@ -// Modified from https://github.com/redlanta/react-jazzicon - -import React from 'react'; -import Color from 'color'; -import { Paper } from './Paper'; -import { RNG } from './RNG'; - -const defaultColors = [ - '#01888c', // teal - '#fc7500', // bright orange - '#034f5d', // dark teal - '#E784BA', // light pink - '#81C8B6', // bright green - '#c7144c', // raspberry - '#f3c100', // goldenrod - '#1598f2', // lightning blue - '#2465e1', // sail blue - '#f19e02', // gold -]; - -const isColor = (str: string) => /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(str); -const isColors = (arr: Array) => { - if (!Array.isArray(arr)) { - return false; - } - - if (arr.every(value => typeof value === 'string' && isColor(value))) { - return true; - } - - return false; -}; - -interface Props { - diameter: number; - seed: number; - paperStyles?: Object; - svgStyles?: Object; - shapeCount?: number; - wobble?: number; - colors?: Array; -} - -// tslint:disable-next-line no-http-string -const svgns = 'http://www.w3.org/2000/svg'; -const shapeCount = 4; -const wobble = 30; - -export class JazzIcon extends React.PureComponent { - public render() { - const { - colors: customColors, - diameter, - paperStyles, - seed, - svgStyles, - } = this.props; - - const generator = new RNG(seed); - - const colors = customColors || defaultColors; - - const newColours = this.hueShift( - this.colorsForIcon(colors).slice(), - generator - ); - const shapesArr = Array(shapeCount).fill(null); - const shuffledColours = this.shuffleArray(newColours, generator); - - return ( - - - {shapesArr.map((_, i) => - this.genShape( - shuffledColours[i + 1], - diameter, - i, - shapeCount - 1, - generator - ) - )} - - - ); - } - - private hueShift(colors: Array, generator: RNG) { - const amount = generator.random() * 30 - wobble / 2; - - return colors.map(hex => - Color(hex) - .rotate(amount) - .hex() - ); - } - - private genShape( - colour: string, - diameter: number, - i: number, - total: number, - generator: RNG - ) { - const center = diameter / 2; - const firstRot = generator.random(); - const angle = Math.PI * 2 * firstRot; - const velocity = - (diameter / total) * generator.random() + (i * diameter) / total; - const tx = Math.cos(angle) * velocity; - const ty = Math.sin(angle) * velocity; - const translate = `translate(${tx} ${ty})`; - - // Third random is a shape rotation on top of all of that. - const secondRot = generator.random(); - const rot = firstRot * 360 + secondRot * 180; - const rotate = `rotate(${rot.toFixed(1)} ${center} ${center})`; - const transform = `${translate} ${rotate}`; - - return ( - - ); - } - - private colorsForIcon(arr: Array) { - if (isColors(arr)) { - return arr; - } - - return defaultColors; - } - - private shuffleArray(array: Array, generator: RNG) { - let currentIndex = array.length; - const newArray = [...array]; - - // While there remain elements to shuffle... - while (currentIndex > 0) { - // Pick a remaining element... - const randomIndex = generator.next() % currentIndex; - currentIndex -= 1; - // And swap it with the current element. - const temporaryValue = newArray[currentIndex]; - newArray[currentIndex] = newArray[randomIndex]; - newArray[randomIndex] = temporaryValue; - } - - return newArray; - } -} diff --git a/ts/components/JazzIcon/Paper.tsx b/ts/components/JazzIcon/Paper.tsx deleted file mode 100644 index ee3791637..000000000 --- a/ts/components/JazzIcon/Paper.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import React from 'react'; - -const styles = { - borderRadius: '50%', - display: 'inline-block', - margin: 0, - overflow: 'hidden', - padding: 0, -}; - -// @ts-ignore -export const Paper = ({ children, color, diameter, style: styleOverrides }) => ( -
- {children} -
-); diff --git a/ts/components/JazzIcon/RNG.tsx b/ts/components/JazzIcon/RNG.tsx deleted file mode 100644 index b6c18ac6b..000000000 --- a/ts/components/JazzIcon/RNG.tsx +++ /dev/null @@ -1,21 +0,0 @@ -export class RNG { - private _seed: number; - constructor(seed: number) { - this._seed = seed % 2147483647; - if (this._seed <= 0) { - this._seed += 2147483646; - } - } - - public next() { - return (this._seed = (this._seed * 16807) % 2147483647); - } - - public nextFloat() { - return (this.next() - 1) / 2147483646; - } - - public random() { - return this.nextFloat(); - } -} diff --git a/ts/components/JazzIcon/index.tsx b/ts/components/JazzIcon/index.tsx deleted file mode 100644 index 204774dc6..000000000 --- a/ts/components/JazzIcon/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -import { JazzIcon } from './JazzIcon'; -export { JazzIcon };