Browse Source

Swipe animation for profile avatars

Search refactor
Fix dialogs slicing
master
Eduard Kuzmenko 4 years ago
parent
commit
3119e7788a
  1. 8
      src/components/appMediaViewer.ts
  2. 2764
      src/components/appMediaViewerNew.ts
  3. 226
      src/components/appSearchSuper..ts
  4. 2
      src/components/chat/bubbles.ts
  5. 12
      src/components/sidebarLeft/index.ts
  6. 104
      src/components/sidebarRight/tabs/sharedMedia.ts
  7. 1
      src/components/slider.ts
  8. 107
      src/components/swipeHandler.ts
  9. 2
      src/config/app.ts
  10. 16
      src/helpers/middleware.ts
  11. 2
      src/index.hbs
  12. 1
      src/lang.ts
  13. 17
      src/lib/appManagers/appDialogsManager.ts
  14. 10
      src/scss/partials/_leftSidebar.scss
  15. 28
      src/scss/partials/_profile.scss
  16. 21
      src/scss/partials/_rightSidebar.scss
  17. 2
      src/scss/style.scss

8
src/components/appMediaViewer.ts

@ -194,7 +194,9 @@ class AppMediaViewerBase<ContentAdditionType extends string, ButtonsAdditionType
this.wholeDiv.addEventListener('click', this.onClick); this.wholeDiv.addEventListener('click', this.onClick);
if(isTouchSupported) { if(isTouchSupported) {
const swipeHandler = new SwipeHandler(this.wholeDiv, (xDiff, yDiff) => { const swipeHandler = new SwipeHandler({
element: this.wholeDiv,
onSwipe: (xDiff, yDiff) => {
if(VideoPlayer.isFullScreen()) { if(VideoPlayer.isFullScreen()) {
return; return;
} }
@ -220,13 +222,15 @@ class AppMediaViewerBase<ContentAdditionType extends string, ButtonsAdditionType
} }
return false; return false;
}, (evt) => { },
verifyTouchTarget: (evt) => {
// * Fix for seek input // * Fix for seek input
if((evt.target as HTMLElement).tagName === 'INPUT' || findUpClassName(evt.target, 'media-viewer-caption')) { if((evt.target as HTMLElement).tagName === 'INPUT' || findUpClassName(evt.target, 'media-viewer-caption')) {
return false; return false;
} }
return true; return true;
}
}); });
} }
} }

2764
src/components/appMediaViewerNew.ts

File diff suppressed because it is too large Load Diff

226
src/components/appSearchSuper..ts

@ -1,6 +1,6 @@
import { formatDateAccordingToToday, months } from "../helpers/date"; import { formatDateAccordingToToday, months } from "../helpers/date";
import { positionElementByIndex } from "../helpers/dom"; import { positionElementByIndex } from "../helpers/dom";
import { copy, getObjectKeysAndSort } from "../helpers/object"; import { copy, getObjectKeysAndSort, safeAssign } from "../helpers/object";
import { escapeRegExp, limitSymbols } from "../helpers/string"; import { escapeRegExp, limitSymbols } from "../helpers/string";
import appChatsManager from "../lib/appManagers/appChatsManager"; import appChatsManager from "../lib/appManagers/appChatsManager";
import appDialogsManager from "../lib/appManagers/appDialogsManager"; import appDialogsManager from "../lib/appManagers/appDialogsManager";
@ -25,11 +25,11 @@ import useHeavyAnimationCheck, { getHeavyAnimationPromise } from "../hooks/useHe
import { isSafari } from "../helpers/userAgent"; import { isSafari } from "../helpers/userAgent";
import { LangPackKey, i18n } from "../lib/langPack"; import { LangPackKey, i18n } from "../lib/langPack";
import findUpClassName from "../helpers/dom/findUpClassName"; import findUpClassName from "../helpers/dom/findUpClassName";
import renderImageFromUrl from "../helpers/dom/renderImageFromUrl"; import { getMiddleware } from "../helpers/middleware";
//const testScroll = false; //const testScroll = false;
export type SearchSuperType = MyInputMessagesFilter/* | 'chats' */; export type SearchSuperType = MyInputMessagesFilter/* | 'members' */;
export type SearchSuperContext = { export type SearchSuperContext = {
peerId: number, peerId: number,
inputFilter: MyInputMessagesFilter, inputFilter: MyInputMessagesFilter,
@ -43,10 +43,20 @@ export type SearchSuperContext = {
maxDate?: number maxDate?: number
}; };
export type SearchSuperMediaType = 'members' | 'media' | 'files' | 'links' | 'music' | 'chats' | 'voice';
export type SearchSuperMediaTab = {
inputFilter: SearchSuperType,
name: LangPackKey,
type: SearchSuperMediaType,
contentTab?: HTMLElement,
menuTab?: HTMLElement,
scroll?: {scrollTop: number, scrollHeight: number}
};
export default class AppSearchSuper { export default class AppSearchSuper {
public tabs: {[t in SearchSuperType]: HTMLDivElement} = {} as any; public tabs: {[t in SearchSuperType]: HTMLDivElement} = {} as any;
public type: SearchSuperType; public mediaTab: SearchSuperMediaTab;
public tabSelected: HTMLElement; public tabSelected: HTMLElement;
public container: HTMLElement; public container: HTMLElement;
@ -56,7 +66,7 @@ export default class AppSearchSuper {
private prevTabId = -1; private prevTabId = -1;
private lazyLoadQueue = new LazyLoadQueue(); private lazyLoadQueue = new LazyLoadQueue();
private cleanupObj = {cleaned: false}; public middleware = getMiddleware();
public historyStorage: Partial<{[type in SearchSuperType]: {mid: number, peerId: number}[]}> = {}; public historyStorage: Partial<{[type in SearchSuperType]: {mid: number, peerId: number}[]}> = {};
public usedFromHistory: Partial<{[type in SearchSuperType]: number}> = {}; public usedFromHistory: Partial<{[type in SearchSuperType]: number}> = {};
@ -84,9 +94,19 @@ export default class AppSearchSuper {
private searchGroupMedia: SearchGroup; private searchGroupMedia: SearchGroup;
public goingHard: Partial<{[type in MyInputMessagesFilter]: {scrollTop: number, scrollHeight: number}}> = {}; public mediaTabsMap: Map<SearchSuperMediaType, SearchSuperMediaTab> = new Map();
// * arguments
public mediaTabs: SearchSuperMediaTab[];
public scrollable: Scrollable;
public searchGroups?: {[group in SearchGroupType]: SearchGroup};
public asChatList? = false;
public groupByMonth? = true;
public hideEmptyTabs? = true;
constructor(options: Pick<AppSearchSuper, 'mediaTabs' | 'scrollable' | 'searchGroups' | 'asChatList' | 'groupByMonth' | 'hideEmptyTabs'>) {
safeAssign(this, options);
constructor(public types: {inputFilter: SearchSuperType, name: LangPackKey, type: string}[], public scrollable: Scrollable, public searchGroups?: {[group in SearchGroupType]: SearchGroup}, public asChatList = false, public groupByMonth = true) {
this.container = document.createElement('div'); this.container = document.createElement('div');
this.container.classList.add('search-super'); this.container.classList.add('search-super');
@ -101,13 +121,13 @@ export default class AppSearchSuper {
navScrollable.container.append(nav); navScrollable.container.append(nav);
for(const type of types) { for(const mediaTab of this.mediaTabs) {
const menuTab = document.createElement('div'); const menuTab = document.createElement('div');
menuTab.classList.add('menu-horizontal-div-item'); menuTab.classList.add('menu-horizontal-div-item');
const span = document.createElement('span'); const span = document.createElement('span');
const i = document.createElement('i'); const i = document.createElement('i');
span.append(i18n(type.name)); span.append(i18n(mediaTab.name));
span.append(i); span.append(i);
menuTab.append(span); menuTab.append(span);
@ -115,38 +135,29 @@ export default class AppSearchSuper {
ripple(menuTab); ripple(menuTab);
this.tabsMenu.append(menuTab); this.tabsMenu.append(menuTab);
this.mediaTabsMap.set(mediaTab.type, mediaTab);
mediaTab.menuTab = menuTab;
} }
this.tabsContainer = document.createElement('div'); this.tabsContainer = document.createElement('div');
this.tabsContainer.classList.add('search-super-tabs-container', 'tabs-container'); this.tabsContainer.classList.add('search-super-tabs-container', 'tabs-container');
for(const type of types) { for(const mediaTab of this.mediaTabs) {
const container = document.createElement('div'); const container = document.createElement('div');
container.classList.add('search-super-container-' + type.type/* , 'scrollable', 'scrollable-y' */); container.classList.add('search-super-container-' + mediaTab.type);
const content = document.createElement('div'); const content = document.createElement('div');
content.classList.add('search-super-content-' + type.type/* , 'scrollable', 'scrollable-y' */); content.classList.add('search-super-content-' + mediaTab.type);
//content.style.overflowY = 'hidden';
/* container.style.overflow = 'visible';
const v = 236;
content.style.top = (v * -1) + 'px';
content.style.paddingTop = v + 'px';
content.style.height = `calc(100% + ${v}px)`;
content.style.maxHeight = `calc(100% + ${v}px)`;
content.addEventListener('scroll', (e) => {
const scrollTop = content.scrollTop;
if(scrollTop <= v) {
//this.scrollable.scrollTop = scrollTop;
(this.container.previousElementSibling as HTMLElement).style.transform = `translateY(-${scrollTop}px)`;
}
}); */
container.append(content); container.append(content);
this.tabsContainer.append(container); this.tabsContainer.append(container);
this.tabs[type.inputFilter] = content; this.tabs[mediaTab.inputFilter] = content;
mediaTab.contentTab = content;
} }
this.container.append(navScrollableContainer, this.tabsContainer); this.container.append(navScrollableContainer, this.tabsContainer);
@ -170,23 +181,23 @@ export default class AppSearchSuper {
this.onTransitionStart(); this.onTransitionStart();
} }
this.goingHard[this.type] = {scrollTop: this.scrollable.scrollTop, scrollHeight: this.scrollable.scrollHeight}; this.mediaTab.scroll = {scrollTop: this.scrollable.scrollTop, scrollHeight: this.scrollable.scrollHeight};
const newType = this.types[id].inputFilter; const newMediaTab = this.mediaTabs[id];
this.tabSelected = tabContent.firstElementChild as HTMLDivElement; this.tabSelected = tabContent.firstElementChild as HTMLDivElement;
if(this.goingHard[newType] === undefined) { if(newMediaTab.scroll === undefined) {
const rect = this.container.getBoundingClientRect(); const rect = this.container.getBoundingClientRect();
const rect2 = this.container.parentElement.getBoundingClientRect(); const rect2 = this.container.parentElement.getBoundingClientRect();
const diff = rect.y - rect2.y; const diff = rect.y - rect2.y;
if(this.scrollable.scrollTop > diff) { if(this.scrollable.scrollTop > diff) {
this.goingHard[newType] = {scrollTop: diff, scrollHeight: 0}; newMediaTab.scroll = {scrollTop: diff, scrollHeight: 0};
} }
} }
if(this.goingHard[newType]) { if(newMediaTab.scroll) {
const diff = this.goingHard[this.type].scrollTop - this.goingHard[newType].scrollTop; const diff = this.mediaTab.scroll.scrollTop - newMediaTab.scroll.scrollTop;
//console.log('what you gonna do', this.goingHard, diff); //console.log('what you gonna do', this.goingHard, diff);
if(diff/* && diff < 0 */) { if(diff/* && diff < 0 */) {
@ -194,7 +205,7 @@ export default class AppSearchSuper {
} }
} }
this.type = newType; this.mediaTab = newMediaTab;
/* if(this.prevTabId !== -1 && nav.offsetTop) { /* if(this.prevTabId !== -1 && nav.offsetTop) {
this.scrollable.scrollTop -= nav.offsetTop; this.scrollable.scrollTop -= nav.offsetTop;
@ -213,9 +224,9 @@ export default class AppSearchSuper {
this.scrollable.onScroll(); this.scrollable.onScroll();
//console.log('what y', this.tabSelected.style.transform); //console.log('what y', this.tabSelected.style.transform);
if(this.goingHard[this.type] !== undefined) { if(this.mediaTab.scroll !== undefined) {
this.tabSelected.style.transform = ''; this.tabSelected.style.transform = '';
this.scrollable.scrollTop = this.goingHard[this.type].scrollTop; this.scrollable.scrollTop = this.mediaTab.scroll.scrollTop;
} }
this.onTransitionEnd(); this.onTransitionEnd();
@ -241,11 +252,11 @@ export default class AppSearchSuper {
const message = appMessagesManager.getMessageByPeer(peerId, mid); const message = appMessagesManager.getMessageByPeer(peerId, mid);
new AppMediaViewer() new AppMediaViewer()
.setSearchContext(this.copySearchContext(this.type)) .setSearchContext(this.copySearchContext(this.mediaTab.inputFilter))
.openMedia(message, target, 0, false, targets.slice(0, idx), targets.slice(idx + 1)); .openMedia(message, target, 0, false, targets.slice(0, idx), targets.slice(idx + 1));
}); });
this.type = this.types[0].inputFilter; this.mediaTab = this.mediaTabs[0];
useHeavyAnimationCheck(() => { useHeavyAnimationCheck(() => {
this.lazyLoadQueue.lock(); this.lazyLoadQueue.lock();
@ -381,7 +392,7 @@ export default class AppSearchSuper {
const elemsToAppend: {element: HTMLElement, message: any}[] = []; const elemsToAppend: {element: HTMLElement, message: any}[] = [];
const sharedMediaDiv: HTMLElement = this.tabs[type]; const sharedMediaDiv: HTMLElement = this.tabs[type];
const promises: Promise<any>[] = []; const promises: Promise<any>[] = [];
const middleware = this.getMiddleware(); const middleware = this.middleware.get();
await getHeavyAnimationPromise(); await getHeavyAnimationPromise();
@ -538,23 +549,22 @@ export default class AppSearchSuper {
//this.log('wrapping webpage', webpage); //this.log('wrapping webpage', webpage);
previewDiv.innerHTML = RichTextProcessor.getAbbreviation(webpage.title || webpage.display_url || webpage.description || webpage.url, true);
previewDiv.classList.add('empty');
if(webpage.photo) { if(webpage.photo) {
let load = () => appPhotosManager.preloadPhoto(webpage.photo.id, appPhotosManager.choosePhotoSize(webpage.photo, 60, 60)) const res = wrapPhoto({
.then(() => { container: previewDiv,
if(!middleware()) { message: null,
//this.log.warn('peer changed'); photo: webpage.photo,
return; boxWidth: 0,
} boxHeight: 0,
withoutPreloader: true,
previewDiv.classList.remove('empty'); lazyLoadQueue: this.lazyLoadQueue,
middleware,
previewDiv.innerText = ''; size: appPhotosManager.choosePhotoSize(webpage.photo, 60, 60, false),
renderImageFromUrl(previewDiv, webpage.photo.url); loadPromises: promises
}); });
} else {
this.lazyLoadQueue.push({div: previewDiv, load}); previewDiv.classList.add('empty');
previewDiv.innerHTML = RichTextProcessor.getAbbreviation(webpage.title || webpage.display_url || webpage.description || webpage.url, true);
} }
let title = webpage.rTitle || ''; let title = webpage.rTitle || '';
@ -624,16 +634,16 @@ export default class AppSearchSuper {
//} //}
} }
private afterPerforming(length: number, tab: HTMLElement) { private afterPerforming(length: number, contentTab: HTMLElement) {
if(tab) { if(contentTab) {
const parent = tab.parentElement; const parent = contentTab.parentElement;
Array.from(parent.children).slice(1).forEach(child => { Array.from(parent.children).slice(1).forEach(child => {
child.remove(); child.remove();
}); });
//this.contentContainer.classList.add('loaded'); //this.contentContainer.classList.add('loaded');
if(!length && !tab.childElementCount) { if(!length && !contentTab.childElementCount) {
const div = document.createElement('div'); const div = document.createElement('div');
div.innerText = 'Nothing interesting here yet...'; div.innerText = 'Nothing interesting here yet...';
div.classList.add('position-center', 'text-center', 'content-empty', 'no-select'); div.classList.add('position-center', 'text-center', 'content-empty', 'no-select');
@ -645,7 +655,7 @@ export default class AppSearchSuper {
private loadChats() { private loadChats() {
const renderedPeerIds: Set<number> = new Set(); const renderedPeerIds: Set<number> = new Set();
const middleware = this.getMiddleware(); const middleware = this.middleware.get();
for(let i in this.searchGroups) { for(let i in this.searchGroups) {
const group = this.searchGroups[i as SearchGroupType]; const group = this.searchGroups[i as SearchGroupType];
@ -808,32 +818,14 @@ export default class AppSearchSuper {
} else return Promise.resolve(); } else return Promise.resolve();
} }
public load(single = false, justLoad = false) { private loadType(mediaTab: SearchSuperMediaTab, justLoad: boolean, loadCount: number, middleware: () => boolean) {
// if(testScroll/* || 1 === 1 */) { const type = mediaTab.inputFilter;
// return;
// }
//return;
const peerId = this.searchContext.peerId;
this.log('load', single, peerId, this.loadPromises);
let typesToLoad = single ? [this.type] : this.types.filter(t => t.inputFilter !== this.type).map(t => t.inputFilter); if(this.loadPromises[type]) {
typesToLoad = typesToLoad.filter(type => !this.loaded[type] return this.loadPromises[type];
|| (this.historyStorage[type] && this.usedFromHistory[type] < this.historyStorage[type].length)); }
if(!typesToLoad.length) return;
const loadCount = justLoad ? 50 : Math.round((appPhotosManager.windowH / 130 | 0) * 3 * 1.25); // that's good for all types
const historyStorage = this.historyStorage ?? (this.historyStorage = {});
const middleware = this.getMiddleware();
const promises: Promise<any>[] = typesToLoad.map(type => {
if(this.loadPromises[type]) return this.loadPromises[type];
const history = historyStorage[type] ?? (historyStorage[type] = []); const history = this.historyStorage[type] ?? (this.historyStorage[type] = []);
if(type === 'inputMessagesFilterEmpty' && !history.length) { if(type === 'inputMessagesFilterEmpty' && !history.length) {
if(!this.loadedChats) { if(!this.loadedChats) {
@ -889,7 +881,7 @@ export default class AppSearchSuper {
//let loadCount = history.length ? 50 : 15; //let loadCount = history.length ? 50 : 15;
return this.loadPromises[type] = appMessagesManager.getSearch({ return this.loadPromises[type] = appMessagesManager.getSearch({
peerId, peerId: this.searchContext.peerId,
query: this.searchContext.query, query: this.searchContext.query,
inputFilter: {_: type}, inputFilter: {_: type},
maxId, maxId,
@ -929,7 +921,7 @@ export default class AppSearchSuper {
setTimeout(() => { setTimeout(() => {
if(!middleware()) return; if(!middleware()) return;
//this.log('will preload more'); //this.log('will preload more');
if(this.type === type) { if(this.mediaTab === mediaTab) {
const promise = this.load(true, true); const promise = this.load(true, true);
if(promise) { if(promise) {
promise.then(() => { promise.then(() => {
@ -953,6 +945,33 @@ export default class AppSearchSuper {
}).finally(() => { }).finally(() => {
this.loadPromises[type] = null; this.loadPromises[type] = null;
}); });
}
public load(single = false, justLoad = false) {
// if(testScroll/* || 1 === 1 */) {
// return;
// }
//return;
const peerId = this.searchContext.peerId;
this.log('load', single, peerId, this.loadPromises);
let toLoad = single ? [this.mediaTab] : this.mediaTabs.filter(t => t !== this.mediaTab);
toLoad = toLoad.filter(mediaTab => {
const inputFilter = mediaTab.inputFilter;
return !this.loaded[inputFilter] || (this.historyStorage[inputFilter] && this.usedFromHistory[inputFilter] < this.historyStorage[inputFilter].length);
});
if(!toLoad.length) {
return;
}
const loadCount = justLoad ? 50 : Math.round((appPhotosManager.windowH / 130 | 0) * 3 * 1.25); // that's good for all types
const middleware = this.middleware.get();
const promises: Promise<any>[] = toLoad.map(mediaTab => {
return this.loadType(mediaTab, justLoad, loadCount, middleware)
}); });
return Promise.all(promises).catch(err => { return Promise.all(promises).catch(err => {
@ -1006,13 +1025,18 @@ export default class AppSearchSuper {
this.lazyLoadQueue.clear(); this.lazyLoadQueue.clear();
this.types.forEach(type => { this.mediaTabs.forEach(mediaTab => {
this.usedFromHistory[type.inputFilter] = -1; this.usedFromHistory[mediaTab.inputFilter] = -1;
}); });
this.cleanupObj.cleaned = true; this.middleware.clean();
this.cleanupObj = {cleaned: false}; this.cleanScrollPositions();
this.goingHard = {}; }
public cleanScrollPositions() {
this.mediaTabs.forEach(mediaTab => {
mediaTab.scroll = undefined;
});
} }
public cleanupHTML() { public cleanupHTML() {
@ -1023,15 +1047,15 @@ export default class AppSearchSuper {
this.urlsToRevoke.length = 0; this.urlsToRevoke.length = 0;
} }
(Object.keys(this.tabs) as SearchSuperType[]).forEach(type => { this.mediaTabs.forEach((tab) => {
this.tabs[type].innerHTML = ''; tab.contentTab.innerHTML = '';
if(type === 'inputMessagesFilterEmpty') { if(tab.type === 'chats') {
return; return;
} }
if(!this.historyStorage || !this.historyStorage[type]) { if(!this.historyStorage[tab.inputFilter]) {
const parent = this.tabs[type].parentElement; const parent = tab.contentTab.parentElement;
//if(!testScroll) { //if(!testScroll) {
if(!parent.querySelector('.preloader')) { if(!parent.querySelector('.preloader')) {
putPreloader(parent, true); putPreloader(parent, true);
@ -1061,14 +1085,6 @@ export default class AppSearchSuper {
} */ } */
} }
// * will change .cleaned in cleanup() and new instance will be created
public getMiddleware() {
const cleanupObj = this.cleanupObj;
return () => {
return !cleanupObj.cleaned;
};
}
private copySearchContext(newInputFilter: MyInputMessagesFilter) { private copySearchContext(newInputFilter: MyInputMessagesFilter) {
const context = copy(this.searchContext); const context = copy(this.searchContext);
context.inputFilter = newInputFilter; context.inputFilter = newInputFilter;
@ -1088,7 +1104,7 @@ export default class AppSearchSuper {
this.searchContext = { this.searchContext = {
peerId: peerId || 0, peerId: peerId || 0,
query: query || '', query: query || '',
inputFilter: this.type, inputFilter: this.mediaTab.inputFilter,
threadId, threadId,
folderId, folderId,
minDate, minDate,

2
src/components/chat/bubbles.ts

@ -829,7 +829,7 @@ export default class ChatBubbles {
//appSidebarRight.forwardTab.open([mid]); //appSidebarRight.forwardTab.open([mid]);
return; return;
} else if(target.classList.contains('peer-title') || target.classList.contains('name')) { } else if(target.classList.contains('peer-title') || target.classList.contains('name')) {
target = findUpClassName(target, 'name'); target = findUpClassName(target, 'name') || target;
const peerId = +target.dataset.peerId; const peerId = +target.dataset.peerId;
const savedFrom = target.dataset.savedFrom; const savedFrom = target.dataset.savedFrom;
if(savedFrom) { if(savedFrom) {

12
src/components/sidebarLeft/index.ts

@ -193,7 +193,8 @@ export class AppSidebarLeft extends SidebarSlider {
recent: new SearchGroup('Recent', 'contacts', true, 'search-group-recent', true, true, close) recent: new SearchGroup('Recent', 'contacts', true, 'search-group-recent', true, true, close)
}; };
const searchSuper = this.searchSuper = new AppSearchSuper([{ const searchSuper = this.searchSuper = new AppSearchSuper({
mediaTabs: [{
inputFilter: 'inputMessagesFilterEmpty', inputFilter: 'inputMessagesFilterEmpty',
name: 'FilterChats', name: 'FilterChats',
type: 'chats' type: 'chats'
@ -217,7 +218,12 @@ export class AppSidebarLeft extends SidebarSlider {
inputFilter: 'inputMessagesFilterVoice', inputFilter: 'inputMessagesFilterVoice',
name: 'SharedVoiceTab2', name: 'SharedVoiceTab2',
type: 'voice' type: 'voice'
}], scrollable, this.searchGroups, true); }],
scrollable,
searchGroups: this.searchGroups,
asChatList: true,
hideEmptyTabs: false
});
searchContainer.prepend(searchSuper.nav.parentElement.parentElement); searchContainer.prepend(searchSuper.nav.parentElement.parentElement);
scrollable.container.append(searchSuper.container); scrollable.container.append(searchSuper.container);
@ -347,7 +353,7 @@ export class AppSidebarLeft extends SidebarSlider {
} }
if(!selectedPeerId && value.trim()) { if(!selectedPeerId && value.trim()) {
const middleware = searchSuper.getMiddleware(); const middleware = searchSuper.middleware.get();
Promise.all([ Promise.all([
appMessagesManager.getConversationsAll(value).then(dialogs => dialogs.map(d => d.peerId)), appMessagesManager.getConversationsAll(value).then(dialogs => dialogs.map(d => d.peerId)),
appUsersManager.getContacts(value, true) appUsersManager.getContacts(value, true)

104
src/components/sidebarRight/tabs/sharedMedia.ts

@ -10,7 +10,7 @@ import AppSearchSuper, { SearchSuperType } from "../../appSearchSuper.";
import AvatarElement from "../../avatar"; import AvatarElement from "../../avatar";
import SidebarSlider, { SliderSuperTab } from "../../slider"; import SidebarSlider, { SliderSuperTab } from "../../slider";
import CheckboxField from "../../checkboxField"; import CheckboxField from "../../checkboxField";
import { attachClickEvent, replaceContent } from "../../../helpers/dom"; import { attachClickEvent, replaceContent, whichChild } from "../../../helpers/dom";
import appSidebarRight from ".."; import appSidebarRight from "..";
import { TransitionSlider } from "../../transition"; import { TransitionSlider } from "../../transition";
import appNotificationsManager from "../../../lib/appManagers/appNotificationsManager"; import appNotificationsManager from "../../../lib/appManagers/appNotificationsManager";
@ -32,6 +32,7 @@ import { safeAssign } from "../../../helpers/object";
import { forEachReverse } from "../../../helpers/array"; import { forEachReverse } from "../../../helpers/array";
import appPhotosManager from "../../../lib/appManagers/appPhotosManager"; import appPhotosManager from "../../../lib/appManagers/appPhotosManager";
import renderImageFromUrl from "../../../helpers/dom/renderImageFromUrl"; import renderImageFromUrl from "../../../helpers/dom/renderImageFromUrl";
import SwipeHandler from "../../swipeHandler";
let setText = (text: string, row: Row) => { let setText = (text: string, row: Row) => {
fastRaf(() => { fastRaf(() => {
@ -175,7 +176,13 @@ class PeerProfileAvatars {
this.container.append(this.avatars, this.info, this.tabs); this.container.append(this.avatars, this.info, this.tabs);
let cancel = false;
attachClickEvent(this.container, (_e) => { attachClickEvent(this.container, (_e) => {
if(cancel) {
cancel = false;
return;
}
const rect = this.container.getBoundingClientRect(); const rect = this.container.getBoundingClientRect();
const e = (_e as TouchEvent).touches ? (_e as TouchEvent).touches[0] : _e as MouseEvent; const e = (_e as TouchEvent).touches ? (_e as TouchEvent).touches[0] : _e as MouseEvent;
@ -184,7 +191,57 @@ class PeerProfileAvatars {
const centerX = rect.right - (rect.width / 2); const centerX = rect.right - (rect.width / 2);
const toRight = x > centerX; const toRight = x > centerX;
// this.avatars.classList.remove('no-transition');
// fastRaf(() => {
this.listLoader.go(toRight ? 1 : -1); this.listLoader.go(toRight ? 1 : -1);
// });
});
let width = 0, x = 0, lastDiffX = 0, lastIndex = 0, minX = 0;
const swipeHandler = new SwipeHandler({
element: this.avatars,
onSwipe: (xDiff, yDiff) => {
lastDiffX = xDiff;
let lastX = x + xDiff * -2;
if(lastX > 0) lastX = 0;
else if(lastX < minX) lastX = minX;
this.avatars.style.transform = `translate3d(${lastX}px, 0, -1px) scale(2)`;
//console.log(xDiff, yDiff);
return false;
},
verifyTouchTarget: (e) => {
if(this.tabs.classList.contains('hide')) {
return false;
}
return true;
},
onFirstSwipe: () => {
const rect = this.avatars.getBoundingClientRect();
width = rect.width;
minX = -width * (this.tabs.childElementCount - 1);
/* lastIndex = whichChild(this.tabs.querySelector('.active'));
x = -width * lastIndex; */
x = rect.left - this.container.getBoundingClientRect().left;
this.avatars.style.transform = `translate3d(${x}px, 0, -1px) scale(2)`;
this.avatars.classList.add('no-transition');
void this.avatars.offsetLeft; // reflow
},
onReset: () => {
const addIndex = Math.ceil(Math.abs(lastDiffX) / (width / 2)) * (lastDiffX >= 0 ? 1 : -1);
cancel = true;
//console.log(addIndex);
this.avatars.classList.remove('no-transition');
fastRaf(() => {
this.listLoader.go(addIndex);
});
}
}); });
} }
@ -211,7 +268,7 @@ class PeerProfileAvatars {
onJump: (item, older) => { onJump: (item, older) => {
const id = this.listLoader.index; const id = this.listLoader.index;
//const nextId = Math.max(0, id); //const nextId = Math.max(0, id);
this.avatars.style.transform = `translateX(-${100 * id}%)`; this.avatars.style.transform = `translate3d(-${200 * id}%, 0, -1px) scale(2)`;
const activeTab = this.tabs.querySelector('.active'); const activeTab = this.tabs.querySelector('.active');
if(activeTab) activeTab.classList.remove('active'); if(activeTab) activeTab.classList.remove('active');
@ -235,6 +292,8 @@ class PeerProfileAvatars {
if(this.tabs.childElementCount === 1) { if(this.tabs.childElementCount === 1) {
tab.classList.add('active'); tab.classList.add('active');
} }
this.tabs.classList.toggle('hide', this.tabs.childElementCount <= 1);
} }
public processItem = (photoId: string) => { public processItem = (photoId: string) => {
@ -242,16 +301,19 @@ class PeerProfileAvatars {
avatar.classList.add(PeerProfileAvatars.BASE_CLASS + '-avatar'); avatar.classList.add(PeerProfileAvatars.BASE_CLASS + '-avatar');
const photo = appPhotosManager.getPhoto(photoId); const photo = appPhotosManager.getPhoto(photoId);
const img = new Image();
img.classList.add(PeerProfileAvatars.BASE_CLASS + '-avatar-image');
img.draggable = false;
if(photo) { if(photo) {
appPhotosManager.preloadPhoto(photo, appPhotosManager.choosePhotoSize(photo, 420, 420, false)).then(() => { appPhotosManager.preloadPhoto(photo, appPhotosManager.choosePhotoSize(photo, 420, 420, false)).then(() => {
const img = new Image();
renderImageFromUrl(img, photo.url, () => { renderImageFromUrl(img, photo.url, () => {
avatar.append(img); avatar.append(img);
}); });
}); });
} else { } else {
const photo = appPeersManager.getPeerPhoto(this.peerId); const photo = appPeersManager.getPeerPhoto(this.peerId);
appProfileManager.putAvatar(avatar, this.peerId, photo, 'photo_big'); appProfileManager.putAvatar(avatar, this.peerId, photo, 'photo_big', img);
} }
this.avatars.append(avatar); this.avatars.append(avatar);
@ -636,19 +698,10 @@ export default class AppSharedMediaTab extends SliderSuperTab {
transition(+isSharedMedia); transition(+isSharedMedia);
if(!isSharedMedia) { if(!isSharedMedia) {
this.searchSuper.goingHard = {}; this.searchSuper.cleanScrollPositions();
} }
}; };
this.scrollable.container.addEventListener('scroll', () => {
if(this.profile.avatars) {
const scrollTop = this.scrollable.scrollTop;
const y = scrollTop / 2;
this.profile.avatars.avatars.style.transform = `translateY(${y}px)`;
//this.profile.avatars.tabs.style.transform = `translateY(${scrollTop}px)`;
}
});
const transition = TransitionSlider(transitionContainer, 'slide-fade', 400, null, false); const transition = TransitionSlider(transitionContainer, 'slide-fade', 400, null, false);
transition(0); transition(0);
@ -686,7 +739,12 @@ export default class AppSharedMediaTab extends SliderSuperTab {
//this.container.prepend(this.closeBtn.parentElement); //this.container.prepend(this.closeBtn.parentElement);
this.searchSuper = new AppSearchSuper([{ this.searchSuper = new AppSearchSuper({
mediaTabs: [{
inputFilter: 'inputMessagesFilterEmpty',
name: 'PeerMedia.Members',
type: 'members'
}, {
inputFilter: 'inputMessagesFilterPhotoVideo', inputFilter: 'inputMessagesFilterPhotoVideo',
name: 'SharedMediaTab2', name: 'SharedMediaTab2',
type: 'media' type: 'media'
@ -702,9 +760,9 @@ export default class AppSharedMediaTab extends SliderSuperTab {
inputFilter: 'inputMessagesFilterMusic', inputFilter: 'inputMessagesFilterMusic',
name: 'SharedMusicTab2', name: 'SharedMusicTab2',
type: 'music' type: 'music'
}], this.scrollable/* , undefined, undefined, false */); }],
scrollable: this.scrollable
this.profile.element.append(this.searchSuper.container); });
} }
public renderNewMessages(peerId: number, mids: number[]) { public renderNewMessages(peerId: number, mids: number[]) {
@ -713,7 +771,7 @@ export default class AppSharedMediaTab extends SliderSuperTab {
if(!this.historiesStorage[peerId]) return; if(!this.historiesStorage[peerId]) return;
mids = mids.slice().reverse(); // ! because it will be ascend sorted array mids = mids.slice().reverse(); // ! because it will be ascend sorted array
for(const type of this.searchSuper.types) { for(const type of this.searchSuper.mediaTabs) {
const inputFilter = type.inputFilter; const inputFilter = type.inputFilter;
const filtered = this.searchSuper.filterMessagesByType(mids.map(mid => appMessagesManager.getMessageByPeer(peerId, mid)), inputFilter); const filtered = this.searchSuper.filterMessagesByType(mids.map(mid => appMessagesManager.getMessageByPeer(peerId, mid)), inputFilter);
if(filtered.length) { if(filtered.length) {
@ -737,7 +795,7 @@ export default class AppSharedMediaTab extends SliderSuperTab {
if(!this.historiesStorage[peerId]) return; if(!this.historiesStorage[peerId]) return;
for(const mid of mids) { for(const mid of mids) {
for(const type of this.searchSuper.types) { for(const type of this.searchSuper.mediaTabs) {
const inputFilter = type.inputFilter; const inputFilter = type.inputFilter;
if(!this.historiesStorage[peerId][inputFilter]) continue; if(!this.historiesStorage[peerId][inputFilter]) continue;
@ -773,6 +831,10 @@ export default class AppSharedMediaTab extends SliderSuperTab {
this.editBtn.style.display = 'none'; this.editBtn.style.display = 'none';
this.searchSuper.cleanupHTML(); this.searchSuper.cleanupHTML();
this.searchSuper.selectTab(0, false); this.searchSuper.selectTab(0, false);
if(!this.searchSuper.container.parentElement) {
this.profile.element.append(this.searchSuper.container);
}
} }
public setLoadMutex(promise: Promise<any>) { public setLoadMutex(promise: Promise<any>) {
@ -800,6 +862,8 @@ export default class AppSharedMediaTab extends SliderSuperTab {
} }
public fillProfileElements() { public fillProfileElements() {
this.cleanupHTML();
this.profile.fillProfileElements(); this.profile.fillProfileElements();
if(this.peerId > 0) { if(this.peerId > 0) {

1
src/components/slider.ts

@ -112,6 +112,7 @@ export default class SidebarSlider {
} }
this.removeTabFromHistory(tab); this.removeTabFromHistory(tab);
appNavigationController.removeByType(this.navigationType, true);
} }
} }

107
src/components/swipeHandler.ts

@ -1,34 +1,108 @@
import { cancelEvent } from "../helpers/dom";
import { safeAssign } from "../helpers/object";
import { isTouchSupported } from "../helpers/touchSupport";
const getEvent = (e: TouchEvent | MouseEvent) => {
return (e as TouchEvent).touches ? (e as TouchEvent).touches[0] : e as MouseEvent;
};
const attachGlobalListenerTo = window;
export default class SwipeHandler { export default class SwipeHandler {
private xDown: number; private element: HTMLElement;
private yDown: number; private onSwipe: (xDiff: number, yDiff: number) => boolean;
private verifyTouchTarget: (evt: Touch | MouseEvent) => boolean;
private onFirstSwipe: () => void;
private onReset: () => void;
private hadMove = false;
private xDown: number = null;
private yDown: number = null;
constructor(options: {
element: SwipeHandler['element'],
onSwipe: SwipeHandler['onSwipe'],
verifyTouchTarget?: SwipeHandler['verifyTouchTarget'],
onFirstSwipe?: SwipeHandler['onFirstSwipe'],
onReset?: SwipeHandler['onReset'],
}) {
safeAssign(this, options);
if(!isTouchSupported) {
this.element.addEventListener('mousedown', this.handleStart, false);
attachGlobalListenerTo.addEventListener('mouseup', this.reset);
} else {
this.element.addEventListener('touchstart', this.handleStart, false);
attachGlobalListenerTo.addEventListener('touchend', this.reset);
}
}
reset = (e?: Event) => {
/* if(e) {
cancelEvent(e);
} */
if(isTouchSupported) {
attachGlobalListenerTo.removeEventListener('touchmove', this.handleMove, {capture: true});
} else {
attachGlobalListenerTo.removeEventListener('mousemove', this.handleMove);
this.element.style.cursor = '';
}
constructor(element: HTMLElement, private onSwipe: (xDiff: number, yDiff: number) => boolean, private verifyTouchTarget?: (evt: TouchEvent) => boolean) { if(this.onReset && this.hadMove) {
element.addEventListener('touchstart', this.handleTouchStart, false); this.onReset();
element.addEventListener('touchmove', this.handleTouchMove, false);
} }
handleTouchStart = (evt: TouchEvent) => {
if(this.verifyTouchTarget && !this.verifyTouchTarget(evt)) {
this.xDown = this.yDown = null; this.xDown = this.yDown = null;
return; this.hadMove = false;
};
handleStart = (_e: TouchEvent | MouseEvent) => {
const e = getEvent(_e);
if(this.verifyTouchTarget && !this.verifyTouchTarget(e)) {
return this.reset();
} }
const firstTouch = evt.touches[0]; this.xDown = e.clientX;
this.xDown = firstTouch.clientX; this.yDown = e.clientY;
this.yDown = firstTouch.clientY;
if(isTouchSupported) {
attachGlobalListenerTo.addEventListener('touchmove', this.handleMove, {passive: false, capture: true});
} else {
attachGlobalListenerTo.addEventListener('mousemove', this.handleMove, false);
}
}; };
handleTouchMove = (evt: TouchEvent) => { handleMove = (_e: TouchEvent | MouseEvent) => {
if(this.xDown === null || this.yDown === null) { if(this.xDown === null || this.yDown === null) {
return; return this.reset();
} }
const xUp = evt.touches[0].clientX; cancelEvent(_e);
const yUp = evt.touches[0].clientY;
const e = getEvent(_e);
const xUp = e.clientX;
const yUp = e.clientY;
const xDiff = this.xDown - xUp; const xDiff = this.xDown - xUp;
const yDiff = this.yDown - yUp; const yDiff = this.yDown - yUp;
if(!this.hadMove) {
if(!xDiff && !yDiff) {
return;
}
this.hadMove = true;
if(!isTouchSupported) {
this.element.style.cursor = 'grabbing';
}
if(this.onFirstSwipe) {
this.onFirstSwipe();
}
}
// if(Math.abs(xDiff) > Math.abs(yDiff)) { /*most significant*/ // if(Math.abs(xDiff) > Math.abs(yDiff)) { /*most significant*/
// if(xDiff > 0) { /* left swipe */ // if(xDiff > 0) { /* left swipe */
@ -45,8 +119,7 @@ export default class SwipeHandler {
/* reset values */ /* reset values */
if(this.onSwipe(xDiff, yDiff)) { if(this.onSwipe(xDiff, yDiff)) {
this.xDown = null; this.reset();
this.yDown = null;
} }
}; };
} }

2
src/config/app.ts

@ -2,7 +2,7 @@ const App = {
id: 1025907, id: 1025907,
hash: '452b0359b988148995f22ff0f4229750', hash: '452b0359b988148995f22ff0f4229750',
version: '0.4.0', version: '0.4.0',
langPackVersion: '0.0.9', langPackVersion: '0.1.0',
langPack: 'macos', langPack: 'macos',
langPackCode: 'en', langPackCode: 'en',
domains: [] as string[], domains: [] as string[],

16
src/helpers/middleware.ts

@ -0,0 +1,16 @@
// * will change .cleaned and new instance will be created
export const getMiddleware = () => {
let cleanupObj = {cleaned: false};
return {
clean: () => {
cleanupObj.cleaned = true;
cleanupObj = {cleaned: false};
},
get: () => {
const _cleanupObj = cleanupObj;
return () => {
return !_cleanupObj.cleaned;
};
}
};
};

2
src/index.hbs

@ -82,7 +82,7 @@
<div class="sidebar-header__btn-container"> <div class="sidebar-header__btn-container">
<div class="animated-menu-icon"></div> <div class="animated-menu-icon"></div>
<div class="btn-icon btn-menu-toggle rp sidebar-tools-button is-visible"></div> <div class="btn-icon btn-menu-toggle rp sidebar-tools-button is-visible"></div>
<div class="btn-icon rp sidebar-back-button"></div> <div class="btn-icon sidebar-back-button"></div>
</div> </div>
</div> </div>
<div class="sidebar-content transition zoom-fade"> <div class="sidebar-content transition zoom-fade">

1
src/lang.ts

@ -529,6 +529,7 @@ const lang = {
"PeerInfo.SharedMedia": "Shared Media", "PeerInfo.SharedMedia": "Shared Media",
"PeerInfo.Subscribers": "Subscribers", "PeerInfo.Subscribers": "Subscribers",
"PeerInfo.DeleteContact": "Delete Contact", "PeerInfo.DeleteContact": "Delete Contact",
"PeerMedia.Members": "Members",
"PollResults.Title.Poll": "Poll Results", "PollResults.Title.Poll": "Poll Results",
"PollResults.Title.Quiz": "Quiz Results", "PollResults.Title.Quiz": "Quiz Results",
"PollResults.LoadMore": { "PollResults.LoadMore": {

17
src/lib/appManagers/appDialogsManager.ts

@ -6,7 +6,6 @@ import { ripple } from "../../components/ripple";
//import Scrollable from "../../components/scrollable"; //import Scrollable from "../../components/scrollable";
import Scrollable, { ScrollableX, SliceSides } from "../../components/scrollable"; import Scrollable, { ScrollableX, SliceSides } from "../../components/scrollable";
import { formatDateAccordingToTodayNew } from "../../helpers/date"; import { formatDateAccordingToTodayNew } from "../../helpers/date";
import { escapeRegExp } from "../../helpers/string";
import { isSafari } from "../../helpers/userAgent"; import { isSafari } from "../../helpers/userAgent";
import { logger, LogLevels } from "../logger"; import { logger, LogLevels } from "../logger";
import { RichTextProcessor } from "../richtextprocessor"; import { RichTextProcessor } from "../richtextprocessor";
@ -830,11 +829,17 @@ export class AppDialogsManager {
//const scrollTopWas = this.scroll.scrollTop; //const scrollTopWas = this.scroll.scrollTop;
const rect = this.scroll.container.getBoundingClientRect(); const firstElementChild = this.chatList.firstElementChild;
const rectX = this.chatList.firstElementChild.getBoundingClientRect(); const rectContainer = this.scroll.container.getBoundingClientRect();
const rectTarget = firstElementChild.getBoundingClientRect();
const children = Array.from(this.scroll.splitUp.children) as HTMLElement[]; const children = Array.from(this.scroll.splitUp.children) as HTMLElement[];
const firstElement = findUpTag(document.elementFromPoint(Math.ceil(rectX.x), Math.ceil(rect.y + 1)), 'LI') as HTMLElement;
const lastElement = findUpTag(document.elementFromPoint(Math.ceil(rectX.x), Math.floor(rect.y + rect.height - 1)), 'LI') as HTMLElement; const offsetTop = this.folders.container.offsetTop;
const firstY = rectContainer.y + offsetTop;
const lastY = rectContainer.y;
const firstElement = findUpTag(document.elementFromPoint(Math.ceil(rectTarget.x), Math.ceil(firstY + 1)), firstElementChild.tagName) as HTMLElement;
const lastElement = findUpTag(document.elementFromPoint(Math.ceil(rectTarget.x), Math.floor(lastY + rectContainer.height - 1)), firstElementChild.tagName) as HTMLElement;
//alert('got element:' + rect.y); //alert('got element:' + rect.y);
@ -845,7 +850,7 @@ export class AppDialogsManager {
//alert('got element:' + !!firstElement); //alert('got element:' + !!firstElement);
const firstElementRect = firstElement.getBoundingClientRect(); const firstElementRect = firstElement.getBoundingClientRect();
const elementOverflow = firstElementRect.y - rect.y; const elementOverflow = firstElementRect.y - firstY;
const sliced: HTMLElement[] = []; const sliced: HTMLElement[] = [];
const firstIndex = children.indexOf(firstElement); const firstIndex = children.indexOf(firstElement);

10
src/scss/partials/_leftSidebar.scss

@ -105,14 +105,8 @@
height: calc(100% - 44px); height: calc(100% - 44px);
#folders-container { #folders-container {
li:first-child {
margin-top: .5rem; margin-top: .5rem;
} }
> div {
background-color: transparent;
}
}
} }
} }
@ -205,6 +199,10 @@
#folders-container { #folders-container {
min-height: 100%; min-height: 100%;
> div {
background-color: transparent;
}
} }
.sidebar-slider { .sidebar-slider {

28
src/scss/partials/_profile.scss

@ -3,9 +3,10 @@
&-container { &-container {
width: 100%; width: 100%;
height: 26.25rem; height: 26.25rem;
overflow: hidden; //overflow: hidden;
position: relative; position: relative;
cursor: pointer; cursor: pointer;
user-select: none;
/* &:before, &:after { /* &:before, &:after {
position: absolute; position: absolute;
@ -20,8 +21,18 @@
height: inherit; height: inherit;
display: flex; display: flex;
flex-wrap: nowrap; flex-wrap: nowrap;
transform: translateX(0); transform: translateZ(-1px) scale(2);
//transition: transform .2s ease-in-out; transform-origin: left top;
transition: transform .2s ease-in-out;
position: relative;
&:before {
content: " ";
top: 0;
right: 0;
bottom: 0;
left: 0;
}
} }
&-avatar { &-avatar {
@ -32,11 +43,12 @@
display: flex; display: flex;
background-color: #000; background-color: #000;
/* img, video { &-image {
width: 100%; width: 100%;
height: 100%; height: 100%;
object-fit: cover; object-fit: cover;
} */ pointer-events: none;
}
} }
&-info { &-info {
@ -60,6 +72,10 @@
.profile-subtitle { .profile-subtitle {
opacity: .7; opacity: .7;
} }
.online {
color: inherit !important;
}
} }
&-tabs { &-tabs {
@ -118,6 +134,8 @@
} }
.sidebar-left-section { .sidebar-left-section {
position: relative;
background-color: var(--surface-color);
//padding-top: .5625rem; //padding-top: .5625rem;
padding-bottom: 0; padding-bottom: 0;
} }

21
src/scss/partials/_rightSidebar.scss

@ -102,6 +102,11 @@
max-height: calc((var(--vh, 1vh) * 100) - 100% - 56px); max-height: calc((var(--vh, 1vh) * 100) - 100% - 56px);
} }
} */ } */
.scrollable {
perspective: 1px;
perspective-origin: left top;
}
.search-super { .search-super {
top: 100%; top: 100%;
min-height: calc((var(--vh, 1vh) * 100) - 56px); min-height: calc((var(--vh, 1vh) * 100) - 56px);
@ -124,6 +129,7 @@
min-height: 100%; min-height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
background-color: var(--surface-color);
.search-group__show-more { .search-group__show-more {
color: var(--primary-color); color: var(--primary-color);
@ -344,16 +350,13 @@
min-height: 58px; min-height: 58px;
.preview { .preview {
height: 48px; height: 3rem;
width: 48px; width: 3rem;
border-radius: 5px; border-radius: .375rem;
overflow: hidden; overflow: hidden;
position: absolute; position: absolute;
left: 0; left: 0;
top: 0; top: 0;
background-repeat: no-repeat;
background-size: cover;
background-position: center center;
&.empty { &.empty {
display: flex; display: flex;
@ -364,6 +367,12 @@
text-transform: uppercase; text-transform: uppercase;
background-color: var(--primary-color); background-color: var(--primary-color);
} }
.media-photo {
object-fit: cover;
width: 100%;
height: 100%;
}
} }
.url { .url {

2
src/scss/style.scss

@ -999,7 +999,7 @@ middle-ellipsis-element {
} }
.row { .row {
min-height: 3rem; min-height: 3.5rem;
position: relative; position: relative;
padding: .6875rem 1rem; padding: .6875rem 1rem;
display: flex; display: flex;

Loading…
Cancel
Save