Eduard Kuzmenko 3 years ago
parent
commit
669cd43978
  1. 62
      src/components/appNavigationController.ts
  2. 6
      src/components/chat/topbar.ts
  3. 5
      src/components/sidebarLeft/index.ts
  4. 53
      src/components/sidebarRight/index.ts
  5. 53
      src/components/slider.ts
  6. 9
      src/helpers/dom.ts
  7. 8
      src/helpers/schedulers.ts
  8. 101
      src/lib/appManagers/appImManager.ts
  9. 1
      src/scss/partials/_leftSidebar.scss
  10. 2
      src/scss/partials/_rightSidebar.scss

62
src/components/appNavigationController.ts

@ -0,0 +1,62 @@ @@ -0,0 +1,62 @@
import { MOUNT_CLASS_TO } from "../config/debug";
import { isSafari } from "../helpers/userAgent";
export type NavigationItem = {
type: 'left' | 'right' | 'im' | 'chat' | 'popup' | 'media' | 'menu' | 'esg',
onPop: (canAnimate: boolean) => boolean | void
};
export class AppNavigationController {
private navigations: Array<NavigationItem> = [];
private id = Date.now();
constructor() {
window.addEventListener('popstate', (e) => {
console.log('popstate', e);
const id: number = e.state;
if(id !== this.id) {
this.pushState();
return;
}
const item = this.navigations.pop();
if(!item) {
this.pushState();
return;
}
const good = item.onPop(isSafari ? false : undefined);
console.log('[NC]: popstate, navigation:', item, this.navigations);
if(good === false) {
this.pushItem(item);
}
//this.pushState(); // * prevent adding forward arrow
});
this.pushState(); // * push init state
}
public back() {
history.back();
}
public pushItem(item: NavigationItem) {
this.navigations.push(item);
console.log('[NC]: pushstate', item, this.navigations);
this.pushState();
}
private pushState() {
history.pushState(this.id, '');
}
public replaceState() {
history.replaceState(this.id, '');
}
}
const appNavigationController = new AppNavigationController();
MOUNT_CLASS_TO && (MOUNT_CLASS_TO.appNavigationController = appNavigationController);
export default appNavigationController;

6
src/components/chat/topbar.ts

@ -18,6 +18,7 @@ import { ButtonMenuItemOptions } from "../buttonMenu"; @@ -18,6 +18,7 @@ import { ButtonMenuItemOptions } from "../buttonMenu";
import ListenerSetter from "../../helpers/listenerSetter";
import appStateManager from "../../lib/appManagers/appStateManager";
import PopupDeleteDialog from "../popups/deleteDialog";
import appNavigationController from "../appNavigationController";
export default class ChatTopbar {
container: HTMLDivElement;
@ -141,8 +142,9 @@ export default class ChatTopbar { @@ -141,8 +142,9 @@ export default class ChatTopbar {
attachClickEvent(this.btnBack, (e) => {
cancelEvent(e);
this.chat.appImManager.setPeer(0);
blurActiveElement();
/* this.chat.appImManager.setPeer(0);
blurActiveElement(); */
appNavigationController.back();
}, {listenerSetter: this.listenerSetter});
}

5
src/components/sidebarLeft/index.ts

@ -82,7 +82,10 @@ export class AppSidebarLeft extends SidebarSlider { @@ -82,7 +82,10 @@ export class AppSidebarLeft extends SidebarSlider {
searchSuper: AppSearchSuper;
constructor() {
super(document.getElementById('column-left') as HTMLDivElement);
super({
sidebarEl: document.getElementById('column-left') as HTMLDivElement,
navigationType: 'left'
});
Object.assign(this.tabs, {
[AppSidebarLeft.SLIDERITEMSIDS.archived]: archivedTab,

53
src/components/sidebarRight/index.ts

@ -6,17 +6,12 @@ import AppGifsTab from "./tabs/gifs"; @@ -6,17 +6,12 @@ import AppGifsTab from "./tabs/gifs";
import mediaSizes, { ScreenSize } from "../../helpers/mediaSizes";
import AppPrivateSearchTab from "./tabs/search";
import AppSharedMediaTab from "./tabs/sharedMedia";
//import AppForwardTab from "./tabs/forward";
import { pause } from "../../helpers/schedulers";
import rootScope from "../../lib/rootScope";
import { dispatchHeavyAnimationEvent } from "../../hooks/useHeavyAnimationCheck";
import { MOUNT_CLASS_TO } from "../../config/debug";
export const RIGHT_COLUMN_ACTIVE_CLASSNAME = 'is-right-column-shown';
const sharedMediaTab = new AppSharedMediaTab();
const searchTab = new AppPrivateSearchTab();
//const forwardTab = new AppForwardTab();
const stickersTab = new AppStickersTab();
const pollResultsTab = new AppPollResultsTab();
const gifsTab = new AppGifsTab();
@ -37,13 +32,18 @@ export class AppSidebarRight extends SidebarSlider { @@ -37,13 +32,18 @@ export class AppSidebarRight extends SidebarSlider {
public gifsTab: AppGifsTab;
constructor() {
super(document.getElementById('column-right') as HTMLElement, {
[AppSidebarRight.SLIDERITEMSIDS.sharedMedia]: sharedMediaTab,
[AppSidebarRight.SLIDERITEMSIDS.search]: searchTab,
[AppSidebarRight.SLIDERITEMSIDS.stickers]: stickersTab,
[AppSidebarRight.SLIDERITEMSIDS.pollResults]: pollResultsTab,
[AppSidebarRight.SLIDERITEMSIDS.gifs]: gifsTab
}, true);
super({
sidebarEl: document.getElementById('column-right') as HTMLElement,
tabs: {
[AppSidebarRight.SLIDERITEMSIDS.sharedMedia]: sharedMediaTab,
[AppSidebarRight.SLIDERITEMSIDS.search]: searchTab,
[AppSidebarRight.SLIDERITEMSIDS.stickers]: stickersTab,
[AppSidebarRight.SLIDERITEMSIDS.pollResults]: pollResultsTab,
[AppSidebarRight.SLIDERITEMSIDS.gifs]: gifsTab
},
canHideFirst: true,
navigationType: 'right'
});
this.sharedMediaTab = sharedMediaTab;
this.searchTab = searchTab;
@ -58,12 +58,12 @@ export class AppSidebarRight extends SidebarSlider { @@ -58,12 +58,12 @@ export class AppSidebarRight extends SidebarSlider {
});
}
public onCloseTab(id: number) {
public onCloseTab(id: number, animate: boolean) {
if(!this.historyTabIds.length) {
this.toggleSidebar(false);
this.toggleSidebar(false, animate);
}
super.onCloseTab(id);
super.onCloseTab(id, animate);
}
/* public selectTab(id: number) {
@ -76,7 +76,7 @@ export class AppSidebarRight extends SidebarSlider { @@ -76,7 +76,7 @@ export class AppSidebarRight extends SidebarSlider {
return res;
} */
public toggleSidebar(enable?: boolean, saveStatus = true) {
public toggleSidebar(enable?: boolean, animate?: boolean) {
/////this.log('sidebarEl', this.sidebarEl, enable, isElementInViewport(this.sidebarEl));
const active = document.body.classList.contains(RIGHT_COLUMN_ACTIVE_CLASSNAME);
@ -95,30 +95,13 @@ export class AppSidebarRight extends SidebarSlider { @@ -95,30 +95,13 @@ export class AppSidebarRight extends SidebarSlider {
if(!willChange) return Promise.resolve();
if(saveStatus) {
appImManager.hideRightSidebar = false;
}
if(!active && !this.historyTabIds.length) {
this.selectTab(AppSidebarRight.SLIDERITEMSIDS.sharedMedia);
}
const transitionTime = rootScope.settings.animationsEnabled ? (mediaSizes.isMobile ? 250 : 200) : 0;
const promise = pause(transitionTime);
if(transitionTime) {
dispatchHeavyAnimationEvent(promise, transitionTime);
}
const animationPromise = appImManager.selectTab(active ? 1 : 2, animate);
document.body.classList.toggle(RIGHT_COLUMN_ACTIVE_CLASSNAME, enable);
//console.log('sidebar selectTab', enable, willChange);
//if(mediaSizes.isMobile) {
//appImManager._selectTab(active ? 1 : 2);
appImManager.selectTab(active ? 1 : 2);
return promise; // delay of slider animation
//}
return pause(200); // delay for third column open
//return Promise.resolve();
return animationPromise;
/* return new Promise((resolve, reject) => {
const hidden: {element: HTMLDivElement, height: number}[] = [];

53
src/components/slider.ts

@ -3,6 +3,8 @@ import { horizontalMenu } from "./horizontalMenu"; @@ -3,6 +3,8 @@ import { horizontalMenu } from "./horizontalMenu";
import ButtonIcon from "./buttonIcon";
import Scrollable from "./scrollable";
import { TransitionSlider } from "./transition";
import appNavigationController, { NavigationItem } from "./appNavigationController";
import { isSafari } from "../helpers/userAgent";
export interface SliderTab {
onOpen?: () => void,
@ -84,28 +86,47 @@ export default class SidebarSlider { @@ -84,28 +86,47 @@ export default class SidebarSlider {
protected _selectTab: ReturnType<typeof horizontalMenu>;
public historyTabIds: number[] = [];
public tabsContainer: HTMLElement;
public sidebarEl: HTMLElement;
public tabs: {[id: number]: SliderTab} = {};
private canHideFirst = false;
private navigationType: NavigationItem['type']
constructor(options: {
sidebarEl: SidebarSlider['sidebarEl'],
tabs?: SidebarSlider['tabs'],
canHideFirst?: SidebarSlider['canHideFirst'],
navigationType: SidebarSlider['navigationType']
}) {
for(const i in options) {
// @ts-ignore
this[i] = options[i];
}
constructor(public sidebarEl: HTMLElement, public tabs: {[id: number]: SliderTab} = {}, private canHideFirst = false) {
this.tabsContainer = this.sidebarEl.querySelector('.sidebar-slider');
this._selectTab = TransitionSlider(this.tabsContainer, 'navigation', TRANSITION_TIME);
if(!canHideFirst) {
if(!this.canHideFirst) {
this._selectTab(0);
}
Array.from(this.sidebarEl.querySelectorAll('.sidebar-close-button') as any as HTMLElement[]).forEach(el => {
attachClickEvent(el, () => this.closeTab());
attachClickEvent(el, this.onCloseBtnClick);
});
}
public closeTab = (tabId?: number) => {
private onCloseBtnClick = () => {
appNavigationController.back();
// this.closeTab();
};
public closeTab = (tabId?: number, animate?: boolean) => {
if(tabId !== undefined && this.historyTabIds[this.historyTabIds.length - 1] !== tabId) {
return false;
}
//console.log('sidebar-close-button click:', this.historyTabIDs);
let closingId = this.historyTabIds.pop(); // pop current
this.onCloseTab(closingId);
this._selectTab(this.historyTabIds[this.historyTabIds.length - 1] ?? (this.canHideFirst ? -1 : 0));
const closingId = this.historyTabIds.pop(); // pop current
this.onCloseTab(closingId, animate);
this._selectTab(this.historyTabIds[this.historyTabIds.length - 1] ?? (this.canHideFirst ? -1 : 0), animate);
return true;
};
@ -130,6 +151,16 @@ export default class SidebarSlider { @@ -130,6 +151,16 @@ export default class SidebarSlider {
}, TRANSITION_TIME);
}
}
//if(!this.canHideFirst || this.historyTabIds.length) {
appNavigationController.pushItem({
type: this.navigationType,
onPop: (canAnimate) => {
this.closeTab(undefined, canAnimate);
return true;
}
});
//}
this.historyTabIds.push(id);
this._selectTab(id);
@ -138,10 +169,10 @@ export default class SidebarSlider { @@ -138,10 +169,10 @@ export default class SidebarSlider {
public removeTabFromHistory(id: number) {
this.historyTabIds.findAndSplice(i => i === id);
this.onCloseTab(id);
this.onCloseTab(id, undefined);
}
public onCloseTab(id: number) {
public onCloseTab(id: number, animate: boolean) {
let tab = this.tabs[id];
if(tab) {
if(tab.onClose) {
@ -165,7 +196,7 @@ export default class SidebarSlider { @@ -165,7 +196,7 @@ export default class SidebarSlider {
this.tabsContainer.append(tab.container);
if(tab.closeBtn) {
tab.closeBtn.addEventListener('click', () => this.closeTab());
tab.closeBtn.addEventListener('click', this.onCloseBtnClick);
}
}
@ -173,4 +204,4 @@ export default class SidebarSlider { @@ -173,4 +204,4 @@ export default class SidebarSlider {
return id;
}
}
}

9
src/helpers/dom.ts

@ -5,6 +5,7 @@ import { isTouchSupported } from "./touchSupport"; @@ -5,6 +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";
/* export function isInDOM(element: Element, parentNode?: HTMLElement): boolean {
if(!element) {
@ -775,3 +776,11 @@ export function isSelectionEmpty(selection = window.getSelection()) { @@ -775,3 +776,11 @@ export function isSelectionEmpty(selection = window.getSelection()) {
return false;
}
export function disableTransition(elements: HTMLElement[]) {
elements.forEach(el => el.classList.add('no-transition'));
superRaf().then(() => {
elements.forEach(el => el.classList.remove('no-transition'));
});
}

8
src/helpers/schedulers.ts

@ -123,3 +123,11 @@ export function fastRaf(callback: NoneToVoidFunction) { @@ -123,3 +123,11 @@ export function fastRaf(callback: NoneToVoidFunction) {
fastRafCallbacks.push(callback);
}
}
export function superRaf() {
return new Promise((resolve) => {
window.requestAnimationFrame(() => {
window.requestAnimationFrame(resolve);
});
});
}

101
src/lib/appManagers/appImManager.ts

@ -18,7 +18,7 @@ import appPhotosManager from './appPhotosManager'; @@ -18,7 +18,7 @@ import appPhotosManager from './appPhotosManager';
import appProfileManager from './appProfileManager';
import appStickersManager from './appStickersManager';
import appWebPagesManager from './appWebPagesManager';
import { cancelEvent, getFilesFromEvent, placeCaretAtEnd } from '../../helpers/dom';
import { blurActiveElement, cancelEvent, disableTransition, getFilesFromEvent, placeCaretAtEnd, whichChild } from '../../helpers/dom';
import PopupNewMedia from '../../components/popups/newMedia';
import { numberThousandSplitter } from '../../helpers/number';
import MarkupTooltip from '../../components/chat/markupTooltip';
@ -26,16 +26,17 @@ import { isTouchSupported } from '../../helpers/touchSupport'; @@ -26,16 +26,17 @@ import { isTouchSupported } from '../../helpers/touchSupport';
import appPollsManager from './appPollsManager';
import SetTransition from '../../components/singleTransition';
import ChatDragAndDrop from '../../components/chat/dragAndDrop';
import { debounce, pause } from '../../helpers/schedulers';
import { debounce, pause, superRaf } from '../../helpers/schedulers';
import lottieLoader from '../lottieLoader';
import useHeavyAnimationCheck, { dispatchHeavyAnimationEvent } from '../../hooks/useHeavyAnimationCheck';
import appDraftsManager from './appDraftsManager';
import serverTimeManager from '../mtproto/serverTimeManager';
import sessionStorage from '../sessionStorage';
import { renderImageFromUrl } from '../../components/misc';
import appDownloadManager from './appDownloadManager';
import appStateManager, { AppStateManager } from './appStateManager';
import { MOUNT_CLASS_TO } from '../../config/debug';
import appNavigationController from '../../components/appNavigationController';
import { isSafari } from '../../helpers/userAgent';
//console.log('appImManager included33!');
@ -58,7 +59,6 @@ export class AppImManager { @@ -58,7 +59,6 @@ export class AppImManager {
public setPeerPromise: Promise<void> = null;
public tabId = -1;
public hideRightSidebar = false;
private chats: Chat[] = [];
private prevTab: HTMLElement;
@ -179,6 +179,14 @@ export class AppImManager { @@ -179,6 +179,14 @@ export class AppImManager {
this.setBackground('');
}
// * fix simultaneous opened both sidebars, can happen when floating sidebar is opened with left sidebar
mediaSizes.addListener('changeScreen', (from, to) => {
if(document.body.classList.contains(LEFT_COLUMN_ACTIVE_CLASSNAME)
&& document.body.classList.contains(RIGHT_COLUMN_ACTIVE_CLASSNAME)) {
appSidebarRight.toggleSidebar(false);
}
});
/* rootScope.on('peer_changing', (chat) => {
this.saveChatPosition(chat);
});
@ -248,18 +256,35 @@ export class AppImManager { @@ -248,18 +256,35 @@ export class AppImManager {
// * не могу использовать тут TransitionSlider, так как мне нужен отрисованный блок рядом
// * (или под текущим чатом) чтобы правильно отрендерить чат (напр. scrollTop)
private chatsSelectTab(tab: HTMLElement) {
private chatsSelectTab(tab: HTMLElement, animate?: boolean) {
if(this.prevTab === tab) {
return;
}
if(animate === false && this.prevTab) { // * will be used for Safari iOS history swipe
disableTransition([tab, this.prevTab].filter(Boolean));
}
if(this.prevTab) {
this.prevTab.classList.remove('active');
this.chatsSelectTabDebounced();
if(rootScope.settings.animationsEnabled) { // ! нужно переделать на animation, так как при лаге анимация будет длиться не 250мс
// ! нужно переделать на animation, так как при лаге анимация будет длиться не 250мс
if(rootScope.settings.animationsEnabled && animate !== false) {
dispatchHeavyAnimationEvent(pause(250 + 150), 250 + 150);
}
const prevIdx = whichChild(this.prevTab);
const idx = whichChild(tab);
if(idx > prevIdx) {
appNavigationController.pushItem({
type: 'chat',
onPop: (canAnimate) => {
this.setPeer(0, undefined, canAnimate);
blurActiveElement();
}
});
}
}
tab.classList.add('active');
@ -509,34 +534,50 @@ export class AppImManager { @@ -509,34 +534,50 @@ export class AppImManager {
});
};
public selectTab(id: number) {
public selectTab(id: number, animate?: boolean) {
if(animate === false) { // * will be used for Safari iOS history swipe
disableTransition([appSidebarLeft.sidebarEl, this.columnEl, appSidebarRight.sidebarEl]);
}
document.body.classList.toggle(LEFT_COLUMN_ACTIVE_CLASSNAME, id === 0);
const prevTabId = this.tabId;
this.log('selectTab', id, prevTabId);
if(prevTabId !== -1 && prevTabId !== id && rootScope.settings.animationsEnabled) {
let animationPromise: Promise<any> = superRaf();
if(prevTabId !== -1 && prevTabId !== id && rootScope.settings.animationsEnabled && animate !== false) {
const transitionTime = (mediaSizes.isMobile ? 250 : 200) + 100; // * cause transition time could be > 250ms
const promise = pause(transitionTime);
dispatchHeavyAnimationEvent(promise, transitionTime);
animationPromise = pause(transitionTime);
dispatchHeavyAnimationEvent(animationPromise, transitionTime);
this.columnEl.classList.add('disable-hover');
promise.finally(() => {
animationPromise.finally(() => {
this.columnEl.classList.remove('disable-hover');
});
}
this.tabId = id;
if(mediaSizes.isMobile && prevTabId === 2 && id === 1) {
//appSidebarRight.toggleSidebar(false);
if(mediaSizes.isMobile && prevTabId === 2 && id < 2) {
document.body.classList.remove(RIGHT_COLUMN_ACTIVE_CLASSNAME);
}
if(prevTabId !== -1 && id > prevTabId && id < 2) {
appNavigationController.pushItem({
type: 'im',
onPop: (canAnimate) => {
//this.selectTab(prevTabId, !isSafari);
this.setPeer(0, undefined, canAnimate);
}
});
}
rootScope.broadcast('im_tab_change', id);
//this._selectTab(id, mediaSizes.isMobile);
//document.body.classList.toggle(RIGHT_COLUMN_ACTIVE_CLASSNAME, id === 2);
return animationPromise;
}
public updateStatus() {
@ -556,7 +597,7 @@ export class AppImManager { @@ -556,7 +597,7 @@ export class AppImManager {
this.chats.push(chat);
}
private spliceChats(fromIndex: number, justReturn = true) {
private spliceChats(fromIndex: number, justReturn = true, animate?: boolean) {
if(fromIndex >= this.chats.length) return;
if(this.chats.length > 1 && justReturn) {
@ -572,7 +613,7 @@ export class AppImManager { @@ -572,7 +613,7 @@ export class AppImManager {
});
}
this.chatsSelectTab(this.chat.container);
this.chatsSelectTab(this.chat.container, animate);
if(justReturn) {
rootScope.broadcast('peer_changed', this.chat.peerId);
@ -598,7 +639,7 @@ export class AppImManager { @@ -598,7 +639,7 @@ export class AppImManager {
}, 250 + 100);
}
public setPeer(peerId: number, lastMsgId?: number): boolean {
public setPeer(peerId: number, lastMsgId?: number, animate?: boolean): boolean {
if(this.init) {
this.init();
this.init = null;
@ -609,29 +650,20 @@ export class AppImManager { @@ -609,29 +650,20 @@ export class AppImManager {
if(!peerId) {
if(chatIndex > 0) {
this.spliceChats(chatIndex);
this.spliceChats(chatIndex, undefined, animate);
return;
} else if(mediaSizes.activeScreen === ScreenSize.medium) { // * floating sidebar case
const isNowOpen = document.body.classList.toggle(LEFT_COLUMN_ACTIVE_CLASSNAME);
if(isNowOpen && document.body.classList.contains(RIGHT_COLUMN_ACTIVE_CLASSNAME)) {
appSidebarRight.toggleSidebar(false, false);
this.selectTab(0);
this.hideRightSidebar = isNowOpen;
} else if(this.hideRightSidebar) {
appSidebarRight.toggleSidebar(true);
}
this.selectTab(+!this.tabId, animate);
return;
}
} else if(chatIndex > 0 && chat.peerId && chat.peerId !== peerId) {
this.spliceChats(1, false);
this.spliceChats(1, false, animate);
return this.setPeer(peerId, lastMsgId);
}
// * don't reset peer if returning
if(peerId === chat.peerId && mediaSizes.activeScreen === ScreenSize.mobile && document.body.classList.contains(LEFT_COLUMN_ACTIVE_CLASSNAME)) {
this.selectTab(1);
if(peerId === chat.peerId && mediaSizes.activeScreen <= ScreenSize.medium && document.body.classList.contains(LEFT_COLUMN_ACTIVE_CLASSNAME)) {
this.selectTab(1, animate);
return false;
}
@ -644,22 +676,17 @@ export class AppImManager { @@ -644,22 +676,17 @@ export class AppImManager {
promise.then(() => {
//window.requestAnimationFrame(() => {
setTimeout(() => { // * setTimeout is better here
if(this.hideRightSidebar) {
appSidebarRight.toggleSidebar(true);
this.hideRightSidebar = false;
}
setTimeout(() => {
this.chatsSelectTab(this.chat.container);
}, 0);
this.selectTab(1);
this.selectTab(1, animate);
}, 0);
});
}
}
if(!peerId) {
this.selectTab(0);
this.selectTab(0, animate);
return false;
}
}

1
src/scss/partials/_leftSidebar.scss

@ -26,6 +26,7 @@ @@ -26,6 +26,7 @@
width: 26.5rem;
transform: translate3d(-5rem, 0, 0);
transition: transform var(--layer-transition);
max-width: unset;
body.animation-level-0 & {
transition: none;

2
src/scss/partials/_rightSidebar.scss

@ -27,7 +27,7 @@ @@ -27,7 +27,7 @@
border-left-width: 0;
} */
body.is-right-column-shown & {
body.is-right-column-shown:not(.is-left-column-shown) & {
transform: translate3d(0, 0, 0);
}

Loading…
Cancel
Save