diff --git a/src/components/appMediaViewer.ts b/src/components/appMediaViewer.ts index 0f444134..dcb33cce 100644 --- a/src/components/appMediaViewer.ts +++ b/src/components/appMediaViewer.ts @@ -6,7 +6,7 @@ import MEDIA_MIME_TYPES_SUPPORTED from "../environment/mediaMimeTypesSupport"; import { cancelEvent } from "../helpers/dom/cancelEvent"; -import { attachClickEvent } from "../helpers/dom/clickEvent"; +import { attachClickEvent, detachClickEvent } from "../helpers/dom/clickEvent"; import setInnerHTML from "../helpers/dom/setInnerHTML"; import mediaSizes from "../helpers/mediaSizes"; import SearchListLoader from "../helpers/searchListLoader"; @@ -123,8 +123,8 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet protected setListeners() { super.setListeners(); - this.buttons.forward.addEventListener('click', this.onForwardClick); - this.author.container.addEventListener('click', this.onAuthorClick); + attachClickEvent(this.buttons.forward, this.onForwardClick); + attachClickEvent(this.author.container, this.onAuthorClick); const onCaptionClick = (e: MouseEvent) => { if(e.target instanceof HTMLAnchorElement) { // close viewer if it's t.me/ redirect @@ -136,14 +136,15 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet cancelEvent(e); this.close().then(() => { - this.content.caption.removeEventListener('click', onCaptionClick, {capture: true}); + detachClickEvent(this.content.caption, onCaptionClick, {capture: true}); (e.target as HTMLAnchorElement).click(); }); return false; } }; - this.content.caption.addEventListener('click', onCaptionClick, {capture: true}); + + attachClickEvent(this.content.caption, onCaptionClick, {capture: true}); } /* public close(e?: MouseEvent) { diff --git a/src/components/appMediaViewerBase.ts b/src/components/appMediaViewerBase.ts index 53a89c2e..e35e8339 100644 --- a/src/components/appMediaViewerBase.ts +++ b/src/components/appMediaViewerBase.ts @@ -43,6 +43,7 @@ import { MyMessage } from "../lib/appManagers/appMessagesManager"; import RichTextProcessor from "../lib/richtextprocessor"; import { NULL_PEER_ID } from "../lib/mtproto/mtproto_config"; import { isFullScreen } from "../helpers/dom/fullScreen"; +import { attachClickEvent } from "../helpers/dom/clickEvent"; const ZOOM_STEP = 0.5; const ZOOM_INITIAL_VALUE = 1; @@ -190,9 +191,9 @@ export default class AppMediaViewerBase< this.zoomElements.container.classList.add('zoom-container'); this.zoomElements.btnOut = ButtonIcon('zoomout', {noRipple: true}); - this.zoomElements.btnOut.addEventListener('click', () => this.changeZoom(false)); + attachClickEvent(this.zoomElements.btnOut, () => this.changeZoom(false)); this.zoomElements.btnIn = ButtonIcon('zoomin', {noRipple: true}); - this.zoomElements.btnIn.addEventListener('click', () => this.changeZoom(true)); + attachClickEvent(this.zoomElements.btnIn, () => this.changeZoom(true)); this.zoomElements.rangeSelector = new RangeSelector({ step: ZOOM_STEP, @@ -254,13 +255,13 @@ export default class AppMediaViewerBase< } protected setListeners() { - this.buttons.download.addEventListener('click', this.onDownloadClick); + attachClickEvent(this.buttons.download, this.onDownloadClick); [this.buttons.close, this.buttons['mobile-close'], this.preloaderStreamable.preloader].forEach(el => { - el.addEventListener('click', this.close.bind(this)); + attachClickEvent(el, this.close.bind(this)); }); ([[-1, this.buttons.prev], [1, this.buttons.next]] as [number, HTMLElement][]).forEach(([moveLength, button]) => { - button.addEventListener('click', (e) => { + attachClickEvent(button, (e) => { cancelEvent(e); if(this.setMoverPromise) return; @@ -268,14 +269,14 @@ export default class AppMediaViewerBase< }); }); - this.buttons.zoom.addEventListener('click', () => { + attachClickEvent(this.buttons.zoom, () => { if(this.isZooming()) this.toggleZoom(false); else { this.changeZoom(true); } }); - this.wholeDiv.addEventListener('click', this.onClick); + attachClickEvent(this.wholeDiv, this.onClick); this.listLoader.onJump = (item, older) => { if(older) this.onNextClick(item); @@ -1359,7 +1360,14 @@ export default class AppMediaViewerBase< // const play = useController ? appMediaPlaybackController.willBePlayedMedia === video : true; const play = true; - const player = this.videoPlayer = new VideoPlayer(video, play, supportsStreaming); + const player = this.videoPlayer = new VideoPlayer({ + video, + play, + streamable: supportsStreaming, + onPlaybackRackMenuToggle: (open) => { + this.wholeDiv.classList.toggle('hide-caption', !!open); + } + }); player.addEventListener('toggleControls', (show) => { this.wholeDiv.classList.toggle('has-video-controls', show); }); diff --git a/src/components/appSearchSuper..ts b/src/components/appSearchSuper..ts index f8d91e5c..2662edc4 100644 --- a/src/components/appSearchSuper..ts +++ b/src/components/appSearchSuper..ts @@ -1100,7 +1100,7 @@ export default class AppSearchSuper { }); showMore.append(intlElement.element); this.searchGroups.globalContacts.nameEl.append(showMore); - showMore.addEventListener('click', () => { + attachClickEvent(showMore, () => { const isShort = this.searchGroups.globalContacts.container.classList.toggle('is-short'); intlElement.key = isShort ? 'Separator.ShowMore' : 'Separator.ShowLess'; intlElement.update(); @@ -1194,7 +1194,7 @@ export default class AppSearchSuper { if(!this.membersList) { this.membersList = new SortedUserList({lazyLoadQueue: this.lazyLoadQueue, rippleEnabled: false}); - this.membersList.list.addEventListener('click', (e) => { + attachClickEvent(this.membersList.list, (e) => { const li = findUpTag(e.target, 'LI'); if(!li) { return; diff --git a/src/components/appSelectPeers.ts b/src/components/appSelectPeers.ts index 9f679415..03e43f9e 100644 --- a/src/components/appSelectPeers.ts +++ b/src/components/appSelectPeers.ts @@ -25,6 +25,7 @@ import debounce from "../helpers/schedulers/debounce"; import windowSize from "../helpers/windowSize"; import appPeersManager, { IsPeerType } from "../lib/appManagers/appPeersManager"; import { generateDelimiter, SettingSection } from "./sidebarLeft"; +import { attachClickEvent } from "../helpers/dom/clickEvent"; type SelectSearchPeerType = 'contacts' | 'dialogs' | 'channelParticipants'; @@ -157,7 +158,7 @@ export default class AppSelectPeers { // let delimiter = document.createElement('hr'); - this.selectedContainer.addEventListener('click', (e) => { + attachClickEvent(this.selectedContainer, (e) => { if(this.freezed) return; let target = e.target as HTMLElement; target = findUpClassName(target, 'selector-user'); @@ -188,7 +189,7 @@ export default class AppSelectPeers { this.scrollable = new Scrollable(this.chatsContainer); this.scrollable.setVirtualContainer(this.list); - this.chatsContainer.addEventListener('click', (e) => { + attachClickEvent(this.chatsContainer, (e) => { const target = findUpAttribute(e.target, 'data-peer-id') as HTMLElement; cancelEvent(e); diff --git a/src/components/avatarEdit.ts b/src/components/avatarEdit.ts index f5a9fa69..0ff64ca6 100644 --- a/src/components/avatarEdit.ts +++ b/src/components/avatarEdit.ts @@ -5,6 +5,7 @@ */ import type { CancellablePromise } from "../helpers/cancellablePromise"; +import { attachClickEvent } from "../helpers/dom/clickEvent"; import type { InputFile } from "../layer"; import PopupAvatar from "./popups/avatar"; @@ -25,7 +26,7 @@ export default class AvatarEdit { this.container.append(this.canvas, this.icon); - this.container.addEventListener('click', () => { + attachClickEvent(this.container, () => { new PopupAvatar().open(this.canvas, onChange); }); } diff --git a/src/components/buttonMenuToggle.ts b/src/components/buttonMenuToggle.ts index e1d2af23..4dcfbd2d 100644 --- a/src/components/buttonMenuToggle.ts +++ b/src/components/buttonMenuToggle.ts @@ -23,7 +23,7 @@ const ButtonMenuToggle = (options: Partial<{noRipple: true, onlyMobile: true, li }; // TODO: refactor for attachClickEvent, because if move finger after touchstart, it will start anyway -const ButtonMenuToggleHandler = (el: HTMLElement, onOpen?: (e: Event) => void, options?: AttachClickOptions) => { +const ButtonMenuToggleHandler = (el: HTMLElement, onOpen?: (e: Event) => void, options?: AttachClickOptions, onClose?: () => void) => { const add = options?.listenerSetter ? options.listenerSetter.add(el) : el.addEventListener.bind(el); //console.trace('ButtonMenuToggleHandler attach', el, onOpen, options); @@ -39,7 +39,7 @@ const ButtonMenuToggleHandler = (el: HTMLElement, onOpen?: (e: Event) => void, o closeBtnMenu(); } else { onOpen && onOpen(e); - openBtnMenu(openedMenu); + openBtnMenu(openedMenu, onClose); } }); }; diff --git a/src/components/chat/bubbles.ts b/src/components/chat/bubbles.ts index 79ed0458..d0eede85 100644 --- a/src/components/chat/bubbles.ts +++ b/src/components/chat/bubbles.ts @@ -522,9 +522,6 @@ export default class ChatBubbles { }); }); - // attachClickEvent(this.bubblesContainer, this.onBubblesClick, {listenerSetter: this.listenerSetter}); - this.listenerSetter.add(this.bubblesContainer)('click', this.onBubblesClick/* , {capture: true, passive: false} */); - if(IS_TOUCH_SUPPORTED) { const className = 'is-gesturing-reply'; const MAX = 64; @@ -593,6 +590,9 @@ export default class ChatBubbles { }); } + attachClickEvent(this.bubblesContainer, this.onBubblesClick, {listenerSetter: this.listenerSetter}); + // this.listenerSetter.add(this.bubblesContainer)('click', this.onBubblesClick/* , {capture: true, passive: false} */); + if(DEBUG) { this.listenerSetter.add(this.bubblesContainer)('dblclick', (e) => { const bubble = findUpClassName(e.target, 'grouped-item') || findUpClassName(e.target, 'bubble'); diff --git a/src/components/chat/chat.ts b/src/components/chat/chat.ts index 50dc76a5..3428128b 100644 --- a/src/components/chat/chat.ts +++ b/src/components/chat/chat.ts @@ -42,6 +42,7 @@ import AppPrivateSearchTab from "../sidebarRight/tabs/search"; import renderImageFromUrl from "../../helpers/dom/renderImageFromUrl"; import mediaSizes from "../../helpers/mediaSizes"; import ChatSearch from "./search"; +import { IS_TOUCH_SUPPORTED } from "../../environment/touchSupport"; export type ChatType = 'chat' | 'pinned' | 'replies' | 'discussion' | 'scheduled'; @@ -216,7 +217,7 @@ export default class Chat extends EventListenerBase<{ this.input.constructPeerHelpers(); } - if(this.type !== 'scheduled') { + if(this.type !== 'scheduled' && !IS_TOUCH_SUPPORTED) { this.bubbles.setReactionsHoverListeners(); } diff --git a/src/components/connectionStatus.ts b/src/components/connectionStatus.ts index 5bf1a91f..cd7242b9 100644 --- a/src/components/connectionStatus.ts +++ b/src/components/connectionStatus.ts @@ -18,6 +18,7 @@ import sessionStorage from '../lib/sessionStorage'; import { ConnectionStatus } from "../lib/mtproto/connectionStatus"; import { cancelEvent } from "../helpers/dom/cancelEvent"; import apiManager from "../lib/mtproto/mtprotoworker"; +import { attachClickEvent } from "../helpers/dom/clickEvent"; export default class ConnectionStatusComponent { public static CHANGE_STATE_DELAY = 1000; @@ -133,7 +134,7 @@ export default class ConnectionStatusComponent { const a = document.createElement('a'); a.classList.add('force-reconnect'); a.append(i18n(langPackKey)); - a.addEventListener('click', (e) => { + attachClickEvent(a, (e) => { cancelEvent(e); callback(); }); diff --git a/src/helpers/dom/clickEvent.ts b/src/helpers/dom/clickEvent.ts index d60dbab0..eee456ac 100644 --- a/src/helpers/dom/clickEvent.ts +++ b/src/helpers/dom/clickEvent.ts @@ -45,7 +45,7 @@ export function attachClickEvent(elem: HTMLElement | Window, callback: (e: /* To add(CLICK_EVENT_NAME, callback, options); } -export function detachClickEvent(elem: HTMLElement, callback: (e: TouchEvent | MouseEvent) => void, options?: AddEventListenerOptions) { +export function detachClickEvent(elem: HTMLElement, callback: (e: /* TouchEvent | */MouseEvent) => void, options?: AddEventListenerOptions) { // if(CLICK_EVENT_NAME === 'touchend') { // elem.removeEventListener('touchstart', callback, options); // } else { diff --git a/src/helpers/dom/controlsHover.ts b/src/helpers/dom/controlsHover.ts index 89fd704b..6fc37854 100644 --- a/src/helpers/dom/controlsHover.ts +++ b/src/helpers/dom/controlsHover.ts @@ -20,6 +20,7 @@ export default class ControlsHover extends EventListenerBase<{ protected element: HTMLElement; protected listenerSetter: ListenerSetter; protected showOnLeaveToClassName: string; + protected ignoreClickClassName: string; constructor() { super(false); @@ -30,14 +31,19 @@ export default class ControlsHover extends EventListenerBase<{ element: HTMLElement, listenerSetter: ListenerSetter, canHideControls?: () => boolean, - showOnLeaveToClassName?: string + showOnLeaveToClassName?: string, + ignoreClickClassName?: string }) { safeAssign(this, options); const {listenerSetter, element} = this; if(IS_TOUCH_SUPPORTED) { - listenerSetter.add(element)('click', () => { + listenerSetter.add(element)('click', (e) => { + if(this.ignoreClickClassName && findUpClassName(e.target, this.ignoreClickClassName)) { + return; + } + this.toggleControls(); }); diff --git a/src/lib/mediaPlayer.ts b/src/lib/mediaPlayer.ts index 362c7359..5597a362 100644 --- a/src/lib/mediaPlayer.ts +++ b/src/lib/mediaPlayer.ts @@ -266,6 +266,7 @@ export default class VideoPlayer extends ControlsHover { private static PLAYBACK_RATES = [0.5, 1, 1.5, 2]; private static PLAYBACK_RATES_ICONS = ['playback_05', 'playback_1x', 'playback_15', 'playback_2x']; + protected video: HTMLVideoElement; protected wrapper: HTMLDivElement; protected progress: MediaProgressLine; protected skin: 'default'; @@ -276,12 +277,23 @@ export default class VideoPlayer extends ControlsHover { /* protected videoParent: HTMLElement; protected videoWhichChild: number; */ - constructor(protected video: HTMLVideoElement, play = false, streamable = false, duration?: number) { + protected onPlaybackRackMenuToggle?: (open: boolean) => void; + + constructor({video, play = false, streamable = false, duration, onPlaybackRackMenuToggle}: { + video: HTMLVideoElement, + play?: boolean, + streamable?: boolean, + duration?: number, + onPlaybackRackMenuToggle?: (open: boolean) => void + }) { super(); + this.video = video; this.wrapper = document.createElement('div'); this.wrapper.classList.add('ckin__player'); + this.onPlaybackRackMenuToggle = onPlaybackRackMenuToggle; + this.listenerSetter = new ListenerSetter(); this.setup({ @@ -290,7 +302,8 @@ export default class VideoPlayer extends ControlsHover { canHideControls: () => { return !this.video.paused && (!this.playbackRateButton || !this.playbackRateButton.classList.contains('menu-open')); }, - showOnLeaveToClassName: 'media-viewer-caption' + showOnLeaveToClassName: 'media-viewer-caption', + ignoreClickClassName: 'ckin__controls' }); video.parentNode.insertBefore(this.wrapper, video); @@ -423,9 +436,11 @@ export default class VideoPlayer extends ControlsHover { listenerSetter.add(video)('play', () => { wrapper.classList.add('played'); - listenerSetter.add(video)('play', () => { - this.hideControls(true); - }); + if(!IS_TOUCH_SUPPORTED) { + listenerSetter.add(video)('play', () => { + this.hideControls(true); + }); + } }, {once: true}); listenerSetter.add(video)('pause', () => { @@ -495,7 +510,16 @@ export default class VideoPlayer extends ControlsHover { }); const btnMenu = ButtonMenu(buttons); btnMenu.classList.add('top-left'); - ButtonMenuToggleHandler(this.playbackRateButton); + ButtonMenuToggleHandler( + this.playbackRateButton, + this.onPlaybackRackMenuToggle ? () => { + this.onPlaybackRackMenuToggle(true); + } : undefined, + undefined, + this.onPlaybackRackMenuToggle ? () => { + this.onPlaybackRackMenuToggle(false); + } : undefined + ); this.playbackRateButton.append(btnMenu); this.setPlaybackRateIcon(); @@ -572,5 +596,6 @@ export default class VideoPlayer extends ControlsHover { super.cleanup(); this.listenerSetter.removeAll(); this.progress.removeListeners(); + this.onPlaybackRackMenuToggle = undefined; } } diff --git a/src/scss/partials/_ckin.scss b/src/scss/partials/_ckin.scss index 25f757d1..d157957f 100644 --- a/src/scss/partials/_ckin.scss +++ b/src/scss/partials/_ckin.scss @@ -92,7 +92,10 @@ left: 50%; transform: translate3d(-50%, -50%, 0) scale(1); font-size: 4rem; - pointer-events: none; + + @include respond-to(not-handhelds) { + pointer-events: none; + } @include animation-level(2) { transition: visibility var(--layer-transition), opacity var(--layer-transition); diff --git a/src/scss/partials/_mediaViewer.scss b/src/scss/partials/_mediaViewer.scss index 9eaee2e2..f950149c 100644 --- a/src/scss/partials/_mediaViewer.scss +++ b/src/scss/partials/_mediaViewer.scss @@ -533,7 +533,7 @@ $inactive-opacity: .4; transition: opacity var(--open-duration) 0s, visibility 0s var(--open-duration); } */ - .btn-menu-toggle { + .btn-menu-toggle:not(.playback-rate) { color: rgba(255, 255, 255, $inactive-opacity); opacity: 1; @@ -542,6 +542,13 @@ $inactive-opacity: .4; background-color: rgba(112, 117, 121, .2) !important; } } + + &.hide-caption { + .media-viewer-caption { + opacity: 0 !important; + pointer-events: none; + } + } } &.highlight-switchers { diff --git a/src/scss/partials/_reaction.scss b/src/scss/partials/_reaction.scss index c7ab70e9..8dbf8bfb 100644 --- a/src/scss/partials/_reaction.scss +++ b/src/scss/partials/_reaction.scss @@ -44,7 +44,7 @@ &.has-animation { > .media-sticker { - visibility: hidden; + opacity: 0; } }