Message: Play effect on deletion (#5125)
This commit is contained in:
parent
a68ee36582
commit
6babbae9f9
@ -1,13 +1,13 @@
|
|||||||
import type { FC } from '../../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
import React, {
|
import React, {
|
||||||
memo, useCallback, useEffect, useState,
|
memo, useCallback, useEffect, useRef, useState,
|
||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../../global';
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
import { DEBUG_LOG_FILENAME } from '../../../config';
|
import { DEBUG_LOG_FILENAME } from '../../../config';
|
||||||
import { getDebugLogs } from '../../../util/debugConsole';
|
import { getDebugLogs } from '../../../util/debugConsole';
|
||||||
import download from '../../../util/download';
|
import download from '../../../util/download';
|
||||||
import { IS_ELECTRON, IS_WAVE_TRANSFORM_SUPPORTED } from '../../../util/windowEnvironment';
|
import { IS_ELECTRON, IS_SNAP_EFFECT_SUPPORTED, IS_WAVE_TRANSFORM_SUPPORTED } from '../../../util/windowEnvironment';
|
||||||
import { LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets';
|
import { LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets';
|
||||||
|
|
||||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||||
@ -15,6 +15,7 @@ import useLastCallback from '../../../hooks/useLastCallback';
|
|||||||
import useOldLang from '../../../hooks/useOldLang';
|
import useOldLang from '../../../hooks/useOldLang';
|
||||||
|
|
||||||
import AnimatedIconWithPreview from '../../common/AnimatedIconWithPreview';
|
import AnimatedIconWithPreview from '../../common/AnimatedIconWithPreview';
|
||||||
|
import { animateSnap } from '../../main/visualEffects/SnapEffectContainer';
|
||||||
import Checkbox from '../../ui/Checkbox';
|
import Checkbox from '../../ui/Checkbox';
|
||||||
import ListItem from '../../ui/ListItem';
|
import ListItem from '../../ui/ListItem';
|
||||||
|
|
||||||
@ -39,6 +40,11 @@ const SettingsExperimental: FC<OwnProps & StateProps> = ({
|
|||||||
shouldDebugExportedSenders,
|
shouldDebugExportedSenders,
|
||||||
}) => {
|
}) => {
|
||||||
const { requestConfetti, setSettingOption, requestWave } = getActions();
|
const { requestConfetti, setSettingOption, requestWave } = getActions();
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-null/no-null
|
||||||
|
const snapButtonRef = useRef<HTMLDivElement>(null);
|
||||||
|
const [isSnapButtonAnimating, setIsSnapButtonAnimating] = useState(false);
|
||||||
|
|
||||||
const lang = useOldLang();
|
const lang = useOldLang();
|
||||||
|
|
||||||
const [isAutoUpdateEnabled, setIsAutoUpdateEnabled] = useState(false);
|
const [isAutoUpdateEnabled, setIsAutoUpdateEnabled] = useState(false);
|
||||||
@ -65,6 +71,19 @@ const SettingsExperimental: FC<OwnProps & StateProps> = ({
|
|||||||
requestWave({ startX: e.clientX, startY: e.clientY });
|
requestWave({ startX: e.clientX, startY: e.clientY });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleSnap = useLastCallback(() => {
|
||||||
|
const button = snapButtonRef.current;
|
||||||
|
if (!button) return;
|
||||||
|
|
||||||
|
if (animateSnap(button)) {
|
||||||
|
setIsSnapButtonAnimating(true);
|
||||||
|
// Manual reset for debug
|
||||||
|
setTimeout(() => {
|
||||||
|
setIsSnapButtonAnimating(false);
|
||||||
|
}, 1500);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="settings-content custom-scroll">
|
<div className="settings-content custom-scroll">
|
||||||
<div className="settings-content-header no-border">
|
<div className="settings-content-header no-border">
|
||||||
@ -92,6 +111,15 @@ const SettingsExperimental: FC<OwnProps & StateProps> = ({
|
|||||||
>
|
>
|
||||||
<div className="title">Start wave</div>
|
<div className="title">Start wave</div>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
<ListItem
|
||||||
|
ref={snapButtonRef}
|
||||||
|
onClick={handleSnap}
|
||||||
|
icon="spoiler"
|
||||||
|
disabled={!IS_SNAP_EFFECT_SUPPORTED}
|
||||||
|
style={isSnapButtonAnimating ? 'visibility: hidden' : ''}
|
||||||
|
>
|
||||||
|
<div className="title">Vaporize this button</div>
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
<Checkbox
|
<Checkbox
|
||||||
label="Allow HTTP Transport"
|
label="Allow HTTP Transport"
|
||||||
|
|||||||
@ -15,7 +15,7 @@ import {
|
|||||||
} from '../../../global/initialState';
|
} from '../../../global/initialState';
|
||||||
import { selectPerformanceSettings } from '../../../global/selectors';
|
import { selectPerformanceSettings } from '../../../global/selectors';
|
||||||
import { areDeepEqual } from '../../../util/areDeepEqual';
|
import { areDeepEqual } from '../../../util/areDeepEqual';
|
||||||
import { IS_BACKDROP_BLUR_SUPPORTED } from '../../../util/windowEnvironment';
|
import { IS_BACKDROP_BLUR_SUPPORTED, IS_SNAP_EFFECT_SUPPORTED } from '../../../util/windowEnvironment';
|
||||||
|
|
||||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||||
import useOldLang from '../../../hooks/useOldLang';
|
import useOldLang from '../../../hooks/useOldLang';
|
||||||
@ -60,6 +60,7 @@ const PERFORMANCE_OPTIONS: PerformanceSection[] = [
|
|||||||
{ key: 'contextMenuAnimations', label: 'Context Menu Animation' },
|
{ key: 'contextMenuAnimations', label: 'Context Menu Animation' },
|
||||||
{ key: 'contextMenuBlur', label: 'Context Menu Blur', disabled: !IS_BACKDROP_BLUR_SUPPORTED },
|
{ key: 'contextMenuBlur', label: 'Context Menu Blur', disabled: !IS_BACKDROP_BLUR_SUPPORTED },
|
||||||
{ key: 'rightColumnAnimations', label: 'Right Column Animation' },
|
{ key: 'rightColumnAnimations', label: 'Right Column Animation' },
|
||||||
|
{ key: 'snapEffect', label: 'Dust-effect deletion' },
|
||||||
]],
|
]],
|
||||||
['Stickers and Emoji', [
|
['Stickers and Emoji', [
|
||||||
{ key: 'animatedEmoji', label: 'Allow Animated Emoji' },
|
{ key: 'animatedEmoji', label: 'Allow Animated Emoji' },
|
||||||
@ -195,16 +196,19 @@ function SettingsPerformance({
|
|||||||
</div>
|
</div>
|
||||||
{Boolean(sectionExpandedStates[index]) && (
|
{Boolean(sectionExpandedStates[index]) && (
|
||||||
<div className="DropdownList DropdownList--open">
|
<div className="DropdownList DropdownList--open">
|
||||||
{options.map(({ key, label, disabled }) => (
|
{options.map(({ key, label, disabled }) => {
|
||||||
<Checkbox
|
if (key === 'snapEffect' && !IS_SNAP_EFFECT_SUPPORTED) return undefined;
|
||||||
key={key}
|
return (
|
||||||
name={key}
|
<Checkbox
|
||||||
checked={performanceSettings[key]}
|
key={key}
|
||||||
label={lang(label)}
|
name={key}
|
||||||
disabled={disabled}
|
checked={performanceSettings[key]}
|
||||||
onChange={handlePropertyChange}
|
label={lang(label)}
|
||||||
/>
|
disabled={disabled}
|
||||||
))}
|
onChange={handlePropertyChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -87,6 +87,7 @@ import PremiumMainModal from './premium/PremiumMainModal.async';
|
|||||||
import StarsGiftingPickerModal from './premium/StarsGiftingPickerModal.async';
|
import StarsGiftingPickerModal from './premium/StarsGiftingPickerModal.async';
|
||||||
import SafeLinkModal from './SafeLinkModal.async';
|
import SafeLinkModal from './SafeLinkModal.async';
|
||||||
import ConfettiContainer from './visualEffects/ConfettiContainer';
|
import ConfettiContainer from './visualEffects/ConfettiContainer';
|
||||||
|
import SnapEffectContainer from './visualEffects/SnapEffectContainer';
|
||||||
import WaveContainer from './visualEffects/WaveContainer';
|
import WaveContainer from './visualEffects/WaveContainer';
|
||||||
|
|
||||||
import './Main.scss';
|
import './Main.scss';
|
||||||
@ -566,6 +567,7 @@ const Main = ({
|
|||||||
<DownloadManager />
|
<DownloadManager />
|
||||||
<ConfettiContainer />
|
<ConfettiContainer />
|
||||||
{IS_WAVE_TRANSFORM_SUPPORTED && <WaveContainer />}
|
{IS_WAVE_TRANSFORM_SUPPORTED && <WaveContainer />}
|
||||||
|
<SnapEffectContainer />
|
||||||
<PhoneCall isActive={isPhoneCallActive} />
|
<PhoneCall isActive={isPhoneCallActive} />
|
||||||
<UnreadCount isForAppBadge />
|
<UnreadCount isForAppBadge />
|
||||||
<RatePhoneCallModal isOpen={isRatePhoneCallModalOpen} />
|
<RatePhoneCallModal isOpen={isRatePhoneCallModalOpen} />
|
||||||
|
|||||||
@ -0,0 +1,29 @@
|
|||||||
|
.root {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
z-index: var(--z-overlay-effects);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ghost {
|
||||||
|
position: absolute;
|
||||||
|
overflow: visible !important;
|
||||||
|
|
||||||
|
animation: scale 1s ease-in forwards !important;
|
||||||
|
transform-origin: bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
.elementContainer {
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes scale {
|
||||||
|
0% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: scale(1.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
172
src/components/main/visualEffects/SnapEffectContainer.tsx
Normal file
172
src/components/main/visualEffects/SnapEffectContainer.tsx
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
import React, { memo } from '../../../lib/teact/teact';
|
||||||
|
import { getGlobal } from '../../../global';
|
||||||
|
|
||||||
|
import { SNAP_EFFECT_CONTAINER_ID, SNAP_EFFECT_ID } from '../../../config';
|
||||||
|
import { selectCanAnimateSnapEffect } from '../../../global/selectors';
|
||||||
|
import generateUniqueId from '../../../util/generateUniqueId';
|
||||||
|
import { SVG_NAMESPACE } from '../../../util/svgController';
|
||||||
|
|
||||||
|
import styles from './SnapEffectContainer.module.scss';
|
||||||
|
|
||||||
|
const VISIBLITY_MARGIN = 50;
|
||||||
|
const DURATION = 1000;
|
||||||
|
|
||||||
|
const SnapEffectContainer = () => {
|
||||||
|
return (
|
||||||
|
<div className={styles.root} id={SNAP_EFFECT_CONTAINER_ID} />
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(SnapEffectContainer);
|
||||||
|
|
||||||
|
export function animateSnap(element: HTMLElement) {
|
||||||
|
const global = getGlobal();
|
||||||
|
const canPlayEffect = selectCanAnimateSnapEffect(global);
|
||||||
|
if (!canPlayEffect) return false;
|
||||||
|
// Get element current fixed position on screen
|
||||||
|
const rect = element.getBoundingClientRect();
|
||||||
|
const x = rect.left + window.scrollX;
|
||||||
|
const y = rect.top + window.scrollY;
|
||||||
|
const width = rect.width;
|
||||||
|
const height = rect.height;
|
||||||
|
|
||||||
|
// Check for visibility
|
||||||
|
if (x + width + VISIBLITY_MARGIN < 0 || x - VISIBLITY_MARGIN > window.innerWidth
|
||||||
|
|| y + height + VISIBLITY_MARGIN < 0 || y - VISIBLITY_MARGIN > window.innerHeight) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const seed = Math.floor(Date.now() / 1000);
|
||||||
|
const filterId = `${SNAP_EFFECT_ID}-${generateUniqueId()}`;
|
||||||
|
|
||||||
|
const svg = document.createElementNS(SVG_NAMESPACE, 'svg');
|
||||||
|
svg.setAttribute('class', styles.ghost);
|
||||||
|
svg.setAttribute('width', `${width}px`);
|
||||||
|
svg.setAttribute('height', `${height}px`);
|
||||||
|
svg.setAttribute('style', `left: ${x}px; top: ${y}px;`);
|
||||||
|
svg.setAttribute('viewBox', `0 0 ${width} ${height}`);
|
||||||
|
|
||||||
|
const defs = document.createElementNS(SVG_NAMESPACE, 'defs');
|
||||||
|
svg.appendChild(defs);
|
||||||
|
|
||||||
|
const filter = createFilter(Math.min(width, height), seed);
|
||||||
|
filter.setAttribute('id', filterId);
|
||||||
|
defs.appendChild(filter);
|
||||||
|
|
||||||
|
const g = document.createElementNS(SVG_NAMESPACE, 'g');
|
||||||
|
g.setAttribute('filter', `url(#${filterId})`);
|
||||||
|
svg.appendChild(g);
|
||||||
|
|
||||||
|
const foreignObject = document.createElementNS(SVG_NAMESPACE, 'foreignObject');
|
||||||
|
foreignObject.setAttribute('class', styles.elementContainer);
|
||||||
|
foreignObject.setAttribute('width', `${width}px`);
|
||||||
|
foreignObject.setAttribute('height', `${height}px`);
|
||||||
|
g.appendChild(foreignObject);
|
||||||
|
|
||||||
|
const computedStyle = window.getComputedStyle(element);
|
||||||
|
const clone = element.cloneNode(true) as HTMLElement;
|
||||||
|
Array.from(computedStyle).forEach((key) => (
|
||||||
|
clone.style.setProperty(key, computedStyle.getPropertyValue(key), 'important')
|
||||||
|
));
|
||||||
|
|
||||||
|
foreignObject.appendChild(clone);
|
||||||
|
|
||||||
|
const snapContainer = document.getElementById(SNAP_EFFECT_CONTAINER_ID)!;
|
||||||
|
snapContainer.appendChild(svg);
|
||||||
|
|
||||||
|
svg.addEventListener('animationend', () => {
|
||||||
|
snapContainer.removeChild(svg);
|
||||||
|
}, {
|
||||||
|
once: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createFilter(smallestSide: number, baseSeed: number = 42) {
|
||||||
|
const filter = document.createElementNS(SVG_NAMESPACE, 'filter');
|
||||||
|
filter.setAttribute('x', '-150%');
|
||||||
|
filter.setAttribute('y', '-150%');
|
||||||
|
filter.setAttribute('width', '400%');
|
||||||
|
filter.setAttribute('height', '400%');
|
||||||
|
filter.setAttribute('color-interpolation-filters', 'sRGB');
|
||||||
|
|
||||||
|
const feTurbulence = document.createElementNS(SVG_NAMESPACE, 'feTurbulence');
|
||||||
|
feTurbulence.setAttribute('type', 'fractalNoise');
|
||||||
|
feTurbulence.setAttribute('baseFrequency', '0.5');
|
||||||
|
feTurbulence.setAttribute('numOctaves', '1');
|
||||||
|
feTurbulence.setAttribute('result', 'dustNoise');
|
||||||
|
feTurbulence.setAttribute('seed', baseSeed.toString());
|
||||||
|
filter.appendChild(feTurbulence);
|
||||||
|
|
||||||
|
const feComponentTransfer = document.createElementNS(SVG_NAMESPACE, 'feComponentTransfer');
|
||||||
|
feComponentTransfer.setAttribute('in', 'dustNoise');
|
||||||
|
feComponentTransfer.setAttribute('result', 'dustNoiseMask');
|
||||||
|
filter.appendChild(feComponentTransfer);
|
||||||
|
|
||||||
|
const feFuncA = document.createElementNS(SVG_NAMESPACE, 'feFuncA');
|
||||||
|
feFuncA.setAttribute('type', 'linear');
|
||||||
|
feFuncA.setAttribute('slope', '5');
|
||||||
|
feFuncA.setAttribute('intercept', '0');
|
||||||
|
feComponentTransfer.appendChild(feFuncA);
|
||||||
|
|
||||||
|
const feFuncAAnimate = document.createElementNS(SVG_NAMESPACE, 'animate');
|
||||||
|
feFuncAAnimate.setAttribute('attributeName', 'slope');
|
||||||
|
feFuncAAnimate.setAttribute('values', '5; 2; 1; 0');
|
||||||
|
feFuncAAnimate.setAttribute('dur', `${DURATION}ms`);
|
||||||
|
feFuncAAnimate.setAttribute('fill', 'freeze');
|
||||||
|
feFuncA.appendChild(feFuncAAnimate);
|
||||||
|
|
||||||
|
const feComposite = document.createElementNS(SVG_NAMESPACE, 'feComposite');
|
||||||
|
feComposite.setAttribute('in', 'SourceGraphic');
|
||||||
|
feComposite.setAttribute('in2', 'dustNoiseMask');
|
||||||
|
feComposite.setAttribute('operator', 'in');
|
||||||
|
feComposite.setAttribute('result', 'dustySource');
|
||||||
|
filter.appendChild(feComposite);
|
||||||
|
|
||||||
|
const feTurbulence2 = document.createElementNS(SVG_NAMESPACE, 'feTurbulence');
|
||||||
|
feTurbulence2.setAttribute('type', 'fractalNoise');
|
||||||
|
feTurbulence2.setAttribute('baseFrequency', '0.015');
|
||||||
|
feTurbulence2.setAttribute('numOctaves', '1');
|
||||||
|
feTurbulence2.setAttribute('result', 'displacementNoice1');
|
||||||
|
feTurbulence2.setAttribute('seed', (baseSeed + 1).toString());
|
||||||
|
filter.appendChild(feTurbulence2);
|
||||||
|
|
||||||
|
const feTurbulence3 = document.createElementNS(SVG_NAMESPACE, 'feTurbulence');
|
||||||
|
feTurbulence3.setAttribute('type', 'fractalNoise');
|
||||||
|
feTurbulence3.setAttribute('baseFrequency', '1');
|
||||||
|
feTurbulence3.setAttribute('numOctaves', '2');
|
||||||
|
feTurbulence3.setAttribute('result', 'displacementNoice2');
|
||||||
|
feTurbulence3.setAttribute('seed', (baseSeed + 2).toString());
|
||||||
|
filter.appendChild(feTurbulence3);
|
||||||
|
|
||||||
|
const feMerge = document.createElementNS(SVG_NAMESPACE, 'feMerge');
|
||||||
|
feMerge.setAttribute('result', 'combinedNoise');
|
||||||
|
filter.appendChild(feMerge);
|
||||||
|
|
||||||
|
const feMergeNode1 = document.createElementNS(SVG_NAMESPACE, 'feMergeNode');
|
||||||
|
feMergeNode1.setAttribute('in', 'displacementNoice1');
|
||||||
|
feMerge.appendChild(feMergeNode1);
|
||||||
|
|
||||||
|
const feMergeNode2 = document.createElementNS(SVG_NAMESPACE, 'feMergeNode');
|
||||||
|
feMergeNode2.setAttribute('in', 'displacementNoice2');
|
||||||
|
feMerge.appendChild(feMergeNode2);
|
||||||
|
|
||||||
|
const feDisplacementMap = document.createElementNS(SVG_NAMESPACE, 'feDisplacementMap');
|
||||||
|
feDisplacementMap.setAttribute('in', 'dustySource');
|
||||||
|
feDisplacementMap.setAttribute('in2', 'combinedNoise');
|
||||||
|
feDisplacementMap.setAttribute('scale', '0');
|
||||||
|
|
||||||
|
feDisplacementMap.setAttribute('xChannelSelector', 'R');
|
||||||
|
feDisplacementMap.setAttribute('yChannelSelector', 'G');
|
||||||
|
filter.appendChild(feDisplacementMap);
|
||||||
|
|
||||||
|
const feDisplacementMapAnimate = document.createElementNS(SVG_NAMESPACE, 'animate');
|
||||||
|
feDisplacementMapAnimate.setAttribute('attributeName', 'scale');
|
||||||
|
feDisplacementMapAnimate.setAttribute('values', `0; ${smallestSide * 3}`);
|
||||||
|
feDisplacementMapAnimate.setAttribute('dur', `${DURATION}ms`);
|
||||||
|
feDisplacementMapAnimate.setAttribute('fill', 'freeze');
|
||||||
|
feDisplacementMap.appendChild(feDisplacementMapAnimate);
|
||||||
|
|
||||||
|
return filter;
|
||||||
|
}
|
||||||
@ -268,6 +268,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.is-dissolving {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
&.is-story-mention {
|
&.is-story-mention {
|
||||||
--background-color: var(--pattern-color);
|
--background-color: var(--pattern-color);
|
||||||
|
|
||||||
|
|||||||
@ -157,6 +157,7 @@ import StarIcon from '../../common/icons/StarIcon';
|
|||||||
import MessageText from '../../common/MessageText';
|
import MessageText from '../../common/MessageText';
|
||||||
import ReactionStaticEmoji from '../../common/reactions/ReactionStaticEmoji';
|
import ReactionStaticEmoji from '../../common/reactions/ReactionStaticEmoji';
|
||||||
import TopicChip from '../../common/TopicChip';
|
import TopicChip from '../../common/TopicChip';
|
||||||
|
import { animateSnap } from '../../main/visualEffects/SnapEffectContainer';
|
||||||
import Button from '../../ui/Button';
|
import Button from '../../ui/Button';
|
||||||
import Album from './Album';
|
import Album from './Album';
|
||||||
import AnimatedCustomEmoji from './AnimatedCustomEmoji';
|
import AnimatedCustomEmoji from './AnimatedCustomEmoji';
|
||||||
@ -443,6 +444,8 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
const lang = useOldLang();
|
const lang = useOldLang();
|
||||||
|
|
||||||
const [isTranscriptionHidden, setTranscriptionHidden] = useState(false);
|
const [isTranscriptionHidden, setTranscriptionHidden] = useState(false);
|
||||||
|
const [isPlayingSnapAnimation, setIsPlayingSnapAnimation] = useState(false);
|
||||||
|
const [isPlayingDeleteAnimation, setIsPlayingDeleteAnimation] = useState(false);
|
||||||
const [shouldPlayEffect, requestEffect, hideEffect] = useFlag();
|
const [shouldPlayEffect, requestEffect, hideEffect] = useFlag();
|
||||||
const { isMobile, isTouchScreen } = useAppLayout();
|
const { isMobile, isTouchScreen } = useAppLayout();
|
||||||
|
|
||||||
@ -666,6 +669,17 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
}, [focusLastMessage, isLastInList, transcribedText, withVoiceTranscription]);
|
}, [focusLastMessage, isLastInList, transcribedText, withVoiceTranscription]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const element = ref.current;
|
||||||
|
if (message.isDeleting && element) {
|
||||||
|
if (animateSnap(element)) {
|
||||||
|
setIsPlayingSnapAnimation(true);
|
||||||
|
} else {
|
||||||
|
setIsPlayingDeleteAnimation(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [message.isDeleting]);
|
||||||
|
|
||||||
const textMessage = album?.hasMultipleCaptions ? undefined : (album?.captionMessage || message);
|
const textMessage = album?.hasMultipleCaptions ? undefined : (album?.captionMessage || message);
|
||||||
const hasTextContent = textMessage && hasMessageText(textMessage);
|
const hasTextContent = textMessage && hasMessageText(textMessage);
|
||||||
const hasText = hasTextContent || hasFactCheck;
|
const hasText = hasTextContent || hasFactCheck;
|
||||||
@ -685,7 +699,8 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
isContextMenuOpen && 'has-menu-open',
|
isContextMenuOpen && 'has-menu-open',
|
||||||
isFocused && !noFocusHighlight && 'focused',
|
isFocused && !noFocusHighlight && 'focused',
|
||||||
isForwarding && 'is-forwarding',
|
isForwarding && 'is-forwarding',
|
||||||
message.isDeleting && 'is-deleting',
|
isPlayingDeleteAnimation && 'is-deleting',
|
||||||
|
isPlayingSnapAnimation && 'is-dissolving',
|
||||||
isInDocumentGroup && 'is-in-document-group',
|
isInDocumentGroup && 'is-in-document-group',
|
||||||
isAlbum && 'is-album',
|
isAlbum && 'is-album',
|
||||||
message.hasUnreadMention && 'has-unread-mention',
|
message.hasUnreadMention && 'has-unread-mention',
|
||||||
|
|||||||
@ -148,6 +148,9 @@ export const CUSTOM_APPENDIX_ATTRIBUTE = 'data-has-custom-appendix';
|
|||||||
export const MESSAGE_CONTENT_CLASS_NAME = 'message-content';
|
export const MESSAGE_CONTENT_CLASS_NAME = 'message-content';
|
||||||
export const MESSAGE_CONTENT_SELECTOR = '.message-content';
|
export const MESSAGE_CONTENT_SELECTOR = '.message-content';
|
||||||
|
|
||||||
|
export const SNAP_EFFECT_CONTAINER_ID = 'snap-effect-container';
|
||||||
|
export const SNAP_EFFECT_ID = 'snap-effect';
|
||||||
|
|
||||||
export const STARS_ICON_PLACEHOLDER = '⭐';
|
export const STARS_ICON_PLACEHOLDER = '⭐';
|
||||||
export const STARS_CURRENCY_CODE = 'XTR';
|
export const STARS_CURRENCY_CODE = 'XTR';
|
||||||
|
|
||||||
|
|||||||
@ -56,6 +56,7 @@ import {
|
|||||||
import { updateUnreadReactions } from '../../reducers/reactions';
|
import { updateUnreadReactions } from '../../reducers/reactions';
|
||||||
import { updateTabState } from '../../reducers/tabs';
|
import { updateTabState } from '../../reducers/tabs';
|
||||||
import {
|
import {
|
||||||
|
selectCanAnimateSnapEffect,
|
||||||
selectChat,
|
selectChat,
|
||||||
selectChatLastMessageId,
|
selectChatLastMessageId,
|
||||||
selectChatMessage,
|
selectChatMessage,
|
||||||
@ -85,6 +86,7 @@ import {
|
|||||||
} from '../../selectors';
|
} from '../../selectors';
|
||||||
|
|
||||||
const ANIMATION_DELAY = 350;
|
const ANIMATION_DELAY = 350;
|
||||||
|
const SNAP_ANIMATION_DELAY = 1000;
|
||||||
|
|
||||||
addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
||||||
switch (update['@type']) {
|
switch (update['@type']) {
|
||||||
@ -1056,11 +1058,13 @@ export function deleteMessages<T extends GlobalState>(
|
|||||||
|
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
|
|
||||||
|
const isAnimatingAsSnap = selectCanAnimateSnapEffect(global);
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
global = deleteChatMessages(global, chatId, ids);
|
global = deleteChatMessages(global, chatId, ids);
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
}, ANIMATION_DELAY);
|
}, isAnimatingAsSnap ? SNAP_ANIMATION_DELAY : ANIMATION_DELAY);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1099,11 +1103,13 @@ export function deleteMessages<T extends GlobalState>(
|
|||||||
global = deletePeerPhoto(global, commonBoxChatId, message.content.action.photo.id, true);
|
global = deletePeerPhoto(global, commonBoxChatId, message.content.action.photo.id, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isAnimatingAsSnap = selectCanAnimateSnapEffect(global);
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
global = deleteChatMessages(global, commonBoxChatId, [id]);
|
global = deleteChatMessages(global, commonBoxChatId, [id]);
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
}, ANIMATION_DELAY);
|
}, isAnimatingAsSnap ? SNAP_ANIMATION_DELAY : ANIMATION_DELAY);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1129,6 +1135,8 @@ function deleteScheduledMessages<T extends GlobalState>(
|
|||||||
|
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
|
|
||||||
|
const isAnimatingAsSnap = selectCanAnimateSnapEffect(global);
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
global = deleteChatScheduledMessages(global, chatId, ids);
|
global = deleteChatScheduledMessages(global, chatId, ids);
|
||||||
@ -1137,5 +1145,5 @@ function deleteScheduledMessages<T extends GlobalState>(
|
|||||||
global, chatId, MAIN_THREAD_ID, 'scheduledIds', Object.keys(scheduledMessages || {}).map(Number),
|
global, chatId, MAIN_THREAD_ID, 'scheduledIds', Object.keys(scheduledMessages || {}).map(Number),
|
||||||
);
|
);
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
}, ANIMATION_DELAY);
|
}, isAnimatingAsSnap ? SNAP_ANIMATION_DELAY : ANIMATION_DELAY);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,6 +29,7 @@ export const INITIAL_PERFORMANCE_STATE_MAX: PerformanceType = {
|
|||||||
rightColumnAnimations: true,
|
rightColumnAnimations: true,
|
||||||
stickerEffects: true,
|
stickerEffects: true,
|
||||||
storyRibbonAnimations: true,
|
storyRibbonAnimations: true,
|
||||||
|
snapEffect: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const INITIAL_PERFORMANCE_STATE_MID: PerformanceType = {
|
export const INITIAL_PERFORMANCE_STATE_MID: PerformanceType = {
|
||||||
@ -46,6 +47,7 @@ export const INITIAL_PERFORMANCE_STATE_MID: PerformanceType = {
|
|||||||
rightColumnAnimations: false,
|
rightColumnAnimations: false,
|
||||||
stickerEffects: false,
|
stickerEffects: false,
|
||||||
storyRibbonAnimations: false,
|
storyRibbonAnimations: false,
|
||||||
|
snapEffect: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const INITIAL_PERFORMANCE_STATE_MIN: PerformanceType = {
|
export const INITIAL_PERFORMANCE_STATE_MIN: PerformanceType = {
|
||||||
@ -63,6 +65,7 @@ export const INITIAL_PERFORMANCE_STATE_MIN: PerformanceType = {
|
|||||||
rightColumnAnimations: false,
|
rightColumnAnimations: false,
|
||||||
stickerEffects: false,
|
stickerEffects: false,
|
||||||
storyRibbonAnimations: false,
|
storyRibbonAnimations: false,
|
||||||
|
snapEffect: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const INITIAL_GLOBAL_STATE: GlobalState = {
|
export const INITIAL_GLOBAL_STATE: GlobalState = {
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import type { GlobalState, TabArgs } from '../types';
|
|||||||
import { NewChatMembersProgress, RightColumnContent } from '../../types';
|
import { NewChatMembersProgress, RightColumnContent } from '../../types';
|
||||||
|
|
||||||
import { getCurrentTabId } from '../../util/establishMultitabRole';
|
import { getCurrentTabId } from '../../util/establishMultitabRole';
|
||||||
|
import { IS_SNAP_EFFECT_SUPPORTED } from '../../util/windowEnvironment';
|
||||||
import { getMessageVideo, getMessageWebPageVideo } from '../helpers/messageMedia';
|
import { getMessageVideo, getMessageWebPageVideo } from '../helpers/messageMedia';
|
||||||
import { selectCurrentManagement } from './management';
|
import { selectCurrentManagement } from './management';
|
||||||
import { selectIsStatisticsShown } from './statistics';
|
import { selectIsStatisticsShown } from './statistics';
|
||||||
@ -145,3 +146,7 @@ export function selectIsContextMenuTranslucent<T extends GlobalState>(global: T)
|
|||||||
export function selectIsSynced<T extends GlobalState>(global: T) {
|
export function selectIsSynced<T extends GlobalState>(global: T) {
|
||||||
return global.isSynced;
|
return global.isSynced;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function selectCanAnimateSnapEffect<T extends GlobalState>(global: T) {
|
||||||
|
return IS_SNAP_EFFECT_SUPPORTED && selectPerformanceSettingsValue(global, 'snapEffect');
|
||||||
|
}
|
||||||
|
|||||||
@ -57,7 +57,7 @@ export type PerformanceTypeKey = (
|
|||||||
'pageTransitions' | 'messageSendingAnimations' | 'mediaViewerAnimations'
|
'pageTransitions' | 'messageSendingAnimations' | 'mediaViewerAnimations'
|
||||||
| 'messageComposerAnimations' | 'contextMenuAnimations' | 'contextMenuBlur' | 'rightColumnAnimations'
|
| 'messageComposerAnimations' | 'contextMenuAnimations' | 'contextMenuBlur' | 'rightColumnAnimations'
|
||||||
| 'animatedEmoji' | 'loopAnimatedStickers' | 'reactionEffects' | 'stickerEffects' | 'autoplayGifs' | 'autoplayVideos'
|
| 'animatedEmoji' | 'loopAnimatedStickers' | 'reactionEffects' | 'stickerEffects' | 'autoplayGifs' | 'autoplayVideos'
|
||||||
| 'storyRibbonAnimations'
|
| 'storyRibbonAnimations' | 'snapEffect'
|
||||||
);
|
);
|
||||||
export type PerformanceType = {
|
export type PerformanceType = {
|
||||||
[key in PerformanceTypeKey]: boolean;
|
[key in PerformanceTypeKey]: boolean;
|
||||||
|
|||||||
@ -3,6 +3,8 @@ import generateUniqueId from './generateUniqueId';
|
|||||||
export const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
|
export const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
|
||||||
|
|
||||||
const CONTAINER = document.createElementNS(SVG_NAMESPACE, 'svg');
|
const CONTAINER = document.createElementNS(SVG_NAMESPACE, 'svg');
|
||||||
|
CONTAINER.setAttribute('width', '0');
|
||||||
|
CONTAINER.setAttribute('height', '0');
|
||||||
CONTAINER.setAttribute('viewBox', '0 0 1 1');
|
CONTAINER.setAttribute('viewBox', '0 0 1 1');
|
||||||
CONTAINER.classList.add('svg-definitions');
|
CONTAINER.classList.add('svg-definitions');
|
||||||
document.body.appendChild(CONTAINER);
|
document.body.appendChild(CONTAINER);
|
||||||
|
|||||||
@ -71,9 +71,13 @@ export const IS_CANVAS_FILTER_SUPPORTED = (
|
|||||||
export const IS_REQUEST_FULLSCREEN_SUPPORTED = 'requestFullscreen' in document.createElement('div');
|
export const IS_REQUEST_FULLSCREEN_SUPPORTED = 'requestFullscreen' in document.createElement('div');
|
||||||
export const ARE_CALLS_SUPPORTED = !IS_FIREFOX; // https://bugzilla.mozilla.org/show_bug.cgi?id=1923416
|
export const ARE_CALLS_SUPPORTED = !IS_FIREFOX; // https://bugzilla.mozilla.org/show_bug.cgi?id=1923416
|
||||||
export const LAYERS_ANIMATION_NAME = IS_ANDROID ? 'slideFade' : IS_IOS ? 'slideLayers' : 'pushSlide';
|
export const LAYERS_ANIMATION_NAME = IS_ANDROID ? 'slideFade' : IS_IOS ? 'slideLayers' : 'pushSlide';
|
||||||
|
|
||||||
export const IS_WAVE_TRANSFORM_SUPPORTED = !IS_MOBILE
|
export const IS_WAVE_TRANSFORM_SUPPORTED = !IS_MOBILE
|
||||||
&& !IS_FIREFOX // https://bugzilla.mozilla.org/show_bug.cgi?id=1808785
|
&& !IS_FIREFOX // https://bugzilla.mozilla.org/show_bug.cgi?id=1808785
|
||||||
&& !IS_SAFARI; // https://bugs.webkit.org/show_bug.cgi?id=245510
|
&& !IS_SAFARI; // https://bugs.webkit.org/show_bug.cgi?id=245510
|
||||||
|
export const IS_SNAP_EFFECT_SUPPORTED = !IS_MOBILE
|
||||||
|
&& !IS_FIREFOX // https://bugzilla.mozilla.org/show_bug.cgi?id=1896504
|
||||||
|
&& !IS_SAFARI;
|
||||||
|
|
||||||
const TEST_VIDEO = document.createElement('video');
|
const TEST_VIDEO = document.createElement('video');
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user