Browse Source

Changed jumping logic

Scale animation on first chat opening
Draft: save entities on saving
Fix blinking media on chat opening
Changed peer name colors to macOS version
master
morethanwords 4 years ago
parent
commit
abf1acc6c8
  1. 17
      src/components/appMediaViewer.ts
  2. 132
      src/components/chat/bubbles.ts
  3. 22
      src/components/chat/input.ts
  4. 4
      src/components/misc.ts
  5. 2
      src/components/preloader.ts
  6. 4
      src/helpers/fastSmoothScroll.ts
  7. 6
      src/lib/appManagers/appDraftsManager.ts
  8. 2
      src/lib/appManagers/appPeersManager.ts
  9. 4
      src/scss/partials/_badge.scss
  10. 3
      src/scss/partials/_chatBubble.scss
  11. 1
      src/scss/partials/_input.scss
  12. 4
      src/scss/partials/_leftSidebar.scss

17
src/components/appMediaViewer.ts

@ -27,6 +27,7 @@ import appSidebarRight, { AppSidebarRight } from "./sidebarRight";
import SwipeHandler from "./swipeHandler"; import SwipeHandler from "./swipeHandler";
import { months, ONE_DAY } from "../helpers/date"; import { months, ONE_DAY } from "../helpers/date";
import { SearchSuperContext } from "./appSearchSuper."; import { SearchSuperContext } from "./appSearchSuper.";
import { DEBUG } from "../lib/mtproto/mtproto_config";
// TODO: масштабирование картинок (не SVG) при ресайзе, и правильный возврат на исходную позицию // TODO: масштабирование картинок (не SVG) при ресайзе, и правильный возврат на исходную позицию
// TODO: картинки "обрезаются" если возвращаются или появляются с места, где есть их перекрытие (топбар, поле ввода) // TODO: картинки "обрезаются" если возвращаются или появляются с места, где есть их перекрытие (топбар, поле ввода)
@ -332,7 +333,9 @@ class AppMediaViewerBase<ContentAdditionType extends string, ButtonsAdditionType
mover = this.setNewMover(); mover = this.setNewMover();
} */ } */
this.log('setMoverToTarget', target, closing, wasActive, fromRight); /* if(DEBUG) {
this.log('setMoverToTarget', target, closing, wasActive, fromRight);
} */
let realParent: HTMLElement; let realParent: HTMLElement;
@ -805,7 +808,9 @@ class AppMediaViewerBase<ContentAdditionType extends string, ButtonsAdditionType
prevTargets: TargetType[] = [], nextTargets: TargetType[] = [], needLoadMore = true) { prevTargets: TargetType[] = [], nextTargets: TargetType[] = [], needLoadMore = true) {
if(this.setMoverPromise) return this.setMoverPromise; if(this.setMoverPromise) return this.setMoverPromise;
this.log('openMedia:', media, fromId, prevTargets, nextTargets); /* if(DEBUG) {
this.log('openMedia:', media, fromId, prevTargets, nextTargets);
} */
this.setAuthorInfo(fromId, timestamp); this.setAuthorInfo(fromId, timestamp);
@ -1263,7 +1268,9 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet
minDate: this.searchContext.minDate, minDate: this.searchContext.minDate,
maxDate: this.searchContext.maxDate maxDate: this.searchContext.maxDate
}).then(value => { }).then(value => {
this.log('loaded more media by maxId:', maxId, value, older, this.reverse); /* if(DEBUG) {
this.log('loaded more media by maxId:', maxId, value, older, this.reverse);
} */
if(value.next_rate) { if(value.next_rate) {
this.searchContext.nextRate = value.next_rate; this.searchContext.nextRate = value.next_rate;
@ -1414,7 +1421,9 @@ export class AppMediaViewerAvatar extends AppMediaViewerBase<'', 'delete', AppMe
return; return;
} }
this.log('loaded more media by maxId:', /* maxId, */value, older, this.reverse); // if(DEBUG) {
// this.log('loaded more media by maxId:', /* maxId, */value, older, this.reverse);
// }
if(value.photos.length < loadCount) { if(value.photos.length < loadCount) {
this.loadedAllMediaDown = true; this.loadedAllMediaDown = true;

132
src/components/chat/bubbles.ts

@ -113,6 +113,8 @@ export default class ChatBubbles {
public isHeavyAnimationInProgress = false; public isHeavyAnimationInProgress = false;
public scrollingToNewBubble: HTMLElement; public scrollingToNewBubble: HTMLElement;
public isFirstLoad = true;
constructor(private chat: Chat, private appMessagesManager: AppMessagesManager, private appStickersManager: AppStickersManager, private appUsersManager: AppUsersManager, private appInlineBotsManager: AppInlineBotsManager, private appPhotosManager: AppPhotosManager, private appDocsManager: AppDocsManager, private appPeersManager: AppPeersManager, private appChatsManager: AppChatsManager) { constructor(private chat: Chat, private appMessagesManager: AppMessagesManager, private appStickersManager: AppStickersManager, private appUsersManager: AppUsersManager, private appInlineBotsManager: AppInlineBotsManager, private appPhotosManager: AppPhotosManager, private appDocsManager: AppDocsManager, private appPeersManager: AppPeersManager, private appChatsManager: AppChatsManager) {
//this.chat.log.error('Bubbles construction'); //this.chat.log.error('Bubbles construction');
@ -371,10 +373,21 @@ export default class ChatBubbles {
} }
}); });
let middleware: ReturnType<ChatBubbles['getMiddleware']>;
useHeavyAnimationCheck(() => { useHeavyAnimationCheck(() => {
this.isHeavyAnimationInProgress = true; this.isHeavyAnimationInProgress = true;
this.lazyLoadQueue.lock();
middleware = this.getMiddleware();
}, () => { }, () => {
this.isHeavyAnimationInProgress = false; this.isHeavyAnimationInProgress = false;
if(middleware && middleware()) {
this.lazyLoadQueue.unlock();
this.lazyLoadQueue.refresh();
}
middleware = null;
}, this.listenerSetter); }, this.listenerSetter);
} }
@ -1274,6 +1287,8 @@ export default class ChatBubbles {
if(maxBubbleId <= 0) { if(maxBubbleId <= 0) {
maxBubbleId = Math.max(...Object.keys(this.bubbles).map(mid => +mid)); maxBubbleId = Math.max(...Object.keys(this.bubbles).map(mid => +mid));
} }
} else {
this.isFirstLoad = true;
} }
const oldChatInner = this.chatInner; const oldChatInner = this.chatInner;
@ -1288,14 +1303,12 @@ export default class ChatBubbles {
// clear // clear
if(!cached) { if(!cached) {
this.scrollable.container.innerHTML = '';
//oldChatInner.remove();
if(!samePeer) { if(!samePeer) {
this.scrollable.container.innerHTML = '';
//oldChatInner.remove();
this.chat.finishPeerChange(isTarget, isJump, lastMsgId); this.chat.finishPeerChange(isTarget, isJump, lastMsgId);
this.preloader.attach(this.bubblesContainer);
} }
this.preloader.attach(this.bubblesContainer);
} }
//console.timeEnd('appImManager setPeer pre promise'); //console.timeEnd('appImManager setPeer pre promise');
@ -1304,13 +1317,13 @@ export default class ChatBubbles {
const setPeerPromise = promise.then(() => { const setPeerPromise = promise.then(() => {
////this.log('setPeer removing preloader'); ////this.log('setPeer removing preloader');
this.scrollable.container.innerHTML = '';
//oldChatInner.remove();
if(cached) { if(cached) {
if(!samePeer) { if(!samePeer) {
this.chat.finishPeerChange(isTarget, isJump, lastMsgId); // * костыль this.chat.finishPeerChange(isTarget, isJump, lastMsgId); // * костыль
} }
this.scrollable.container.innerHTML = '';
//oldChatInner.remove();
} else { } else {
this.preloader.detach(); this.preloader.detach();
} }
@ -1399,7 +1412,9 @@ export default class ChatBubbles {
else dateMessage.container.append(bubble); else dateMessage.container.append(bubble);
return; */ return; */
//this.log('renderMessagesQueue'); /* if(DEBUG && message.mid === 4314759167) {
this.log('renderMessagesQueue', message, bubble, reverse, promises);
} */
this.messagesQueue.push({message, bubble, reverse, promises}); this.messagesQueue.push({message, bubble, reverse, promises});
@ -2488,13 +2503,16 @@ export default class ChatBubbles {
let resultPromise: Promise<any>; let resultPromise: Promise<any>;
//const isFirstMessageRender = !!additionMsgID && result instanceof Promise && !appMessagesManager.getMessage(additionMsgID).grouped_id; //const isFirstMessageRender = !!additionMsgID && result instanceof Promise && !appMessagesManager.getMessage(additionMsgID).grouped_id;
const isFirstMessageRender = additionMsgIds?.length; const isAdditionRender = additionMsgIds?.length;
if(isFirstMessageRender) { const isFirstMessageRender = (this.isFirstLoad && backLimit) || isAdditionRender;
if(isAdditionRender) {
resultPromise = result as Promise<any>; resultPromise = result as Promise<any>;
result = {history: additionMsgIds}; result = {history: additionMsgIds};
//additionMsgID = 0; //additionMsgID = 0;
} }
this.isFirstLoad = false;
const processResult = (historyResult: typeof result) => { const processResult = (historyResult: typeof result) => {
if(this.chat.type === 'discussion' && 'offsetIdOffset' in historyResult) { if(this.chat.type === 'discussion' && 'offsetIdOffset' in historyResult) {
const isTopEnd = historyResult.offsetIdOffset >= (historyResult.count - loadCount); const isTopEnd = historyResult.offsetIdOffset >= (historyResult.count - loadCount);
@ -2531,7 +2549,7 @@ export default class ChatBubbles {
////console.timeEnd('render history total'); ////console.timeEnd('render history total');
return getHeavyAnimationPromise().then(() => { return getHeavyAnimationPromise().then(() => {
return this.performHistoryResult(result.history || [], reverse, isBackLimit, !isFirstMessageRender && additionMsgId); return this.performHistoryResult(result.history || [], reverse, isBackLimit, !isAdditionRender && additionMsgId);
}); });
}, (err) => { }, (err) => {
this.log.error('getHistory error:', err); this.log.error('getHistory error:', err);
@ -2552,44 +2570,84 @@ export default class ChatBubbles {
//this.log('getHistory cached result by maxId:', maxId, reverse, isBackLimit, result, peerId, justLoad); //this.log('getHistory cached result by maxId:', maxId, reverse, isBackLimit, result, peerId, justLoad);
processResult(result); processResult(result);
promise = getHeavyAnimationPromise().then(() => { promise = getHeavyAnimationPromise().then(() => {
return this.performHistoryResult((result as HistoryResult).history || [], reverse, isBackLimit, !isFirstMessageRender && additionMsgId); return this.performHistoryResult((result as HistoryResult).history || [], reverse, isBackLimit, !isAdditionRender && additionMsgId);
}); });
//return (reverse ? this.getHistoryTopPromise = promise : this.getHistoryBottomPromise = promise); //return (reverse ? this.getHistoryTopPromise = promise : this.getHistoryBottomPromise = promise);
//return this.performHistoryResult(result.history || [], reverse, isBackLimit, additionMsgID, true); //return this.performHistoryResult(result.history || [], reverse, isBackLimit, additionMsgID, true);
} }
const waitPromise = isFirstMessageRender ? processPromise(resultPromise) : promise; const waitPromise = isAdditionRender ? processPromise(resultPromise) : promise;
if(isFirstMessageRender) { if(isFirstMessageRender) {
waitPromise.then(() => { waitPromise.then(() => {
if(rootScope.settings.animationsEnabled) { if(rootScope.settings.animationsEnabled && Object.keys(this.bubbles).length) {
const mids = getObjectKeysAndSort(this.bubbles, 'desc').filter(mid => !additionMsgIds.includes(mid)); let sortedMids = getObjectKeysAndSort(this.bubbles, 'desc');
const animationPromise = deferredPromise<void>();
if(isAdditionRender && additionMsgIds.length) {
sortedMids = sortedMids.filter(mid => !additionMsgIds.includes(mid));
}
let targetMid: number;
if(backLimit) {
targetMid = maxId;
} else {
if(additionMsgId) {
targetMid = additionMsgId;
} else { // * if maxId === 0
targetMid = Math.max(...sortedMids);
}
}
let lastMsDelay = 0; const topIds = sortedMids.slice(sortedMids.findIndex(mid => targetMid > mid));
mids.forEach((mid, idx) => { const middleIds = isAdditionRender ? [] : [targetMid];
const bubble = this.bubbles[mid]; const bottomIds = sortedMids.slice(0, sortedMids.findIndex(mid => targetMid >= mid)).reverse();
/* this.log('getHistory: targeting mid:', targetMid,
topIds.map(m => this.appMessagesManager.getLocalMessageId(m)),
bottomIds.map(m => this.appMessagesManager.getLocalMessageId(m))); */
const delay = isAdditionRender ? 10 : 40;
const offsetIndex = isAdditionRender ? 0 : 1;
const animateAsLadder = (mids: number[], offsetIndex = 0) => {
const animationPromise = deferredPromise<void>();
let lastMsDelay = 0;
mids.forEach((mid, idx) => {
const bubble = this.bubbles[mid];
lastMsDelay = ((idx + offsetIndex) || 0.1) * delay;
//lastMsDelay = (idx || 0.1) * 1000;
//if(idx || isSafari) {
// ! 0.1 = 1ms задержка для Safari, без этого первое сообщение над самым нижним может появиться позже другого с animation-delay, LOL !
bubble.style.animationDelay = lastMsDelay + 'ms';
//}
lastMsDelay = ((idx || 0.1) * 10); bubble.classList.add('zoom-fade');
//if(idx || isSafari) { bubble.addEventListener('animationend', () => {
// ! 0.1 = 1ms задержка для Safari, без этого первое сообщение над самым нижним может появиться позже другого с animation-delay, LOL ! bubble.style.animationDelay = '';
bubble.style.animationDelay = lastMsDelay + 'ms'; bubble.classList.remove('zoom-fade');
//}
bubble.classList.add('zoom-fade'); if(idx === (mids.length - 1)) {
bubble.addEventListener('animationend', () => { animationPromise.resolve();
bubble.style.animationDelay = ''; }
bubble.classList.remove('zoom-fade'); }, {once: true});
//this.log('supa', bubble);
});
if(idx === (mids.length - 1)) { if(!mids.length) {
animationPromise.resolve(); animationPromise.resolve();
} }
}, {once: true});
//this.log('supa', bubble); return {lastMsDelay, animationPromise};
}); };
const topRes = animateAsLadder(topIds, offsetIndex);
const middleRes = animateAsLadder(middleIds);
const bottomRes = animateAsLadder(bottomIds, offsetIndex);
const promises = [topRes.animationPromise, middleRes.animationPromise, bottomRes.animationPromise];
const delays: number[] = [topRes.lastMsDelay, middleRes.lastMsDelay, bottomRes.lastMsDelay];
if(mids.length) { if(topIds.length || middleIds.length || bottomIds.length) {
dispatchHeavyAnimationEvent(animationPromise, lastMsDelay); dispatchHeavyAnimationEvent(Promise.all(promises), Math.max(...delays));
} }
} }

22
src/components/chat/input.ts

@ -491,7 +491,7 @@ export default class ChatInput {
} }
public saveDraft() { public saveDraft() {
if(!this.chat.peerId) return; if(!this.chat.peerId || this.editMsgId) return;
const entities: MessageEntity[] = []; const entities: MessageEntity[] = [];
const str = getRichValue(this.messageInputField.input, entities); const str = getRichValue(this.messageInputField.input, entities);
@ -537,17 +537,18 @@ export default class ChatInput {
} }
public setDraft(draft?: MyDraftMessage, fromUpdate = true) { public setDraft(draft?: MyDraftMessage, fromUpdate = true) {
if(!isInputEmpty(this.messageInput)) return; if(!isInputEmpty(this.messageInput)) return false;
if(!draft) { if(!draft) {
draft = this.appDraftsManager.getDraft(this.chat.peerId, this.chat.threadId); draft = this.appDraftsManager.getDraft(this.chat.peerId, this.chat.threadId);
if(!draft) { if(!draft) {
return; return false;
} }
} }
this.setInputValue(draft.rMessage, fromUpdate, fromUpdate); this.setInputValue(draft.rMessage, fromUpdate, fromUpdate);
return true;
} }
public finishPeerChange() { public finishPeerChange() {
@ -954,7 +955,9 @@ export default class ChatInput {
} }
} }
this.saveDraftDebounced(); if(!this.editMsgId) {
this.saveDraftDebounced();
}
this.updateSendBtn(); this.updateSendBtn();
}; };
@ -1105,7 +1108,7 @@ export default class ChatInput {
} }
}; };
public clearInput() { public clearInput(canSetDraft = true) {
if(isTouchSupported) { if(isTouchSupported) {
this.messageInput.innerText = ''; this.messageInput.innerText = '';
} else { } else {
@ -1118,7 +1121,14 @@ export default class ChatInput {
this.canUndoFromHTML = ''; this.canUndoFromHTML = '';
} }
this.onMessageInput(); let set = false;
if(canSetDraft) {
set = this.setDraft(undefined, false);
}
if(!set) {
this.onMessageInput();
}
} }
public isInputEmpty() { public isInputEmpty() {

4
src/components/misc.ts

@ -13,8 +13,8 @@ const set = (elem: HTMLElement | HTMLImageElement | SVGImageElement | HTMLVideoE
}; };
// проблема функции в том, что она не подходит для ссылок, пригодна только для blob'ов, потому что обычным ссылкам нужен 'load' каждый раз. // проблема функции в том, что она не подходит для ссылок, пригодна только для blob'ов, потому что обычным ссылкам нужен 'load' каждый раз.
export function renderImageFromUrl(elem: HTMLElement | HTMLImageElement | SVGImageElement | HTMLVideoElement, url: string, callback?: (err?: Event) => void): boolean { export function renderImageFromUrl(elem: HTMLElement | HTMLImageElement | SVGImageElement | HTMLVideoElement, url: string, callback?: (err?: Event) => void, useCache = false): boolean {
if((loadedURLs[url]/* && false */) || elem instanceof HTMLVideoElement) { if(((loadedURLs[url]/* && false */) && useCache) || elem instanceof HTMLVideoElement) {
set(elem, url); set(elem, url);
callback && callback(); callback && callback();
return true; return true;

2
src/components/preloader.ts

@ -69,7 +69,7 @@ export default class ProgressivePreloader {
promise.notify = null; promise.notify = null;
if(tempId === this.tempId) { if(tempId === this.tempId) {
if(successfully) { if(successfully && this.cancelable) {
this.setProgress(100); this.setProgress(100);
setTimeout(() => { // * wait for transition complete setTimeout(() => { // * wait for transition complete

4
src/helpers/fastSmoothScroll.ts

@ -83,7 +83,7 @@ export default function fastSmoothScroll(
}); });
}); });
return dispatchHeavyAnimationEvent(promise); return axis === 'y' ? dispatchHeavyAnimationEvent(promise) : promise;
} }
function scrollWithJs( function scrollWithJs(
@ -225,7 +225,7 @@ function scrollWithJs(
return t < 1; return t < 1;
}; };
if(!duration) { if(!duration || !path) {
cancelAnimationByKey(container); cancelAnimationByKey(container);
tick(); tick();
return Promise.resolve(); return Promise.resolve();

6
src/lib/appManagers/appDraftsManager.ts

@ -156,15 +156,13 @@ export class AppDraftsManager {
} else { } else {
draftObj = {_: 'draftMessage'} as any as DraftMessage.draftMessage; draftObj = {_: 'draftMessage'} as any as DraftMessage.draftMessage;
let message = localDraft.message; let message = localDraft.message;
let entities: MessageEntity[] = []; let entities: MessageEntity[] = localDraft.entities;
//message = RichTextProcessor.parseEmojis(message);
//message = RichTextProcessor.parseMarkdown(message, entities, true);
if(localDraft.reply_to_msg_id) { if(localDraft.reply_to_msg_id) {
params.reply_to_msg_id = draftObj.reply_to_msg_id = localDraft.reply_to_msg_id; params.reply_to_msg_id = draftObj.reply_to_msg_id = localDraft.reply_to_msg_id;
} }
if(entities.length) { if(entities?.length) {
params.entities = draftObj.entities = entities; params.entities = draftObj.entities = entities;
} }

2
src/lib/appManagers/appPeersManager.ts

@ -18,7 +18,7 @@ import appUsersManager from "./appUsersManager";
#2996ad 11 sea #2996ad 11 sea
#ce671b 5 orange #ce671b 5 orange
*/ */
const DialogColorsFg = ['#c03d33', '#4fad2d', '#d09306', '#168acd', '#8544d6', '#cd4073', '#2996ad', '#ce671b']; const DialogColorsFg = ['#fc5c51', '#0fb297', '#d09306', '#3d72ed', '#895dd5', '#cd4073', '#00c1a6', '#fa790f'];
const DialogColors = ['red', 'green', 'yellow', 'blue', 'violet', 'pink', 'cyan', 'orange']; const DialogColors = ['red', 'green', 'yellow', 'blue', 'violet', 'pink', 'cyan', 'orange'];
const DialogColorsMap = [0, 7, 4, 1, 6, 3, 5]; const DialogColorsMap = [0, 7, 4, 1, 6, 3, 5];

4
src/scss/partials/_badge.scss

@ -14,10 +14,6 @@
min-width: 1.25rem; min-width: 1.25rem;
line-height: 1.25rem !important; line-height: 1.25rem !important;
padding: 0 5.75px; padding: 0 5.75px;
html.is-safari & {
line-height: 22px !important;
}
} }
&-24 { &-24 {

3
src/scss/partials/_chatBubble.scss

@ -97,7 +97,8 @@ $bubble-margin: .25rem;
} */ } */
&.is-highlighted:after { &.is-highlighted:after {
background-color: rgba(0, 132, 255, .3); //background-color: rgba(0, 132, 255, .3);
background-color: rgba(77, 142, 80, .4);
body:not(.animation-level-0) & { body:not(.animation-level-0) & {
animation: bubbleSelected 2s linear; animation: bubbleSelected 2s linear;

1
src/scss/partials/_input.scss

@ -140,6 +140,7 @@
padding: 0 6px; padding: 0 6px;
opacity: 1; opacity: 1;
left: .75rem; left: .75rem;
font-weight: 500;
} }
} }
} }

4
src/scss/partials/_leftSidebar.scss

@ -106,6 +106,10 @@
} }
} }
.folders-tabs-scrollable li:first-child {
margin-left: .6875rem;
}
.item-main { .item-main {
.input-search { .input-search {
/* &-input { /* &-input {

Loading…
Cancel
Save