DropTarget: Correctly display background image animation in Safari (#5053)
Co-authored-by: zubiden <19638254+zubiden@users.noreply.github.com>
This commit is contained in:
parent
66ec9f5732
commit
f2def08a13
@ -136,56 +136,71 @@
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.drop-target {
|
||||
.dropTarget {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
border-radius: var(--border-radius-default);
|
||||
pointer-events: none;
|
||||
.dropOutlineContainer {
|
||||
pointer-events: none;
|
||||
overflow: visible !important;
|
||||
|
||||
opacity: 0;
|
||||
transition: 250ms opacity;
|
||||
z-index: 1;
|
||||
}
|
||||
position: absolute;
|
||||
top: 0.5rem;
|
||||
left: 0.5rem;
|
||||
width: calc(100% - 1rem);
|
||||
height: calc(100% - 1rem);
|
||||
|
||||
&::before {
|
||||
background-image: var(--drag-target-border-hovered);
|
||||
background-color: var(--color-background);
|
||||
}
|
||||
background-color: var(--color-background);
|
||||
|
||||
&::after {
|
||||
content: attr(data-attach-description);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.dropOutline {
|
||||
fill: none;
|
||||
|
||||
stroke: var(--color-primary);
|
||||
stroke-width: 2;
|
||||
stroke-dasharray: 11, 8;
|
||||
stroke-linecap: round;
|
||||
stroke-dashoffset: 0;
|
||||
|
||||
animation: outline 0.5s linear infinite;
|
||||
animation-play-state: paused;
|
||||
}
|
||||
|
||||
.hovered {
|
||||
.drop-target::before {
|
||||
opacity: 0.95;
|
||||
.dropOutline {
|
||||
animation-play-state: running;
|
||||
}
|
||||
|
||||
.drop-target::after {
|
||||
.dropOutlineContainer {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.caption-wrapper,
|
||||
.attachments,
|
||||
:global(.input-scroller) {
|
||||
pointer-events: none;
|
||||
.dropTarget {
|
||||
&::after {
|
||||
content: attr(data-attach-description);
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
color: var(--color-primary);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.caption-wrapper,
|
||||
.attachments,
|
||||
:global(.input-scroller),
|
||||
:global(.custom-scroll),
|
||||
:global(.custom-scroll-x) {
|
||||
pointer-events: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -212,3 +227,12 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes outline {
|
||||
from {
|
||||
stroke-dashoffset: 0;
|
||||
}
|
||||
to {
|
||||
stroke-dashoffset: -19;
|
||||
}
|
||||
}
|
||||
|
||||
@ -33,11 +33,13 @@ import { getHtmlTextLength } from './helpers/getHtmlTextLength';
|
||||
import useAppLayout from '../../../hooks/useAppLayout';
|
||||
import useContextMenuHandlers from '../../../hooks/useContextMenuHandlers';
|
||||
import useDerivedState from '../../../hooks/useDerivedState';
|
||||
import useEffectOnce from '../../../hooks/useEffectOnce';
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
import useGetSelectionRange from '../../../hooks/useGetSelectionRange';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
import useOldLang from '../../../hooks/useOldLang';
|
||||
import usePreviousDeprecated from '../../../hooks/usePreviousDeprecated';
|
||||
import useResizeObserver from '../../../hooks/useResizeObserver';
|
||||
import useScrolledState from '../../../hooks/useScrolledState';
|
||||
import useCustomEmojiTooltip from './hooks/useCustomEmojiTooltip';
|
||||
import useEmojiTooltip from './hooks/useEmojiTooltip';
|
||||
@ -139,6 +141,10 @@ const AttachmentModal: FC<OwnProps & StateProps> = ({
|
||||
onRemoveSymbol,
|
||||
onEmojiSelect,
|
||||
}) => {
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const svgRef = useRef<SVGSVGElement>(null);
|
||||
const { addRecentCustomEmoji, addRecentEmoji, updateAttachmentSettings } = getActions();
|
||||
|
||||
const lang = useOldLang();
|
||||
@ -388,6 +394,23 @@ const AttachmentModal: FC<OwnProps & StateProps> = ({
|
||||
}));
|
||||
});
|
||||
|
||||
const handleResize = useLastCallback(() => {
|
||||
const svg = svgRef.current;
|
||||
if (!svg) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get inner width, without padding
|
||||
const { width, height } = svg.getBoundingClientRect();
|
||||
svg.viewBox.baseVal.width = width;
|
||||
svg.viewBox.baseVal.height = height;
|
||||
});
|
||||
|
||||
// Can't listen for SVG resize
|
||||
useResizeObserver(ref, handleResize);
|
||||
|
||||
useEffectOnce(handleResize);
|
||||
|
||||
useEffect(() => {
|
||||
const mainButton = mainButtonRef.current;
|
||||
const input = document.getElementById(ATTACHMENT_MODAL_INPUT_ID);
|
||||
@ -575,6 +598,9 @@ const AttachmentModal: FC<OwnProps & StateProps> = ({
|
||||
data-attach-description={lang('Preview.Dragging.AddItems', 10)}
|
||||
data-dropzone
|
||||
>
|
||||
<svg className={styles.dropOutlineContainer}>
|
||||
<rect className={styles.dropOutline} x="0" y="0" width="100%" height="100%" rx="8" />
|
||||
</svg>
|
||||
<div
|
||||
className={buildClassName(
|
||||
styles.attachments,
|
||||
|
||||
@ -8,6 +8,8 @@
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-bottom: 0.3125rem;
|
||||
|
||||
position: relative;
|
||||
display: flex;
|
||||
color: #a4acb3;
|
||||
box-shadow: 0 1px 2px var(--color-default-shadow);
|
||||
@ -16,9 +18,15 @@
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
&.hovered .target-content {
|
||||
color: var(--color-primary);
|
||||
background-image: var(--drag-target-border-hovered);
|
||||
&.hovered {
|
||||
.target-content {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.target-outline {
|
||||
stroke: var(--color-primary);
|
||||
animation-play-state: running;
|
||||
}
|
||||
}
|
||||
|
||||
& + & {
|
||||
@ -29,16 +37,41 @@
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.target-outline-container {
|
||||
pointer-events: none;
|
||||
overflow: visible;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.target-outline {
|
||||
fill: none;
|
||||
|
||||
stroke: var(--color-placeholders);
|
||||
stroke-width: 2;
|
||||
stroke-dasharray: 11, 8;
|
||||
stroke-linecap: round;
|
||||
stroke-dashoffset: 0;
|
||||
|
||||
transition: 0.2s stroke;
|
||||
animation: outline 0.5s linear infinite;
|
||||
animation-play-state: paused;
|
||||
}
|
||||
|
||||
.target-content {
|
||||
pointer-events: none;
|
||||
background-image: var(--drag-target-border);
|
||||
border-radius: 0.5rem;
|
||||
flex: 1 1 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: 0.2s color, 0.2s background-image;
|
||||
transition: 0.2s color;
|
||||
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.icon {
|
||||
@ -78,4 +111,13 @@
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes outline {
|
||||
from {
|
||||
stroke-dashoffset: 0;
|
||||
}
|
||||
to {
|
||||
stroke-dashoffset: -19;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,9 +1,12 @@
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
import React, { memo } from '../../../lib/teact/teact';
|
||||
import React, { memo, useRef } from '../../../lib/teact/teact';
|
||||
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
|
||||
import useEffectOnce from '../../../hooks/useEffectOnce';
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
import useResizeObserver from '../../../hooks/useResizeObserver';
|
||||
|
||||
import './DropTarget.scss';
|
||||
|
||||
@ -14,9 +17,14 @@ export type OwnProps = {
|
||||
};
|
||||
|
||||
const DropTarget: FC<OwnProps> = ({ isQuick, isGeneric, onFileSelect }) => {
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const svgRef = useRef<SVGSVGElement>(null);
|
||||
|
||||
const [isHovered, markHovered, unmarkHovered] = useFlag();
|
||||
|
||||
const handleDragLeave = (e: React.DragEvent<HTMLDivElement>) => {
|
||||
const handleDragLeave = useLastCallback((e: React.DragEvent<HTMLDivElement>) => {
|
||||
const { relatedTarget: toTarget } = e;
|
||||
|
||||
if (toTarget) {
|
||||
@ -24,7 +32,24 @@ const DropTarget: FC<OwnProps> = ({ isQuick, isGeneric, onFileSelect }) => {
|
||||
}
|
||||
|
||||
unmarkHovered();
|
||||
};
|
||||
});
|
||||
|
||||
const handleResize = useLastCallback(() => {
|
||||
const svg = svgRef.current;
|
||||
if (!svg) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get inner width, without padding
|
||||
const { width, height } = svg.getBoundingClientRect();
|
||||
svg.viewBox.baseVal.width = width;
|
||||
svg.viewBox.baseVal.height = height;
|
||||
});
|
||||
|
||||
// Can't listen for SVG resize
|
||||
useResizeObserver(ref, handleResize);
|
||||
|
||||
useEffectOnce(handleResize);
|
||||
|
||||
const className = buildClassName(
|
||||
'DropTarget',
|
||||
@ -34,11 +59,15 @@ const DropTarget: FC<OwnProps> = ({ isQuick, isGeneric, onFileSelect }) => {
|
||||
return (
|
||||
<div
|
||||
className={className}
|
||||
ref={ref}
|
||||
onDrop={onFileSelect}
|
||||
onDragEnter={markHovered}
|
||||
onDragLeave={handleDragLeave}
|
||||
data-dropzone
|
||||
>
|
||||
<svg className="target-outline-container">
|
||||
<rect className="target-outline" x="0" y="0" width="100%" height="100%" rx="8" />
|
||||
</svg>
|
||||
<div className="target-content">
|
||||
<div className={`icon icon-${isQuick ? 'photo' : 'document'}`} />
|
||||
<div className="title">Drop files here to send them</div>
|
||||
|
||||
@ -286,9 +286,6 @@ $color-message-story-mention-to: #74bcff;
|
||||
--spinner-gray-data: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZD0iTTEwLjggMjIuNEM2IDIxLjkgMi4xIDE4IDEuNiAxMy4yLjkgNy4xIDUuNCAxLjkgMTEuMyAxLjVjLjQgMCAuNy0uMy43LS43IDAtLjQtLjQtLjgtLjgtLjhDNC44LjQtLjIgNS45IDAgMTIuNS4yIDE4LjYgNS40IDIzLjggMTEuNSAyNGM2LjYuMiAxMi00LjggMTIuNC0xMS4yIDAtLjQtLjMtLjgtLjgtLjgtLjQgMC0uNy4zLS43LjctLjMgNS45LTUuNSAxMC40LTExLjYgOS43eiIgZmlsbD0iIzcwNzU3OSIvPjwvc3ZnPg==);
|
||||
--spinner-yellow-data: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZD0iTTEwLjggMjIuNEM2IDIxLjkgMi4xIDE4IDEuNiAxMy4yLjkgNy4xIDUuNCAxLjkgMTEuMyAxLjVjLjQgMCAuNy0uMy43LS43IDAtLjQtLjQtLjgtLjgtLjhDNC44LjQtLjIgNS45IDAgMTIuNS4yIDE4LjYgNS40IDIzLjggMTEuNSAyNGM2LjYuMiAxMi00LjggMTIuNC0xMS4yIDAtLjQtLjMtLjgtLjgtLjgtLjQgMC0uNy4zLS43LjctLjMgNS45LTUuNSAxMC40LTExLjYgOS43eiIgZmlsbD0iI0ZERDc2NCIvPjwvc3ZnPg==);
|
||||
|
||||
--drag-target-border: url("data:image/svg+xml,%3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='100%25' height='100%25' fill='none' rx='8' ry='8' stroke='%23DDDFE0' stroke-width='4' stroke-dasharray='9.1%2c 10.5' stroke-dashoffset='3' stroke-linecap='round'/%3e%3c/svg%3e");
|
||||
--drag-target-border-hovered: url("data:image/svg+xml,%3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='100%25' height='100%25' fill='none' rx='8' ry='8' stroke='%2363A2E3' stroke-width='4' stroke-dasharray='9.1%2c 10.5' stroke-dashoffset='3' stroke-linecap='round'/%3e%3c/svg%3e");
|
||||
|
||||
--premium-gradient: linear-gradient(84.4deg, #6C93FF -4.85%, #976FFF 51.72%, #DF69D1 110.7%);
|
||||
|
||||
--layer-blackout-opacity: 0.1;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user