Browse Source

FIx unneeded webPage refresh

Fix locking scroll due to tab swipe on iOS
master
morethanwords 3 years ago
parent
commit
409f30d806
  1. 26
      src/components/chat/bubbles.ts
  2. 21
      src/components/chat/input.ts
  3. 17
      src/components/inputField.ts
  4. 7
      src/components/scrollable.ts
  5. 3
      src/helpers/dom/handleHorizontalSwipe.ts
  6. 36
      src/helpers/fastSmoothScroll.ts
  7. 4
      src/layer.d.ts
  8. 2
      src/lib/appManagers/appDraftsManager.ts
  9. 12
      src/lib/appManagers/appImManager.ts
  10. 43
      src/lib/appManagers/appMessagesManager.ts
  11. 11
      src/lib/appManagers/appWebPagesManager.ts
  12. 4
      src/scripts/in/schema_additional_params.json
  13. 3
      src/scss/partials/_chat.scss

26
src/components/chat/bubbles.ts

@ -133,7 +133,7 @@ export default class ChatBubbles { @@ -133,7 +133,7 @@ export default class ChatBubbles {
private loadedTopTimes = 0;
private loadedBottomTimes = 0;
private messagesQueuePromise: Promise<void> = null;
public messagesQueuePromise: Promise<void> = null;
private messagesQueue: {message: any, bubble: HTMLElement, reverse: boolean, promises: Promise<void>[]}[] = [];
private messagesQueueOnRender: () => void = null;
private messagesQueueOnRenderAdditional: () => void = null;
@ -1409,6 +1409,10 @@ export default class ChatBubbles { @@ -1409,6 +1409,10 @@ export default class ChatBubbles {
if(msgId > 0 && msgId <= maxId) {
const bubble = this.bubbles[msgId];
if(bubble) {
if(bubble.classList.contains('is-sending')) {
continue;
}
bubble.classList.remove('is-sent', 'is-sending'); // is-sending can be when there are bulk of updates (e.g. sending command to Stickers bot)
bubble.classList.add('is-read');
}
@ -1533,7 +1537,25 @@ export default class ChatBubbles { @@ -1533,7 +1537,25 @@ export default class ChatBubbles {
}
}
return this.scrollable.scrollIntoViewNew(element, position, 4, undefined, forceDirection, forceDuration);
return this.scrollable.scrollIntoViewNew(
element,
position,
4,
undefined,
forceDirection,
forceDuration,
'y',
({rect}) => {
let height = windowSize.windowH;
height -= this.chat.topbar.container.getBoundingClientRect().height;
height -= 78;
return height;
const rowsWrapperHeight = this.chat.input.rowsWrapper.getBoundingClientRect().height;
const diff = rowsWrapperHeight - 54;
return rect.height + diff;
}
);
}
public scrollToBubbleEnd(bubble = this.getLastBubble()) {

21
src/components/chat/input.ts

@ -82,7 +82,6 @@ import PopupPeer from '../popups/peer'; @@ -82,7 +82,6 @@ import PopupPeer from '../popups/peer';
import MEDIA_MIME_TYPES_SUPPORTED from '../../environment/mediaMimeTypesSupport';
import appMediaPlaybackController from '../appMediaPlaybackController';
import { NULL_PEER_ID } from '../../lib/mtproto/mtproto_config';
import replaceContent from '../../helpers/dom/replaceContent';
const RECORD_MIN_TIME = 500;
const POSTING_MEDIA_NOT_ALLOWED = 'Posting media content isn\'t allowed in this group.';
@ -776,6 +775,14 @@ export default class ChatInput { @@ -776,6 +775,14 @@ export default class ChatInput {
draft = this.appDraftsManager.getDraft(this.chat.peerId, this.chat.threadId);
if(!draft) {
if(force) { // this situation can only happen when sending message with clearDraft
((this.chat.bubbles.messagesQueuePromise || Promise.resolve()) as Promise<any>).then(() => {
fastRaf(() => {
this.onMessageSent();
});
});
}
return false;
}
}
@ -921,6 +928,7 @@ export default class ChatInput { @@ -921,6 +928,7 @@ export default class ChatInput {
private attachMessageInputListeners() {
this.listenerSetter.add(this.messageInput)('keydown', (e: KeyboardEvent) => {
if(isSendShortcutPressed(e)) {
cancelEvent(e);
this.sendMessage();
} else if(e.ctrlKey || e.metaKey) {
this.handleMarkdownShortcut(e);
@ -1758,6 +1766,8 @@ export default class ChatInput { @@ -1758,6 +1766,8 @@ export default class ChatInput {
entities,
noWebPage: this.noWebPage
});
this.onMessageSent();
} else {
new PopupDeleteMessages(this.chat.peerId, [this.editMsgId], this.chat.type);
@ -1774,6 +1784,9 @@ export default class ChatInput { @@ -1774,6 +1784,9 @@ export default class ChatInput {
silent: this.sendSilent,
clearDraft: true
});
this.onMessageSent(false, false);
// this.onMessageSent();
}
// * wait for sendText set messageId for invokeAfterMsg
@ -1792,7 +1805,7 @@ export default class ChatInput { @@ -1792,7 +1805,7 @@ export default class ChatInput {
}, 0);
}
this.onMessageSent();
// this.onMessageSent();
}
public sendMessageWithDocument(document: MyDocument | string, force = false, clearDraft = false) {
@ -1934,12 +1947,12 @@ export default class ChatInput { @@ -1934,12 +1947,12 @@ export default class ChatInput {
if(message._ === 'messageEmpty') { // load missing replying message
peerTitleEl = i18n('Loading');
this.chat.appMessagesManager.wrapSingleMessage(this.chat.peerId, mid).then(() => {
this.chat.appMessagesManager.wrapSingleMessage(this.chat.peerId, mid).then((_message) => {
if(this.replyToMsgId !== mid) {
return;
}
message = this.chat.getMessage(mid);
message = _message;
if(message._ === 'messageEmpty') {
this.clearHelper('reply');
} else {

17
src/components/inputField.ts

@ -93,7 +93,7 @@ class InputField { @@ -93,7 +93,7 @@ class InputField {
public validate: () => boolean;
//public onLengthChange: (length: number, isOverflow: boolean) => void;
protected wasInputFakeClientHeight: number;
// protected wasInputFakeClientHeight: number;
// protected showScrollDebounced: () => void;
constructor(public options: InputFieldOptions = {}) {
@ -147,7 +147,7 @@ class InputField { @@ -147,7 +147,7 @@ class InputField {
if(options.animate) {
input.classList.add('scrollable', 'scrollable-y');
this.wasInputFakeClientHeight = 0;
// this.wasInputFakeClientHeight = 0;
// this.showScrollDebounced = debounce(() => this.input.classList.remove('no-scrollbar'), 150, false, true);
this.inputFake = document.createElement('div');
this.inputFake.setAttribute('contenteditable', 'true');
@ -237,14 +237,21 @@ class InputField { @@ -237,14 +237,21 @@ class InputField {
}
public onFakeInput() {
const {scrollHeight, clientHeight} = this.inputFake;
const {scrollHeight: newHeight/* , clientHeight */} = this.inputFake;
/* if(this.wasInputFakeClientHeight && this.wasInputFakeClientHeight !== clientHeight) {
this.input.classList.add('no-scrollbar'); // ! в сафари может вообще не появиться скролл после анимации, так как ему нужен полный reflow блока с overflow.
this.showScrollDebounced();
} */
this.wasInputFakeClientHeight = clientHeight;
this.input.style.height = scrollHeight ? scrollHeight + 'px' : '';
const TRANSITION_DURATION_FACTOR = 50;
const currentHeight = +this.input.style.height.replace('px', '');
const transitionDuration = Math.round(
TRANSITION_DURATION_FACTOR * Math.log(Math.abs(newHeight - currentHeight)),
);
// this.wasInputFakeClientHeight = clientHeight;
this.input.style.transitionDuration = `${transitionDuration}ms`;
this.input.style.height = newHeight ? newHeight + 'px' : '';
}
get value() {

7
src/components/scrollable.ts

@ -6,7 +6,7 @@ @@ -6,7 +6,7 @@
import { IS_TOUCH_SUPPORTED } from "../environment/touchSupport";
import { logger, LogTypes } from "../lib/logger";
import fastSmoothScroll, { FocusDirection } from "../helpers/fastSmoothScroll";
import fastSmoothScroll, { FocusDirection, ScrollGetNormalSizeCallback } from "../helpers/fastSmoothScroll";
import useHeavyAnimationCheck from "../hooks/useHeavyAnimationCheck";
import { cancelEvent } from "../helpers/dom/cancelEvent";
/*
@ -106,10 +106,11 @@ export class ScrollableBase { @@ -106,10 +106,11 @@ export class ScrollableBase {
maxDistance?: number,
forceDirection?: FocusDirection,
forceDuration?: number,
axis?: 'x' | 'y'
axis?: 'x' | 'y',
getNormalSize?: ScrollGetNormalSizeCallback
) {
//return Promise.resolve();
return fastSmoothScroll(this.container, element, position, margin, maxDistance, forceDirection, forceDuration, axis);
return fastSmoothScroll(this.container, element, position, margin, maxDistance, forceDirection, forceDuration, axis, getNormalSize);
}
}

3
src/helpers/dom/handleHorizontalSwipe.ts

@ -5,7 +5,6 @@ @@ -5,7 +5,6 @@
*/
import SwipeHandler, { SwipeHandlerOptions } from "../../components/swipeHandler";
import { IS_APPLE_MOBILE, IS_SAFARI } from "../../environment/userAgent";
import { cancelEvent } from "./cancelEvent";
import findUpClassName from "./findUpClassName";
import isSwipingBackSafari from "./isSwipingBackSafari";
@ -45,6 +44,6 @@ export default function handleHorizontalSwipe(options: SwipeHandlerHorizontalOpt @@ -45,6 +44,6 @@ export default function handleHorizontalSwipe(options: SwipeHandlerHorizontalOpt
cancelY = false;
options.onReset && options.onReset();
},
cancelEvent: true
cancelEvent: false // cannot use cancelEvent on Safari iOS because scroll will be canceled too
});
}

36
src/helpers/fastSmoothScroll.ts

@ -12,9 +12,10 @@ import { animateSingle, cancelAnimationByKey } from './animation'; @@ -12,9 +12,10 @@ import { animateSingle, cancelAnimationByKey } from './animation';
import rootScope from '../lib/rootScope';
import isInDOM from './dom/isInDOM';
const MAX_DISTANCE = 1500;
const MIN_JS_DURATION = 250;
const MAX_JS_DURATION = 600;
const LONG_TRANSITION_MAX_DISTANCE = 1500;
const SHORT_TRANSITION_MAX_DISTANCE = 500;
export enum FocusDirection {
Up,
@ -22,15 +23,18 @@ export enum FocusDirection { @@ -22,15 +23,18 @@ export enum FocusDirection {
Static,
};
export type ScrollGetNormalSizeCallback = (options: {rect: DOMRect}) => number;
export default function fastSmoothScroll(
container: HTMLElement,
element: HTMLElement,
position: ScrollLogicalPosition,
margin = 0,
maxDistance = MAX_DISTANCE,
maxDistance = LONG_TRANSITION_MAX_DISTANCE,
forceDirection?: FocusDirection,
forceDuration?: number,
axis: 'x' | 'y' = 'y'
axis: 'x' | 'y' = 'y',
getNormalSize?: ScrollGetNormalSizeCallback
) {
//return;
@ -40,7 +44,7 @@ export default function fastSmoothScroll( @@ -40,7 +44,7 @@ export default function fastSmoothScroll(
if(forceDirection === FocusDirection.Static) {
forceDuration = 0;
return scrollWithJs(container, element, position, margin, forceDuration, axis);
return scrollWithJs(container, element, position, margin, forceDuration, axis, getNormalSize);
/* return Promise.resolve();
element.scrollIntoView({ block: position });
@ -82,9 +86,9 @@ export default function fastSmoothScroll( @@ -82,9 +86,9 @@ export default function fastSmoothScroll(
} */
}
const promise = new Promise((resolve) => {
const promise = new Promise<void>((resolve) => {
fastRaf(() => {
scrollWithJs(container, element, position, margin, forceDuration, axis)
scrollWithJs(container, element, position, margin, forceDuration, axis, getNormalSize)
.then(resolve);
});
});
@ -93,7 +97,13 @@ export default function fastSmoothScroll( @@ -93,7 +97,13 @@ export default function fastSmoothScroll(
}
function scrollWithJs(
container: HTMLElement, element: HTMLElement, position: ScrollLogicalPosition, margin = 0, forceDuration?: number, axis: 'x' | 'y' = 'y'
container: HTMLElement,
element: HTMLElement,
position: ScrollLogicalPosition,
margin = 0,
forceDuration?: number,
axis: 'x' | 'y' = 'y',
getNormalSize?: ScrollGetNormalSizeCallback
) {
if(!isInDOM(element)) {
cancelAnimationByKey(container);
@ -115,7 +125,7 @@ function scrollWithJs( @@ -115,7 +125,7 @@ function scrollWithJs(
const elementPosition = elementRect[rectStartKey] - containerRect[rectStartKey];
const elementSize = element[scrollSizeKey]; // margin is exclusive in DOMRect
const containerSize = containerRect[sizeKey];
const containerSize = getNormalSize ? getNormalSize({rect: containerRect}) : containerRect[sizeKey];
const scrollPosition = container[scrollPositionKey];
const scrollSize = container[scrollSizeKey];
@ -177,8 +187,9 @@ function scrollWithJs( @@ -177,8 +187,9 @@ function scrollWithJs(
}
const target = container[scrollPositionKey] + path;
const absPath = Math.abs(path);
const duration = forceDuration ?? (
MIN_JS_DURATION + (Math.abs(path) / MAX_DISTANCE) * (MAX_JS_DURATION - MIN_JS_DURATION)
MIN_JS_DURATION + (absPath / LONG_TRANSITION_MAX_DISTANCE) * (MAX_JS_DURATION - MIN_JS_DURATION)
);
const startAt = Date.now();
@ -222,6 +233,7 @@ function scrollWithJs( @@ -222,6 +233,7 @@ function scrollWithJs(
//transformable.style.minHeight = `${transformableHeight}px`;
*/
const transition = absPath < SHORT_TRANSITION_MAX_DISTANCE ? shortTransition : longTransition;
const tick = () => {
const t = duration ? Math.min((Date.now() - startAt) / duration, 1) : 1;
@ -258,6 +270,10 @@ function scrollWithJs( @@ -258,6 +270,10 @@ function scrollWithJs(
return animateSingle(tick, container);
}
function transition(t: number) {
function longTransition(t: number) {
return 1 - ((1 - t) ** 5);
}
function shortTransition(t: number) {
return 1 - ((1 - t) ** 3.5);
}

4
src/layer.d.ts vendored

@ -816,7 +816,9 @@ export namespace Message { @@ -816,7 +816,9 @@ export namespace Message {
flags?: number,
id: number,
peer_id?: Peer,
deleted?: boolean
deleted?: boolean,
mid?: number,
pFlags?: {}
};
export type message = {

2
src/lib/appManagers/appDraftsManager.ts

@ -251,7 +251,7 @@ export class AppDraftsManager { @@ -251,7 +251,7 @@ export class AppDraftsManager {
if(threadId) {
this.syncDraft(peerId, threadId);
} else {
this.saveDraft(peerId, threadId, null, {notify: true/* , force: true */});
this.saveDraft(peerId, threadId, null, {notify: true, force: true});
}
}

12
src/lib/appManagers/appImManager.ts

@ -501,16 +501,22 @@ export class AppImManager { @@ -501,16 +501,22 @@ export class AppImManager {
return;
}
if(chat.input.messageInput &&
if(
chat?.input?.messageInput &&
e.target !== chat.input.messageInput &&
target.tagName !== 'INPUT' &&
!target.hasAttribute('contenteditable') &&
!IS_TOUCH_SUPPORTED &&
(!mediaSizes.isMobile || this.tabId === 1) &&
!this.chat.selection.isSelecting &&
!this.chat.input.recording) {
!chat.selection.isSelecting &&
!chat.input.recording
) {
chat.input.messageInput.focus();
placeCaretAtEnd(chat.input.messageInput);
// clone and dispatch same event to new input. it is needed for sending message if input was blurred
const newEvent = new KeyboardEvent(e.type, e);
chat.input.messageInput.dispatchEvent(newEvent);
}
};

43
src/lib/appManagers/appMessagesManager.ts

@ -1073,7 +1073,7 @@ export class AppMessagesManager { @@ -1073,7 +1073,7 @@ export class AppMessagesManager {
const messages = files.map((file, idx) => {
const details = options.sendFileDetails[idx];
const o: any = {
const o: Parameters<AppMessagesManager['sendFile']>[2] = {
isGroupedItem: true,
isMedia: options.isMedia,
scheduleDate: options.scheduleDate,
@ -1094,7 +1094,9 @@ export class AppMessagesManager { @@ -1094,7 +1094,9 @@ export class AppMessagesManager {
});
if(options.clearDraft) {
setTimeout(() => {
appDraftsManager.clearDraft(peerId, options.threadId);
}, 0);
}
// * test pending
@ -1407,10 +1409,6 @@ export class AppMessagesManager { @@ -1407,10 +1409,6 @@ export class AppMessagesManager {
}, 0);
}
if(!options.isGroupedItem && options.clearDraft) {
appDraftsManager.clearDraft(peerId, options.threadId);
}
this.pendingByRandomId[message.random_id] = {
peerId,
tempId: messageId,
@ -1419,9 +1417,13 @@ export class AppMessagesManager { @@ -1419,9 +1417,13 @@ export class AppMessagesManager {
};
if(!options.isGroupedItem && message.send) {
setTimeout(message.send, 0);
//setTimeout(message.send, 4000);
//setTimeout(message.send, 7000);
setTimeout(() => {
if(options.clearDraft) {
appDraftsManager.clearDraft(peerId, options.threadId);
}
message.send();
}, 0);
}
}
@ -1966,15 +1968,20 @@ export class AppMessagesManager { @@ -1966,15 +1968,20 @@ export class AppMessagesManager {
return promise;
}
public getMessageFromStorage(storage: MessagesStorage, mid: number) {
return storage && storage.get(mid) || {
public generateEmptyMessage(mid: number): Message.messageEmpty {
return {
_: 'messageEmpty',
id: mid,
id: appMessagesIdsManager.getServerMessageId(mid),
mid,
deleted: true,
pFlags: {}
};
}
public getMessageFromStorage(storage: MessagesStorage, mid: number) {
return storage && storage.get(mid) || this.generateEmptyMessage(mid);
}
private createMessageStorage() {
const storage: MessagesStorage = new Map();
@ -5455,7 +5462,6 @@ export class AppMessagesManager { @@ -5455,7 +5462,6 @@ export class AppMessagesManager {
for(const [peerId, map] of this.needSingleMessages) {
const mids = [...map.keys()];
const promises = [...map.values()];
const msgIds: InputMessage[] = mids.map((mid) => {
return {
_: 'inputMessageID',
@ -5483,8 +5489,17 @@ export class AppMessagesManager { @@ -5483,8 +5489,17 @@ export class AppMessagesManager {
this.saveMessages(getMessagesResult.messages);
for(let i = 0; i < getMessagesResult.messages.length; ++i) {
const promise = promises[i];
const message = getMessagesResult.messages[i];
const mid = appMessagesIdsManager.generateMessageId(message.id);
const promise = map.get(mid);
promise.resolve(getMessagesResult.messages[i]);
map.delete(mid);
}
if(map.size) {
for(const [mid, promise] of map) {
promise.resolve(this.generateEmptyMessage(mid));
}
}
}).finally(() => {
rootScope.dispatchEvent('messages_downloaded', {peerId, mids});
@ -5497,7 +5512,7 @@ export class AppMessagesManager { @@ -5497,7 +5512,7 @@ export class AppMessagesManager {
Promise.all(requestPromises).finally(() => {
this.fetchSingleMessagesPromise = null;
if(Object.keys(this.needSingleMessages).length) this.fetchSingleMessages();
if(this.needSingleMessages.size) this.fetchSingleMessages();
resolve();
});
}, 0);

11
src/lib/appManagers/appWebPagesManager.ts

@ -43,6 +43,13 @@ export class AppWebPagesManager { @@ -43,6 +43,13 @@ export class AppWebPagesManager {
if(apiWebPage._ === 'webPageNotModified') return;
const {id} = apiWebPage;
const oldWebPage = this.webpages[id];
if(oldWebPage &&
oldWebPage._ === apiWebPage._ &&
(oldWebPage as WebPage.webPage).hash === (oldWebPage as WebPage.webPage).hash) {
return oldWebPage;
}
if(apiWebPage._ === 'webPage') {
if(apiWebPage.photo?._ === 'photo') {
apiWebPage.photo = appPhotosManager.savePhoto(apiWebPage.photo, mediaContext);
@ -97,10 +104,10 @@ export class AppWebPagesManager { @@ -97,10 +104,10 @@ export class AppWebPagesManager {
pendingSet.add(messageKey);
}
if(this.webpages[id] === undefined) {
if(oldWebPage === undefined) {
this.webpages[id] = apiWebPage;
} else {
safeReplaceObject(this.webpages[id], apiWebPage);
safeReplaceObject(oldWebPage, apiWebPage);
}
if(!messageKey && pendingSet !== undefined) {

4
src/scripts/in/schema_additional_params.json

@ -99,7 +99,9 @@ @@ -99,7 +99,9 @@
}, {
"predicate": "messageEmpty",
"params": [
{"name": "deleted", "type": "boolean"}
{"name": "deleted", "type": "boolean"},
{"name": "mid", "type": "number"},
{"name": "pFlags", "type": "{}"}
]
}, {
"predicate": "userFull",

3
src/scss/partials/_chat.scss

@ -917,7 +917,8 @@ $chat-helper-size: 36px; @@ -917,7 +917,8 @@ $chat-helper-size: 36px;
pointer-events: none;
@include animation-level(2) {
transition: height var(--layer-transition), opacity var(--layer-transition);
// transition: height var(--layer-transition), opacity var(--layer-transition);
transition: height .15s ease-out, opacity .15s ease-out;
}
@include respond-to(esg-bottom-new) {

Loading…
Cancel
Save