Eduard Kuzmenko
3 years ago
16 changed files with 535 additions and 239 deletions
@ -0,0 +1,108 @@
@@ -0,0 +1,108 @@
|
||||
/* |
||||
* https://github.com/morethanwords/tweb
|
||||
* Copyright (C) 2019-2021 Eduard Kuzmenko |
||||
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
||||
*/ |
||||
|
||||
import type { AppMessagesManager } from "../../lib/appManagers/appMessagesManager"; |
||||
import DropdownHover from "../../helpers/dropdownHover"; |
||||
import { ReplyMarkup } from "../../layer"; |
||||
import RichTextProcessor from "../../lib/richtextprocessor"; |
||||
import rootScope from "../../lib/rootScope"; |
||||
import { safeAssign } from "../../helpers/object"; |
||||
import ListenerSetter from "../../helpers/listenerSetter"; |
||||
import findUpClassName from "../../helpers/dom/findUpClassName"; |
||||
|
||||
export default class ReplyKeyboard extends DropdownHover { |
||||
private static BASE_CLASS = 'reply-keyboard'; |
||||
private appendTo: HTMLElement; |
||||
private listenerSetter: ListenerSetter; |
||||
private appMessagesManager: AppMessagesManager; |
||||
private btnHover: HTMLElement; |
||||
private peerId: number; |
||||
|
||||
constructor(options: { |
||||
listenerSetter: ListenerSetter, |
||||
appMessagesManager: AppMessagesManager, |
||||
appendTo: HTMLElement, |
||||
btnHover: HTMLElement |
||||
}) { |
||||
super({ |
||||
element: document.createElement('div') |
||||
}); |
||||
|
||||
safeAssign(this, options); |
||||
|
||||
this.element.classList.add(ReplyKeyboard.BASE_CLASS); |
||||
this.element.style.display = 'none'; |
||||
|
||||
this.attachButtonListener(this.btnHover, this.listenerSetter); |
||||
this.listenerSetter.add(rootScope, 'history_reply_markup', ({peerId}) => { |
||||
if(this.peerId === peerId && this.checkAvailability() && this.isActive()) { |
||||
this.render(); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
protected init() { |
||||
this.appendTo.append(this.element); |
||||
|
||||
this.listenerSetter.add(this, 'open', () => { |
||||
this.render(); |
||||
}); |
||||
|
||||
this.listenerSetter.add(this.element, 'click', (e) => { |
||||
const target = findUpClassName(e.target, 'btn'); |
||||
if(!target) { |
||||
return; |
||||
} |
||||
|
||||
this.appMessagesManager.sendText(this.peerId, target.dataset.text); |
||||
this.toggle(false); |
||||
}); |
||||
|
||||
return super.init(); |
||||
} |
||||
|
||||
private getReplyMarkup(): ReplyMarkup { |
||||
return this.appMessagesManager.getHistoryStorage(this.peerId).reply_markup ?? { |
||||
_: 'replyKeyboardHide' |
||||
}; |
||||
} |
||||
|
||||
public render(replyMarkup: ReplyMarkup.replyKeyboardMarkup = this.getReplyMarkup() as any) { |
||||
this.element.innerHTML = ''; |
||||
|
||||
for(const row of replyMarkup.rows) { |
||||
const div = document.createElement('div'); |
||||
div.classList.add(ReplyKeyboard.BASE_CLASS + '-row'); |
||||
|
||||
for(const button of row.buttons) { |
||||
const btn = document.createElement('button'); |
||||
btn.classList.add(ReplyKeyboard.BASE_CLASS + '-button', 'btn'); |
||||
btn.innerHTML = RichTextProcessor.wrapEmojiText(button.text); |
||||
btn.dataset.text = button.text; |
||||
div.append(btn); |
||||
} |
||||
|
||||
this.element.append(div); |
||||
} |
||||
} |
||||
|
||||
public checkAvailability(replyMarkup: ReplyMarkup = this.getReplyMarkup()) { |
||||
const hide = replyMarkup._ === 'replyKeyboardHide'; |
||||
this.btnHover.classList.toggle('hide', hide); |
||||
|
||||
if(hide) { |
||||
this.toggle(false); |
||||
} |
||||
|
||||
return !hide; |
||||
} |
||||
|
||||
public setPeer(peerId: number) { |
||||
this.peerId = peerId; |
||||
|
||||
this.checkAvailability(); |
||||
} |
||||
} |
@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
|
||||
export default function assumeType<T>(x: unknown): asserts x is T { |
||||
return; // ¯\_(ツ)_/¯
|
||||
} |
@ -0,0 +1,163 @@
@@ -0,0 +1,163 @@
|
||||
/* |
||||
* https://github.com/morethanwords/tweb
|
||||
* Copyright (C) 2019-2021 Eduard Kuzmenko |
||||
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
||||
*/ |
||||
|
||||
import { attachClickEvent } from "./dom/clickEvent"; |
||||
import findUpAsChild from "./dom/findUpAsChild"; |
||||
import EventListenerBase from "./eventListenerBase"; |
||||
import ListenerSetter from "./listenerSetter"; |
||||
import { safeAssign } from "./object"; |
||||
import { isTouchSupported } from "./touchSupport"; |
||||
|
||||
const KEEP_OPEN = false; |
||||
const TOGGLE_TIMEOUT = 200; |
||||
const ANIMATION_DURATION = 200; |
||||
|
||||
export default class DropdownHover extends EventListenerBase<{ |
||||
open: () => Promise<any> | void, |
||||
opened: () => any, |
||||
close: () => any, |
||||
closed: () => any |
||||
}> { |
||||
protected element: HTMLElement; |
||||
protected displayTimeout: number; |
||||
protected forceClose = false; |
||||
protected inited = false; |
||||
|
||||
constructor(options: { |
||||
element: DropdownHover['element'] |
||||
}) { |
||||
super(false); |
||||
safeAssign(this, options); |
||||
} |
||||
|
||||
public attachButtonListener(button: HTMLElement, listenerSetter: ListenerSetter) { |
||||
let firstTime = true; |
||||
if(isTouchSupported) { |
||||
attachClickEvent(button, () => { |
||||
if(firstTime) { |
||||
firstTime = false; |
||||
this.toggle(true); |
||||
} else { |
||||
this.toggle(); |
||||
} |
||||
}, {listenerSetter}); |
||||
} else { |
||||
listenerSetter.add(button, 'mouseover', (e) => { |
||||
//console.log('onmouseover button');
|
||||
if(firstTime) { |
||||
listenerSetter.add(button, 'mouseout', this.onMouseOut); |
||||
firstTime = false; |
||||
} |
||||
|
||||
clearTimeout(this.displayTimeout); |
||||
this.displayTimeout = window.setTimeout(() => { |
||||
this.toggle(true); |
||||
}, TOGGLE_TIMEOUT); |
||||
}); |
||||
} |
||||
} |
||||
|
||||
private onMouseOut = (e: MouseEvent) => { |
||||
if(KEEP_OPEN) return; |
||||
clearTimeout(this.displayTimeout); |
||||
if(!this.isActive()) return; |
||||
|
||||
const toElement = (e as any).toElement as Element; |
||||
if(toElement && findUpAsChild(toElement, this.element)) { |
||||
return; |
||||
} |
||||
|
||||
this.displayTimeout = window.setTimeout(() => { |
||||
this.toggle(false); |
||||
}, TOGGLE_TIMEOUT); |
||||
}; |
||||
|
||||
protected init() { |
||||
if(!isTouchSupported) { |
||||
this.element.onmouseout = this.onMouseOut; |
||||
this.element.onmouseover = (e) => { |
||||
if(this.forceClose) { |
||||
return; |
||||
} |
||||
|
||||
//console.log('onmouseover element');
|
||||
clearTimeout(this.displayTimeout); |
||||
}; |
||||
} |
||||
} |
||||
|
||||
public toggle = async(enable?: boolean) => { |
||||
//if(!this.element) return;
|
||||
const willBeActive = (!!this.element.style.display && enable === undefined) || enable; |
||||
if(this.init) { |
||||
if(willBeActive) { |
||||
this.init(); |
||||
this.init = null; |
||||
} else { |
||||
return; |
||||
} |
||||
} |
||||
|
||||
if(willBeActive === this.isActive()) { |
||||
return; |
||||
} |
||||
|
||||
if((this.element.style.display && enable === undefined) || enable) { |
||||
const res = this.dispatchEvent('open'); |
||||
await Promise.all(res); |
||||
|
||||
this.element.style.display = ''; |
||||
void this.element.offsetLeft; // reflow
|
||||
this.element.classList.add('active'); |
||||
|
||||
clearTimeout(this.displayTimeout); |
||||
this.displayTimeout = window.setTimeout(() => { |
||||
this.forceClose = false; |
||||
this.dispatchEvent('opened'); |
||||
}, isTouchSupported ? 0 : ANIMATION_DURATION); |
||||
|
||||
// ! can't use together with resizeObserver
|
||||
/* if(isTouchSupported) { |
||||
const height = this.element.scrollHeight + appImManager.chat.input.inputContainer.scrollHeight - 10; |
||||
console.log('[ESG]: toggle: enable height', height); |
||||
appImManager.chat.bubbles.scrollable.scrollTop += height; |
||||
} */ |
||||
|
||||
/* if(touchSupport) { |
||||
this.restoreScroll(); |
||||
} */ |
||||
} else { |
||||
this.dispatchEvent('close'); |
||||
|
||||
this.element.classList.remove('active'); |
||||
|
||||
clearTimeout(this.displayTimeout); |
||||
this.displayTimeout = window.setTimeout(() => { |
||||
this.element.style.display = 'none'; |
||||
this.forceClose = false; |
||||
this.dispatchEvent('closed'); |
||||
}, isTouchSupported ? 0 : ANIMATION_DURATION); |
||||
|
||||
/* if(isTouchSupported) { |
||||
const scrollHeight = this.container.scrollHeight; |
||||
if(scrollHeight) { |
||||
const height = this.container.scrollHeight + appImManager.chat.input.inputContainer.scrollHeight - 10; |
||||
appImManager.chat.bubbles.scrollable.scrollTop -= height; |
||||
} |
||||
} */ |
||||
|
||||
/* if(touchSupport) { |
||||
this.restoreScroll(); |
||||
} */ |
||||
} |
||||
|
||||
//animationIntersector.checkAnimations(false, EMOTICONSSTICKERGROUP);
|
||||
}; |
||||
|
||||
public isActive() { |
||||
return this.element.classList.contains('active'); |
||||
} |
||||
} |
@ -0,0 +1,64 @@
@@ -0,0 +1,64 @@
|
||||
.reply-keyboard { |
||||
background: var(--surface-color); |
||||
position: absolute !important; |
||||
right: 0; |
||||
bottom: calc(100% + .625rem); |
||||
width: 26.25rem !important; |
||||
//height: 26.25rem; |
||||
max-height: 26.25rem; |
||||
box-shadow: 0px 5px 10px 5px rgba(16, 35, 47, .14); |
||||
z-index: 3; |
||||
border-radius: $border-radius-medium; |
||||
transition: transform var(--esg-transition), opacity var(--esg-transition); |
||||
transform: scale(0); |
||||
opacity: 0; |
||||
transform-origin: bottom right; |
||||
padding: .625rem !important; |
||||
display: block !important; |
||||
|
||||
@include respond-to(esg-bottom-new) { |
||||
bottom: calc(100% + .5rem); |
||||
} |
||||
|
||||
&.active { |
||||
opacity: 1; |
||||
transform: scale(1); |
||||
} |
||||
|
||||
body.animation-level-0 & { |
||||
transition: none; |
||||
} |
||||
|
||||
&-row { |
||||
display: flex; |
||||
|
||||
& + & { |
||||
margin-top: .3125rem; |
||||
} |
||||
} |
||||
|
||||
&-button { |
||||
width: 100%; |
||||
border-radius: .375rem; |
||||
border: 2px solid var(--primary-color); |
||||
text-align: center; |
||||
color: var(--primary-color); |
||||
background-color: transparent; |
||||
height: 3rem; |
||||
font-weight: 500; |
||||
font-size: .9375rem; |
||||
|
||||
@include animation-level(2) { |
||||
transition: color .15s, background-color .15s; |
||||
} |
||||
|
||||
@include hover() { |
||||
background-color: var(--primary-color); |
||||
color: #fff; |
||||
} |
||||
|
||||
& + & { |
||||
margin-left: .3125rem; |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue