112 lines
2.9 KiB
TypeScript
112 lines
2.9 KiB
TypeScript
import React, {
|
|
memo, useEffect, useState,
|
|
} from '../../../lib/teact/teact';
|
|
import { withGlobal } from '../../../global';
|
|
|
|
import type { TabState } from '../../../global/types';
|
|
|
|
import { SVG_NAMESPACE } from '../../../config';
|
|
import { selectTabState } from '../../../global/selectors';
|
|
import buildClassName from '../../../util/buildClassName';
|
|
import buildStyle from '../../../util/buildStyle';
|
|
import windowSize from '../../../util/windowSize';
|
|
|
|
import useLastCallback from '../../../hooks/useLastCallback';
|
|
|
|
import { addSvgDefinition, removeSvgDefinition } from '../SvgController';
|
|
|
|
import styles from './WaveContainer.module.scss';
|
|
|
|
import waveRipple from '../../../assets/wave_ripple.svg';
|
|
|
|
type StateProps = {
|
|
waveInfo?: TabState['wave'];
|
|
};
|
|
|
|
type Wave = {
|
|
startTime: number;
|
|
waveWidth: number;
|
|
top: number;
|
|
left: number;
|
|
};
|
|
|
|
const BASE_SIZE_MULTIPLIER = 1.73;
|
|
const FILTER_ID = 'wave-filter';
|
|
const FILTER_SCALE = '20';
|
|
const WAVE_COUNT_LIMIT = 7;
|
|
|
|
const WaveContainer = ({ waveInfo }: StateProps) => {
|
|
const [waves, setWaves] = useState<Wave[]>([]);
|
|
|
|
const addWave = useLastCallback((newWave: Wave) => {
|
|
if (waves.length >= WAVE_COUNT_LIMIT) return;
|
|
|
|
setWaves((prevWaves) => [...prevWaves, newWave]);
|
|
});
|
|
|
|
useEffect(() => {
|
|
if (!waveInfo) return;
|
|
|
|
const { startX, startY } = waveInfo;
|
|
const { width, height } = windowSize.get();
|
|
|
|
const maxSize = Math.max(width - startX, height - startY, startX, startY);
|
|
const overlaySize = maxSize * BASE_SIZE_MULTIPLIER;
|
|
const top = startY - overlaySize / 2;
|
|
const left = startX - overlaySize / 2;
|
|
|
|
addWave({
|
|
startTime: waveInfo.lastWaveTime,
|
|
waveWidth: overlaySize,
|
|
top,
|
|
left,
|
|
});
|
|
}, [waveInfo]);
|
|
|
|
useEffect(() => {
|
|
addSvgDefinition(
|
|
<filter x="0" y="0" width="1" height="1" color-interpolation-filters="sRGB" xmlns={SVG_NAMESPACE}>
|
|
<feImage href={waveRipple} result="waveImage" />
|
|
<feDisplacementMap
|
|
in="SourceGraphic"
|
|
in2="waveImage"
|
|
scale={FILTER_SCALE}
|
|
xChannelSelector="R"
|
|
yChannelSelector="B"
|
|
/>
|
|
</filter>,
|
|
FILTER_ID,
|
|
);
|
|
|
|
return () => {
|
|
removeSvgDefinition(FILTER_ID);
|
|
};
|
|
}, []);
|
|
|
|
return (
|
|
<div className={buildClassName(styles.root)} teactFastList>
|
|
{waves.map((wave) => (
|
|
<div
|
|
className={styles.wave}
|
|
style={buildStyle(
|
|
`--wave-width: ${wave.waveWidth}px`,
|
|
`--wave-pos-top: ${wave.top}px`,
|
|
`--wave-pos-left: ${wave.left}px`,
|
|
)}
|
|
key={wave.startTime}
|
|
onAnimationEnd={() => setWaves((prevWaves) => prevWaves.filter((w) => w !== wave))}
|
|
/>
|
|
))}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default memo(withGlobal(
|
|
(global): StateProps => {
|
|
const tabState = selectTabState(global);
|
|
return {
|
|
waveInfo: tabState.wave,
|
|
};
|
|
},
|
|
)(WaveContainer));
|