Fix audio indicator svg glitch

This commit is contained in:
Fedor Indutny 2022-05-23 15:00:01 -07:00 committed by GitHub
parent 9f8ea5b202
commit 494287a570
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 172 additions and 311 deletions

View file

@ -5,55 +5,6 @@
Signal Desktop makes use of the following open source projects.
## @evanhahn/lottie-web-light
The MIT License (MIT)
Copyright (c) 2022 Evan Hahn
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
************
Original lottie-web license:
The MIT License (MIT)
Copyright (c) 2015 Bodymovin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
## @popperjs/core
License: MIT

View file

@ -77,7 +77,6 @@
"fs-xattr": "0.3.0"
},
"dependencies": {
"@evanhahn/lottie-web-light": "5.8.1",
"@popperjs/core": "2.9.2",
"@react-spring/web": "9.4.1",
"@signalapp/libsignal-client": "0.16.0",

View file

@ -18,15 +18,15 @@
}
&__content {
$size: 16px;
$size: 14px;
width: $size;
height: $size;
/* Center Lottie animation */
display: flex;
align-items: center;
justify-content: center;
&--muted {
$size: 14px;
width: $size;
height: $size;
@include color-svg(
'../images/icons/v2/mic-off-solid-28.svg',
$color-white

View file

@ -1,17 +1,53 @@
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import React, { useState, useEffect } from 'react';
import { storiesOf } from '@storybook/react';
import { boolean, select } from '@storybook/addon-knobs';
import { boolean } from '@storybook/addon-knobs';
import { CallingAudioIndicator } from './CallingAudioIndicator';
import { AUDIO_LEVEL_INTERVAL_MS } from '../calling/constants';
const story = storiesOf('Components/CallingAudioIndicator', module);
story.add('Default', () => (
<CallingAudioIndicator
hasAudio={boolean('hasAudio', true)}
audioLevel={select('audioLevel', [0, 0.25, 0.5, 0.75, 1], 0.5)}
/>
));
story.add('Extreme', () => {
const [audioLevel, setAudioLevel] = useState(1);
useEffect(() => {
const timer = setTimeout(() => {
setAudioLevel(1 - audioLevel);
}, 2 * AUDIO_LEVEL_INTERVAL_MS);
return () => {
clearTimeout(timer);
};
}, [audioLevel, setAudioLevel]);
return (
<CallingAudioIndicator
hasAudio={boolean('hasAudio', true)}
audioLevel={audioLevel}
/>
);
});
story.add('Random', () => {
const [audioLevel, setAudioLevel] = useState(1);
useEffect(() => {
const timer = setTimeout(() => {
setAudioLevel(Math.random());
}, AUDIO_LEVEL_INTERVAL_MS);
return () => {
clearTimeout(timer);
};
}, [audioLevel, setAudioLevel]);
return (
<CallingAudioIndicator
hasAudio={boolean('hasAudio', true)}
audioLevel={audioLevel}
/>
);
});

View file

@ -4,7 +4,7 @@
import classNames from 'classnames';
import { noop } from 'lodash';
import type { ReactElement } from 'react';
import React, { useEffect, useCallback, useState } from 'react';
import React, { useEffect, useState } from 'react';
import { useSpring, animated } from '@react-spring/web';
import { AUDIO_LEVEL_INTERVAL_MS } from '../calling/constants';
@ -20,11 +20,11 @@ const SIDE_SCALE_FACTOR = 0.75;
const MAX_CENTRAL_BAR_DELTA = 9;
/* Should match css */
const CONTENT_WIDTH = 16;
const CONTENT_HEIGHT = 16;
const CONTENT_WIDTH = 14;
const CONTENT_HEIGHT = 14;
const BAR_WIDTH = 2;
const CONTENT_PADDING = 2;
const CONTENT_PADDING = 1;
enum BarPosition {
Left,
@ -37,13 +37,15 @@ function generateBarPath(position: BarPosition, audioLevel: number): string {
if (position === BarPosition.Left) {
x = CONTENT_PADDING;
} else if (position === BarPosition.Center) {
x = CONTENT_WIDTH / 2 - CONTENT_PADDING + BAR_WIDTH / 2;
x = CONTENT_WIDTH / 2 - BAR_WIDTH / 2;
} else if (position === BarPosition.Right) {
x = CONTENT_WIDTH - CONTENT_PADDING - BAR_WIDTH;
} else {
throw missingCaseError(position);
}
x = Math.round(x);
let height: number;
if (position === BarPosition.Left || position === BarPosition.Right) {
height =
@ -58,37 +60,34 @@ function generateBarPath(position: BarPosition, audioLevel: number): string {
height -= 2;
const y = (CONTENT_HEIGHT - height) / 2;
const top = y;
const bottom = top + height;
const left = x;
const right = x + BAR_WIDTH;
const top = y.toFixed(2);
const bottom = (y + height).toFixed(2);
return (
`M ${x} ${top} ` +
`L ${x} ${bottom} ` +
`A 0.5 0.5 0 0 0 ${x + BAR_WIDTH} ${bottom} ` +
`L ${x + BAR_WIDTH} ${top} ` +
`A 0.5 0.5 0 0 0 ${x} ${top}`
`M ${left} ${top} ` +
`L ${left} ${bottom} ` +
`A 0.5 0.5 0 0 0 ${right} ${bottom} ` +
`L ${right} ${top} ` +
`A 0.5 0.5 0 0 0 ${left} ${top}`
);
}
function Bar({
position,
audioLevel,
}: {
position: BarPosition;
audioLevel: number;
}): ReactElement {
function generateCombinedPath(audioLevel: number): string {
return (
`${generateBarPath(BarPosition.Left, audioLevel)} ` +
`${generateBarPath(BarPosition.Center, audioLevel)} ` +
`${generateBarPath(BarPosition.Right, audioLevel)} `
);
}
function Bars({ audioLevel }: { audioLevel: number }): ReactElement {
const animatedProps = useSpring({
from: { audioLevel: 0 },
config: { duration: AUDIO_LEVEL_INTERVAL_MS },
});
const levelToPath = useCallback(
(animatedLevel: number): string => {
return generateBarPath(position, animatedLevel);
},
[position]
);
useEffect(() => {
animatedProps.audioLevel.stop();
animatedProps.audioLevel.start(audioLevel);
@ -96,7 +95,7 @@ function Bar({
return (
<animated.path
d={animatedProps.audioLevel.to(levelToPath)}
d={animatedProps.audioLevel.to(generateCombinedPath)}
fill="#ffffff"
/>
);
@ -148,10 +147,15 @@ export function CallingAudioIndicator({
`${BASE_CLASS_NAME}--with-content`
)}
>
<svg className={CONTENT_CLASS_NAME}>
<Bar position={BarPosition.Left} audioLevel={audioLevel} />
<Bar position={BarPosition.Center} audioLevel={audioLevel} />
<Bar position={BarPosition.Right} audioLevel={audioLevel} />
<svg
xmlns="http://www.w3.org/2000/svg"
className={CONTENT_CLASS_NAME}
viewBox={`0 0 ${CONTENT_WIDTH} ${CONTENT_HEIGHT}`}
width={CONTENT_WIDTH}
height={CONTENT_HEIGHT}
style={{ transform: 'translate3d(0px, 0px, 0px)' }}
>
<Bars audioLevel={audioLevel} />
</svg>
</div>
);

View file

@ -1,21 +0,0 @@
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import { storiesOf } from '@storybook/react';
import { Lottie } from './Lottie';
import testAnimationData from '../../fixtures/lottie-loader-by-lucas-bariani.json';
const STORYBOOK_CONTAINER_CLASS_NAME = 'lottie-test-storybook-container';
const story = storiesOf('Components/Lottie', module);
story.add('Default', () => (
<Lottie
animationData={testAnimationData}
className={STORYBOOK_CONTAINER_CLASS_NAME}
style={{ width: 300, height: 300 }}
/>
));

View file

@ -1,42 +0,0 @@
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { CSSProperties, ReactElement } from 'react';
import React, { useEffect, useRef } from 'react';
import lottie from '@evanhahn/lottie-web-light';
import { lottieNoopAudioFactory } from '../util/lottieNoopAudioFactory';
export function Lottie({
animationData,
className,
style,
}: Readonly<{
animationData: unknown;
className?: string;
style?: CSSProperties;
}>): ReactElement {
const containerRef = useRef<null | HTMLDivElement>(null);
useEffect(() => {
const container = containerRef.current;
if (!container) {
return;
}
const animationItem = lottie.loadAnimation({
container,
renderer: 'svg',
loop: true,
autoplay: true,
animationData,
audioFactory: lottieNoopAudioFactory,
});
return () => {
animationItem.destroy();
};
}, [animationData]);
return <div className={className} ref={containerRef} style={style} />;
}

View file

@ -132,41 +132,6 @@
"reasonCategory": "falseMatch",
"updated": "2021-04-05T20:48:36.065Z"
},
{
"rule": "jQuery-append(",
"path": "node_modules/@evanhahn/lottie-web-light/index.js",
"line": " this._elementHelper.append(img);",
"reasonCategory": "falseMatch",
"updated": "2022-01-27T20:06:59.988Z"
},
{
"rule": "jQuery-insertBefore(",
"path": "node_modules/@evanhahn/lottie-web-light/index.js",
"line": " this.layerElement.insertBefore(newElement, nextElement);",
"reasonCategory": "falseMatch",
"updated": "2022-01-27T20:06:59.988Z"
},
{
"rule": "jQuery-insertBefore(",
"path": "node_modules/@evanhahn/lottie-web-light/index.js",
"line": " parentNode.insertBefore(useElem, nextChild);",
"reasonCategory": "falseMatch",
"updated": "2022-01-27T20:06:59.988Z"
},
{
"rule": "jQuery-load(",
"path": "node_modules/@evanhahn/lottie-web-light/index.js",
"line": " _workerSelf.assetLoader.load(",
"reasonCategory": "falseMatch",
"updated": "2022-01-27T20:06:59.988Z"
},
{
"rule": "jQuery-load(",
"path": "node_modules/@evanhahn/lottie-web-light/index.js",
"line": " _workerSelf.assetLoader.load(",
"reasonCategory": "falseMatch",
"updated": "2022-01-27T20:06:59.988Z"
},
{
"rule": "jQuery-load(",
"path": "node_modules/@malept/flatpak-bundler/node_modules/debug/src/browser.js",
@ -652,6 +617,30 @@
"reasonCategory": "falseMatch",
"updated": "2022-02-11T21:58:24.827Z"
},
{
"rule": "jQuery-load(",
"path": "node_modules/agentkeepalive/node_modules/debug/src/browser.js",
"line": "function load() {",
"reasonCategory": "falseMatch",
"updated": "2022-04-08T07:10:10.677Z",
"reasonDetail": "node-gyp dependency"
},
{
"rule": "jQuery-load(",
"path": "node_modules/agentkeepalive/node_modules/debug/src/common.js",
"line": "\tcreateDebug.enable(createDebug.load());",
"reasonCategory": "falseMatch",
"updated": "2022-04-08T07:10:10.677Z",
"reasonDetail": "node-gyp dependency"
},
{
"rule": "jQuery-load(",
"path": "node_modules/agentkeepalive/node_modules/debug/src/node.js",
"line": "function load() {",
"reasonCategory": "falseMatch",
"updated": "2022-04-08T07:10:10.677Z",
"reasonDetail": "node-gyp dependency"
},
{
"rule": "jQuery-wrap(",
"path": "node_modules/asar/node_modules/commander/index.js",
@ -4470,6 +4459,30 @@
"reasonCategory": "falseMatch",
"updated": "2019-03-09T00:08:44.242Z"
},
{
"rule": "jQuery-load(",
"path": "node_modules/make-fetch-happen/node_modules/debug/src/browser.js",
"line": "function load() {",
"reasonCategory": "falseMatch",
"updated": "2022-04-08T07:10:10.677Z",
"reasonDetail": "node-gyp dependency"
},
{
"rule": "jQuery-load(",
"path": "node_modules/make-fetch-happen/node_modules/debug/src/common.js",
"line": "\tcreateDebug.enable(createDebug.load());",
"reasonCategory": "falseMatch",
"updated": "2022-04-08T07:10:10.677Z",
"reasonDetail": "node-gyp dependency"
},
{
"rule": "jQuery-load(",
"path": "node_modules/make-fetch-happen/node_modules/debug/src/node.js",
"line": "function load() {",
"reasonCategory": "falseMatch",
"updated": "2022-04-08T07:10:10.677Z",
"reasonDetail": "node-gyp dependency"
},
{
"rule": "eval",
"path": "node_modules/micro/node_modules/depd/index.js",
@ -4501,6 +4514,46 @@
"updated": "2020-09-11T17:24:56.124Z",
"reasonDetail": "Read, not write"
},
{
"rule": "jQuery-append(",
"path": "node_modules/minipass-fetch/lib/headers.js",
"line": " this.append(headerName, value)",
"reasonCategory": "falseMatch",
"updated": "2022-04-08T07:10:10.677Z",
"reasonDetail": "node-gyp dependency"
},
{
"rule": "jQuery-append(",
"path": "node_modules/minipass-fetch/lib/headers.js",
"line": " this.append(pair[0], pair[1])",
"reasonCategory": "falseMatch",
"updated": "2022-04-08T07:10:10.677Z",
"reasonDetail": "node-gyp dependency"
},
{
"rule": "jQuery-append(",
"path": "node_modules/minipass-fetch/lib/headers.js",
"line": " this.append(key, init[key])",
"reasonCategory": "falseMatch",
"updated": "2022-04-08T07:10:10.677Z",
"reasonDetail": "node-gyp dependency"
},
{
"rule": "jQuery-append(",
"path": "node_modules/minipass-fetch/lib/request.js",
"line": " headers.append('Content-Type', contentType)",
"reasonCategory": "falseMatch",
"updated": "2022-04-08T07:10:10.677Z",
"reasonDetail": "node-gyp dependency"
},
{
"rule": "jQuery-append(",
"path": "node_modules/minipass-fetch/lib/response.js",
"line": " headers.append('Content-Type', contentType)",
"reasonCategory": "falseMatch",
"updated": "2022-04-08T07:10:10.677Z",
"reasonDetail": "node-gyp dependency"
},
{
"rule": "jQuery-$(",
"path": "node_modules/moment/min/moment-with-locales.min.js",
@ -7564,14 +7617,6 @@
"reasonCategory": "usageTrusted",
"updated": "2021-10-11T21:21:08.188Z"
},
{
"rule": "React-useRef",
"path": "ts/components/Lottie.tsx",
"line": " const containerRef = useRef<null | HTMLDivElement>(null);",
"reasonCategory": "usageTrusted",
"updated": "2022-01-27T20:06:59.988Z",
"reasonDetail": "Doesn't manipulate the DOM."
},
{
"rule": "React-useRef",
"path": "ts/components/Modal.tsx",
@ -8096,93 +8141,5 @@
"line": " message.innerHTML = window.SignalContext.i18n('optimizingApplication');",
"reasonCategory": "usageTrusted",
"updated": "2021-09-17T21:02:59.414Z"
},
{
"rule": "jQuery-load(",
"path": "node_modules/agentkeepalive/node_modules/debug/src/browser.js",
"line": "function load() {",
"reasonCategory": "falseMatch",
"updated": "2022-04-08T07:10:10.677Z",
"reasonDetail": "node-gyp dependency"
},
{
"rule": "jQuery-load(",
"path": "node_modules/agentkeepalive/node_modules/debug/src/common.js",
"line": "\tcreateDebug.enable(createDebug.load());",
"reasonCategory": "falseMatch",
"updated": "2022-04-08T07:10:10.677Z",
"reasonDetail": "node-gyp dependency"
},
{
"rule": "jQuery-load(",
"path": "node_modules/agentkeepalive/node_modules/debug/src/node.js",
"line": "function load() {",
"reasonCategory": "falseMatch",
"updated": "2022-04-08T07:10:10.677Z",
"reasonDetail": "node-gyp dependency"
},
{
"rule": "jQuery-load(",
"path": "node_modules/make-fetch-happen/node_modules/debug/src/browser.js",
"line": "function load() {",
"reasonCategory": "falseMatch",
"updated": "2022-04-08T07:10:10.677Z",
"reasonDetail": "node-gyp dependency"
},
{
"rule": "jQuery-load(",
"path": "node_modules/make-fetch-happen/node_modules/debug/src/common.js",
"line": "\tcreateDebug.enable(createDebug.load());",
"reasonCategory": "falseMatch",
"updated": "2022-04-08T07:10:10.677Z",
"reasonDetail": "node-gyp dependency"
},
{
"rule": "jQuery-load(",
"path": "node_modules/make-fetch-happen/node_modules/debug/src/node.js",
"line": "function load() {",
"reasonCategory": "falseMatch",
"updated": "2022-04-08T07:10:10.677Z",
"reasonDetail": "node-gyp dependency"
},
{
"rule": "jQuery-append(",
"path": "node_modules/minipass-fetch/lib/headers.js",
"line": " this.append(headerName, value)",
"reasonCategory": "falseMatch",
"updated": "2022-04-08T07:10:10.677Z",
"reasonDetail": "node-gyp dependency"
},
{
"rule": "jQuery-append(",
"path": "node_modules/minipass-fetch/lib/headers.js",
"line": " this.append(pair[0], pair[1])",
"reasonCategory": "falseMatch",
"updated": "2022-04-08T07:10:10.677Z",
"reasonDetail": "node-gyp dependency"
},
{
"rule": "jQuery-append(",
"path": "node_modules/minipass-fetch/lib/headers.js",
"line": " this.append(key, init[key])",
"reasonCategory": "falseMatch",
"updated": "2022-04-08T07:10:10.677Z",
"reasonDetail": "node-gyp dependency"
},
{
"rule": "jQuery-append(",
"path": "node_modules/minipass-fetch/lib/request.js",
"line": " headers.append('Content-Type', contentType)",
"reasonCategory": "falseMatch",
"updated": "2022-04-08T07:10:10.677Z",
"reasonDetail": "node-gyp dependency"
},
{
"rule": "jQuery-append(",
"path": "node_modules/minipass-fetch/lib/response.js",
"line": " headers.append('Content-Type', contentType)",
"reasonCategory": "falseMatch",
"updated": "2022-04-08T07:10:10.677Z",
"reasonDetail": "node-gyp dependency"
}
]

View file

@ -1,18 +0,0 @@
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { noop } from 'lodash';
import type { AnimationConfig } from '@evanhahn/lottie-web-light';
type LottieAudioFactory = NonNullable<AnimationConfig['audioFactory']>;
type LottieAudio = ReturnType<LottieAudioFactory>;
const lottieNoopAudio: LottieAudio = {
play: noop,
seek: noop,
playing: noop,
rate: noop,
setVolume: noop,
};
export const lottieNoopAudioFactory: LottieAudioFactory = () => lottieNoopAudio;

View file

@ -1185,11 +1185,6 @@
resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.2.3.tgz#dfa0c92efe44a1d1a7974fb49ffeb40ef2da5a27"
integrity sha512-zVgvPwGK7c1aVdUVc9Qv7SqepOGRDrqCw7KZPSZziWGxSlbII3gmvGLPzLX4d0n0BMbamBacUrN22zOMyFFEkQ==
"@evanhahn/lottie-web-light@5.8.1":
version "5.8.1"
resolved "https://registry.yarnpkg.com/@evanhahn/lottie-web-light/-/lottie-web-light-5.8.1.tgz#9154f9301479ec16745da925d44bd721efa04cbb"
integrity sha512-U0G1tt3/UEYnyCNNslWPi1dB7X1xQ9aoSip+B3GTKO/Bns8yz/p39vBkRSN9d25nkbHuCsbjky2coQftj5YVKw==
"@gar/promisify@^1.1.3":
version "1.1.3"
resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6"