diff --git a/src/components/appNavigationController.ts b/src/components/appNavigationController.ts index d0ee48be..c2b7c0fd 100644 --- a/src/components/appNavigationController.ts +++ b/src/components/appNavigationController.ts @@ -1,6 +1,8 @@ import { MOUNT_CLASS_TO } from "../config/debug"; -import { isSafari, isAppleMobile } from "../helpers/userAgent"; +import { isMobileSafari } from "../helpers/userAgent"; import { cancelEvent } from "../helpers/dom"; +import { logger } from "../lib/logger"; +import { doubleRaf } from "../helpers/schedulers"; export type NavigationItem = { type: 'left' | 'right' | 'im' | 'chat' | 'popup' | 'media' | 'menu' | 'esg', @@ -12,10 +14,14 @@ export type NavigationItem = { export class AppNavigationController { private navigations: Array = []; private id = Date.now(); + private manual = false; + private log = logger('NC'); + private debug = true; constructor() { + let isPossibleSwipe = false; window.addEventListener('popstate', (e) => { - console.log('popstate', e); + this.debug && this.log('popstate', e, isPossibleSwipe); const id: number = e.state; if(id !== this.id) { @@ -29,12 +35,8 @@ export class AppNavigationController { return; } - const good = item.onPop(isSafari && isAppleMobile ? false : undefined); - console.log('[NC]: popstate, navigation:', item, this.navigations); - if(good === false) { - this.pushItem(item); - } - + this.manual = !isPossibleSwipe; + this.handleItem(item); //this.pushState(); // * prevent adding forward arrow }); @@ -47,16 +49,62 @@ export class AppNavigationController { } }, {capture: true}); + if(isMobileSafari) { + /* window.addEventListener('touchstart', (e) => { + this.debug && this.log('touchstart'); + }, {passive: true}); */ + + window.addEventListener('touchend', (e) => { + this.debug && this.log('touchend'); + if(e.touches.length > 1) return; + isPossibleSwipe = true; + doubleRaf().then(() => { + isPossibleSwipe = false; + }); + }, {passive: true}); + } + this.pushState(); // * push init state } - public back() { + private handleItem(item: NavigationItem) { + const good = item.onPop(!this.manual ? false : undefined); + this.debug && this.log('popstate, navigation:', item, this.navigations); + if(good === false) { + this.pushItem(item); + } + + this.manual = false; + } + + public back(type?: NavigationItem['type']) { + if(type) { + let item: NavigationItem; + let i = this.navigations.length - 1; + for(; i >= 0; --i) { + const _item = this.navigations[i]; + if(_item.type === type) { + item = _item; + break; + } + } + + if(item) { + this.manual = true; + if(i !== (this.navigations.length - 1)) { + this.navigations.splice(i, 1); + this.handleItem(item); + return; + } + } + } + history.back(); } public pushItem(item: NavigationItem) { this.navigations.push(item); - console.log('[NC]: pushstate', item, this.navigations); + this.debug && this.log('pushstate', item, this.navigations); if(!item.noHistory) { this.pushState(); @@ -64,6 +112,7 @@ export class AppNavigationController { } private pushState() { + this.manual = false; history.pushState(this.id, ''); } @@ -74,6 +123,19 @@ export class AppNavigationController { public removeItem(item: NavigationItem) { this.navigations.findAndSplice(i => i === item); } + + public removeByType(type: NavigationItem['type'], single = false) { + for(let i = this.navigations.length - 1; i >= 0; --i) { + const item = this.navigations[i]; + if(item.type === type) { + this.navigations.splice(i, 1); + + if(single) { + break; + } + } + } + } } const appNavigationController = new AppNavigationController(); diff --git a/src/components/chat/topbar.ts b/src/components/chat/topbar.ts index ce6146e7..60a2a0c6 100644 --- a/src/components/chat/topbar.ts +++ b/src/components/chat/topbar.ts @@ -144,7 +144,7 @@ export default class ChatTopbar { cancelEvent(e); /* this.chat.appImManager.setPeer(0); blurActiveElement(); */ - appNavigationController.back(); + appNavigationController.back('chat'); }, {listenerSetter: this.listenerSetter}); } diff --git a/src/components/misc.ts b/src/components/misc.ts index a727ae29..5c3cf6a7 100644 --- a/src/components/misc.ts +++ b/src/components/misc.ts @@ -4,7 +4,8 @@ import { cancelEvent, CLICK_EVENT_NAME } from "../helpers/dom"; import ListenerSetter from "../helpers/listenerSetter"; import mediaSizes from "../helpers/mediaSizes"; import { isTouchSupported } from "../helpers/touchSupport"; -import { isApple } from "../helpers/userAgent"; +import { isApple, isMobileSafari } from "../helpers/userAgent"; +import appNavigationController from "./appNavigationController"; export const loadedURLs: {[url: string]: boolean} = {}; const set = (elem: HTMLElement | HTMLImageElement | SVGImageElement | HTMLVideoElement, url: string) => { @@ -136,12 +137,13 @@ const onClick = (e: MouseEvent | TouchEvent) => { closeBtnMenu(); }; -const onKeyDown = (e: KeyboardEvent) => { +// ! no need in this due to the same handler in appNavigationController +/* const onKeyDown = (e: KeyboardEvent) => { if(e.key === 'Escape') { closeBtnMenu(); cancelEvent(e); } -}; +}; */ export const closeBtnMenu = () => { if(openedMenu) { @@ -159,11 +161,15 @@ export const closeBtnMenu = () => { if(!isTouchSupported) { window.removeEventListener('mousemove', onMouseMove); - window.removeEventListener('keydown', onKeyDown, {capture: true}); + //window.removeEventListener('keydown', onKeyDown, {capture: true}); window.removeEventListener('contextmenu', onClick); } document.removeEventListener(CLICK_EVENT_NAME, onClick); + + if(!isMobileSafari) { + appNavigationController.removeByType('menu'); + } }; window.addEventListener('resize', () => { @@ -182,6 +188,15 @@ window.addEventListener('resize', () => { let openedMenu: HTMLElement = null, openedMenuOnClose: () => void = null, menuOverlay: HTMLElement = null; export function openBtnMenu(menuElement: HTMLElement, onClose?: () => void) { closeBtnMenu(); + + if(!isMobileSafari) { + appNavigationController.pushItem({ + type: 'menu', + onPop: (canAnimate) => { + closeBtnMenu(); + } + }); + } openedMenu = menuElement; openedMenu.classList.add('active'); @@ -206,7 +221,7 @@ export function openBtnMenu(menuElement: HTMLElement, onClose?: () => void) { if(!isTouchSupported) { window.addEventListener('mousemove', onMouseMove); - window.addEventListener('keydown', onKeyDown, {capture: true}); + //window.addEventListener('keydown', onKeyDown, {capture: true}); window.addEventListener('contextmenu', onClick, {once: true}); } diff --git a/src/components/popups/datePicker.ts b/src/components/popups/datePicker.ts index a9ca1bfa..dcf6a08e 100644 --- a/src/components/popups/datePicker.ts +++ b/src/components/popups/datePicker.ts @@ -145,6 +145,9 @@ export default class PopupDatePicker extends PopupElement { }, {once: true}); this.body.append(this.timeDiv); + + this.prevBtn.classList.add('primary'); + this.nextBtn.classList.add('primary'); } const popupCenterer = document.createElement('div'); diff --git a/src/components/popups/index.ts b/src/components/popups/index.ts index ebe122cc..512079de 100644 --- a/src/components/popups/index.ts +++ b/src/components/popups/index.ts @@ -75,9 +75,9 @@ export default class PopupElement { const buttonsElements = buttons.map(b => { const button = document.createElement('button'); - button.className = 'btn' + (b.isDanger ? ' danger' : ''); + button.className = 'btn' + (b.isDanger ? ' danger' : ' primary'); button.innerHTML = b.text; - //ripple(button); + ripple(button); if(b.callback) { button.addEventListener('click', () => { @@ -118,7 +118,7 @@ export default class PopupElement { } public hide = () => { - appNavigationController.back(); + appNavigationController.back('popup'); }; private destroy = () => { diff --git a/src/components/ripple.ts b/src/components/ripple.ts index 235e0cfa..55f65926 100644 --- a/src/components/ripple.ts +++ b/src/components/ripple.ts @@ -168,6 +168,10 @@ export function ripple(elem: HTMLElement, callback: (id: number) => Promise { + if(![0, 2].includes(e.button)) { // only left and right buttons + return; + } + if(!rootScope.settings.animationsEnabled) { return; } diff --git a/src/components/sidebarLeft/index.ts b/src/components/sidebarLeft/index.ts index 4dca30ab..9d7f9b51 100644 --- a/src/components/sidebarLeft/index.ts +++ b/src/components/sidebarLeft/index.ts @@ -417,9 +417,10 @@ export class AppSidebarLeft extends SidebarSlider { transition(0); + const activeClassName = 'is-visible'; const onFocus = () => { - this.toolsBtn.classList.remove('active'); - this.backBtn.classList.add('active'); + this.toolsBtn.classList.remove(activeClassName); + this.backBtn.classList.add(activeClassName); this.newBtnMenu.classList.add('is-hidden'); this.toolsBtn.parentElement.firstElementChild.classList.toggle('state-back', true); @@ -430,8 +431,8 @@ export class AppSidebarLeft extends SidebarSlider { onFocus(); this.backBtn.addEventListener('click', (e) => { - this.toolsBtn.classList.add('active'); - this.backBtn.classList.remove('active'); + this.toolsBtn.classList.add(activeClassName); + this.backBtn.classList.remove(activeClassName); this.toolsBtn.parentElement.firstElementChild.classList.toggle('state-back', false); transition(0); diff --git a/src/components/sidebarLeft/tabs/editFolder.ts b/src/components/sidebarLeft/tabs/editFolder.ts index 5f16cad3..aee48fbd 100644 --- a/src/components/sidebarLeft/tabs/editFolder.ts +++ b/src/components/sidebarLeft/tabs/editFolder.ts @@ -90,7 +90,7 @@ export default class AppEditFolderTab extends SliderSuperTab { categories.classList.add('folder-categories'); buttons.forEach(o => { - const button = Button('folder-category-button btn-primary btn-transparent', { + const button = Button('folder-category-button btn btn-primary btn-transparent', { icon: o.icon, text: o.text, noRipple: o.withRipple ? undefined : true, @@ -110,7 +110,7 @@ export default class AppEditFolderTab extends SliderSuperTab { }; this.include_peers = generateList('folder-list-included', 'Included chats', [{ - icon: 'add blue', + icon: 'add primary', text: 'Add Chats', withRipple: true }, { @@ -136,7 +136,7 @@ export default class AppEditFolderTab extends SliderSuperTab { }], this.flags); this.exclude_peers = generateList('folder-list-excluded', 'Excluded chats', [{ - icon: 'minus blue', + icon: 'minus primary', text: 'Remove Chats', withRipple: true }, { diff --git a/src/components/slider.ts b/src/components/slider.ts index 432e1e3f..03cfa23b 100644 --- a/src/components/slider.ts +++ b/src/components/slider.ts @@ -114,7 +114,7 @@ export default class SidebarSlider { } private onCloseBtnClick = () => { - appNavigationController.back(); + appNavigationController.back(this.navigationType); // this.closeTab(); }; diff --git a/src/helpers/dom.ts b/src/helpers/dom.ts index a6f569bf..4a805c11 100644 --- a/src/helpers/dom.ts +++ b/src/helpers/dom.ts @@ -5,7 +5,7 @@ import { isTouchSupported } from "./touchSupport"; import { isApple } from "./userAgent"; import rootScope from "../lib/rootScope"; import { MOUNT_CLASS_TO } from "../config/debug"; -import { superRaf } from "./schedulers"; +import { doubleRaf } from "./schedulers"; /* export function isInDOM(element: Element, parentNode?: HTMLElement): boolean { if(!element) { @@ -780,7 +780,7 @@ export function isSelectionEmpty(selection = window.getSelection()) { export function disableTransition(elements: HTMLElement[]) { elements.forEach(el => el.classList.add('no-transition')); - superRaf().then(() => { + doubleRaf().then(() => { elements.forEach(el => el.classList.remove('no-transition')); }); } diff --git a/src/helpers/schedulers.ts b/src/helpers/schedulers.ts index 0edc5d7b..b3c75731 100644 --- a/src/helpers/schedulers.ts +++ b/src/helpers/schedulers.ts @@ -124,7 +124,7 @@ export function fastRaf(callback: NoneToVoidFunction) { } } -export function superRaf() { +export function doubleRaf() { return new Promise((resolve) => { window.requestAnimationFrame(() => { window.requestAnimationFrame(resolve); diff --git a/src/index.hbs b/src/index.hbs index 07321ac7..e4c655e1 100644 --- a/src/index.hbs +++ b/src/index.hbs @@ -99,7 +99,7 @@