Popup changes:

Closing by escape
Avatar & new media are now popups
Fix deleting message
Edit single item from album
Fix message text in album
Fix overflow in pinned subtitle
This commit is contained in:
morethanwords 2020-10-13 00:06:02 +03:00
parent 002bc53804
commit ad16f59219
16 changed files with 503 additions and 450 deletions

View File

@ -45,9 +45,16 @@ export class ChatContextMenu {
if(!msgID) return;
this.peerID = $rootScope.selectedPeerID;
this.msgID = msgID;
//this.msgID = msgID;
this.target = e.target as HTMLElement;
const albumItem = findUpClassName(this.target, 'album-item');
if(albumItem) {
this.msgID = +albumItem.dataset.mid;
} else {
this.msgID = msgID;
}
this.buttons.forEach(button => {
const good = button.verify(this.peerID, this.msgID, this.target);
button.element.classList.toggle('hide', !good);
@ -145,13 +152,13 @@ export class ChatContextMenu {
};
private onCopyClick = () => {
let message = appMessagesManager.getMessage(this.msgID);
const message = appMessagesManager.getMessage(this.msgID);
let str = message ? message.message : '';
const str = message ? message.message : '';
var textArea = document.createElement("textarea");
const textArea = document.createElement('textarea');
textArea.value = str;
textArea.style.position = "fixed"; //avoid scrolling to bottom
textArea.style.position = 'fixed'; //avoid scrolling to bottom
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
@ -182,22 +189,16 @@ export class ChatContextMenu {
};
private onForwardClick = () => {
let msgID: number;
const albumItem = findUpClassName(this.target, 'album-item');
if(albumItem) {
msgID = +albumItem.dataset.mid;
}
appSidebarRight.forwardTab.open([msgID]);
appSidebarRight.forwardTab.open([this.msgID]);
};
private onDeleteClick = () => {
let peerID = $rootScope.selectedPeerID;
let firstName = appPeersManager.getPeerTitle(peerID, false, true);
const peerID = $rootScope.selectedPeerID;
const firstName = appPeersManager.getPeerTitle(peerID, false, true);
let callback = (revoke: boolean) => {
appMessagesManager.deleteMessages([this.msgID], revoke);
const msgID = this.msgID;
const callback = (revoke: boolean) => {
appMessagesManager.deleteMessages([msgID], revoke);
};
let title: string, description: string, buttons: PopupButton[];
@ -237,7 +238,7 @@ export class ChatContextMenu {
isCancel: true
});
let popup = new PopupPeer('popup-delete-chat', {
const popup = new PopupPeer('popup-delete-chat', {
peerID: peerID,
title: title,
description: description,

View File

@ -10,15 +10,15 @@ import apiManager from "../../lib/mtproto/mtprotoworker";
import opusDecodeController from "../../lib/opusDecodeController";
import { RichTextProcessor } from "../../lib/richtextprocessor";
import $rootScope from '../../lib/rootScope';
import { calcImageInBox, cancelEvent, getRichValue } from "../../lib/utils";
import { cancelEvent, getRichValue } from "../../lib/utils";
import ButtonMenu, { ButtonMenuItemOptions } from '../buttonMenu';
import emoticonsDropdown from "../emoticonsDropdown";
import { Layouter, RectPart } from "../groupedLayout";
import PopupCreatePoll from "../popupCreatePoll";
import PopupNewMedia from '../popupNewMedia';
import { ripple } from '../ripple';
import Scrollable from "../scrollable";
import { toast } from "../toast";
import { wrapDocument, wrapReply } from "../wrappers";
import { wrapReply } from "../wrappers";
const RECORD_MIN_TIME = 500;
const POSTING_MEDIA_NOT_ALLOWED = 'Posting media content isn\'t allowed in this group.';
@ -40,14 +40,6 @@ export class ChatInput {
public attachMenu: HTMLButtonElement;
private attachMenuButtons: (ButtonMenuItemOptions & {verify: (peerID: number) => boolean})[];
public attachMediaPopUp: {
container?: HTMLDivElement,
titleEl?: HTMLDivElement,
sendBtn?: HTMLButtonElement,
mediaContainer?: HTMLDivElement,
captionInput?: HTMLInputElement
} = {};
public replyElements: {
container?: HTMLDivElement,
cancelBtn?: HTMLButtonElement,
@ -74,12 +66,14 @@ export class ChatInput {
constructor() {
this.attachMenu = document.getElementById('attach-file') as HTMLButtonElement;
let willAttachType: 'document' | 'media';
this.attachMenuButtons = [{
icon: 'photo',
text: 'Photo or Video',
onClick: () => {
this.fileInput.value = '';
this.fileInput.setAttribute('accept', 'image/*, video/*');
willAttach.type = 'media';
willAttachType = 'media';
this.fileInput.click();
},
verify: (peerID: number) => peerID > 0 || appChatsManager.hasRights(peerID, 'send', 'send_media')
@ -87,8 +81,9 @@ export class ChatInput {
icon: 'document',
text: 'Document',
onClick: () => {
this.fileInput.value = '';
this.fileInput.removeAttribute('accept');
willAttach.type = 'document';
willAttachType = 'document';
this.fileInput.click();
},
verify: (peerID: number) => peerID > 0 || appChatsManager.hasRights(peerID, 'send', 'send_media')
@ -116,12 +111,6 @@ export class ChatInput {
ripple(this.attachMenu);
this.attachMediaPopUp.container = this.pageEl.querySelector('.popup-send-photo') as HTMLDivElement;
this.attachMediaPopUp.titleEl = this.attachMediaPopUp.container.querySelector('.popup-title') as HTMLDivElement;
this.attachMediaPopUp.sendBtn = this.attachMediaPopUp.container.querySelector('.btn-primary') as HTMLButtonElement;
this.attachMediaPopUp.mediaContainer = this.attachMediaPopUp.container.querySelector('.popup-photo') as HTMLDivElement;
this.attachMediaPopUp.captionInput = this.attachMediaPopUp.container.querySelector('input') as HTMLInputElement;
this.replyElements.container = this.pageEl.querySelector('.reply-wrapper') as HTMLDivElement;
this.replyElements.cancelBtn = this.replyElements.container.querySelector('.reply-cancel') as HTMLButtonElement;
this.replyElements.titleEl = this.replyElements.container.querySelector('.reply-title') as HTMLDivElement;
@ -281,210 +270,19 @@ export class ChatInput {
window.document.execCommand('insertHTML', false, text);
});
let attachFile = (file: File) => {
return new Promise<HTMLDivElement>((resolve, reject) => {
let params: SendFileParams = {};
params.file = file;
//console.log('selected file:', file, typeof(file), willAttach);
let itemDiv = document.createElement('div');
switch(willAttach.type) {
case 'media': {
let isVideo = file.type.indexOf('video/') === 0;
itemDiv.classList.add('popup-item-media');
if(isVideo) {
let video = document.createElement('video');
let source = document.createElement('source');
source.src = params.objectURL = URL.createObjectURL(file);
video.autoplay = false;
video.controls = false;
video.muted = true;
video.setAttribute('playsinline', '');
video.onloadeddata = () => {
params.width = video.videoWidth;
params.height = video.videoHeight;
params.duration = Math.floor(video.duration);
itemDiv.append(video);
resolve(itemDiv);
};
video.append(source);
} else {
let img = new Image();
img.src = params.objectURL = URL.createObjectURL(file);
img.onload = () => {
params.width = img.naturalWidth;
params.height = img.naturalHeight;
itemDiv.append(img);
resolve(itemDiv);
};
}
break;
}
case 'document': {
const isPhoto = file.type.indexOf('image/') !== -1;
if(isPhoto) {
params.objectURL = URL.createObjectURL(file);
}
let docDiv = wrapDocument({
file: file,
file_name: file.name || '',
size: file.size,
type: isPhoto ? 'photo' : 'doc',
url: params.objectURL
} as any, false, true);
const finish = () => {
itemDiv.append(docDiv);
resolve(itemDiv);
};
if(isPhoto) {
let img = new Image();
img.src = params.objectURL;
img.onload = () => {
params.width = img.naturalWidth;
params.height = img.naturalHeight;
finish();
};
img.onerror = finish;
} else {
finish();
}
break;
}
}
willAttach.sendFileDetails.push(params);
});
};
let attachFiles = (files: File[]) => {
this.fileInput.value = '';
let container = this.attachMediaPopUp.container.firstElementChild as HTMLElement;
container.classList.remove('is-media', 'is-document', 'is-album');
this.attachMediaPopUp.captionInput.value = '';
this.attachMediaPopUp.mediaContainer.innerHTML = '';
this.attachMediaPopUp.mediaContainer.style.width = this.attachMediaPopUp.mediaContainer.style.height = '';
//willAttach.sendFileDetails.length = 0;
willAttach.sendFileDetails = []; // need new array
files = files.filter(file => {
if(willAttach.type == 'media') {
return ['image/', 'video/'].find(s => file.type.indexOf(s) === 0);
} else {
return true;
}
});
if(files.length) {
if(willAttach.type == 'document') {
this.attachMediaPopUp.titleEl.innerText = 'Send ' + (files.length > 1 ? files.length + ' Files' : 'File');
container.classList.add('is-document');
} else {
container.classList.add('is-media');
let foundPhotos = 0;
let foundVideos = 0;
files.forEach(file => {
if(file.type.indexOf('image/') === 0) ++foundPhotos;
else if(file.type.indexOf('video/') === 0) ++foundVideos;
});
if(foundPhotos && foundVideos) {
this.attachMediaPopUp.titleEl.innerText = 'Send Album';
} else if(foundPhotos) {
this.attachMediaPopUp.titleEl.innerText = 'Send ' + (foundPhotos > 1 ? foundPhotos + ' Photos' : 'Photo');
} else if(foundVideos) {
this.attachMediaPopUp.titleEl.innerText = 'Send ' + (foundVideos > 1 ? foundVideos + ' Videos' : 'Video');
}
}
}
Promise.all(files.map(attachFile)).then(results => {
if(willAttach.type == 'media') {
if(willAttach.sendFileDetails.length > 1) {
container.classList.add('is-album');
let layouter = new Layouter(willAttach.sendFileDetails.map(o => ({w: o.width, h: o.height})), 380, 100, 4);
let layout = layouter.layout();
for(let {geometry, sides} of layout) {
let div = results.shift();
div.style.width = geometry.width + 'px';
div.style.height = geometry.height + 'px';
div.style.top = geometry.y + 'px';
div.style.left = geometry.x + 'px';
if(sides & RectPart.Right) {
this.attachMediaPopUp.mediaContainer.style.width = geometry.width + geometry.x + 'px';
}
if(sides & RectPart.Bottom) {
this.attachMediaPopUp.mediaContainer.style.height = geometry.height + geometry.y + 'px';
}
this.attachMediaPopUp.mediaContainer.append(div);
}
//console.log('chatInput album layout:', layout);
} else {
let params = willAttach.sendFileDetails[0];
let div = results[0];
let {w, h} = calcImageInBox(params.width, params.height, 380, 320);
div.style.width = w + 'px';
div.style.height = h + 'px';
this.attachMediaPopUp.mediaContainer.append(div);
}
} else {
this.attachMediaPopUp.mediaContainer.append(...results);
}
this.attachMediaPopUp.container.classList.add('active');
});
};
type SendFileParams = Partial<{
file: File,
objectURL: string,
width: number,
height: number,
duration: number
}>;
let willAttach: Partial<{
type: 'media' | 'document',
isMedia: boolean,
sendFileDetails: SendFileParams[]
}> = {
sendFileDetails: []
};
this.fileInput.addEventListener('change', (e) => {
let files = (e.target as HTMLInputElement & EventTarget).files;
if(!files.length) {
return;
}
attachFiles(Array.from(files));
new PopupNewMedia(Array.from(files).slice(), willAttachType);
this.fileInput.value = '';
}, false);
document.addEventListener('paste', (event) => {
const peerID = $rootScope.selectedPeerID;
if(!peerID || this.attachMediaPopUp.container.classList.contains('active') || (peerID < 0 && !appChatsManager.hasRights(peerID, 'send', 'send_media'))) {
if(!peerID || $rootScope.overlayIsActive || (peerID < 0 && !appChatsManager.hasRights(peerID, 'send', 'send_media'))) {
return;
}
@ -503,56 +301,12 @@ export class ChatInput {
//console.log(items[i], file);
if(!file) continue;
willAttach.type = file.type.indexOf('image/') === 0 ? 'media' : "document";
attachFiles([file]);
willAttachType = file.type.indexOf('image/') === 0 ? 'media' : "document";
new PopupNewMedia([file], willAttachType);
}
}
}, true);
this.attachMediaPopUp.sendBtn.addEventListener('click', () => {
this.attachMediaPopUp.container.classList.remove('active');
let caption = this.attachMediaPopUp.captionInput.value;
willAttach.isMedia = willAttach.type == 'media';
//console.log('will send files with options:', willAttach);
let peerID = appImManager.peerID;
if(willAttach.sendFileDetails.length > 1 && willAttach.isMedia) {
appMessagesManager.sendAlbum(peerID, willAttach.sendFileDetails.map(d => d.file), Object.assign({
caption,
replyToMsgID: this.replyToMsgID
}, willAttach));
} else {
if(caption) {
if(willAttach.sendFileDetails.length > 1) {
appMessagesManager.sendText(peerID, caption, {replyToMsgID: this.replyToMsgID});
caption = '';
this.replyToMsgID = 0;
}
}
let promises = willAttach.sendFileDetails.map(params => {
let promise = appMessagesManager.sendFile(peerID, params.file, Object.assign({
//isMedia: willAttach.isMedia,
isMedia: params.file.type.includes('audio/') || willAttach.isMedia,
caption,
replyToMsgID: this.replyToMsgID
}, params));
caption = '';
this.replyToMsgID = 0;
return promise;
});
}
//Promise.all(promises);
//appMessagesManager.sendFile(appImManager.peerID, willAttach.file, willAttach);
this.onMessageSent();
});
const onBtnSendClick = (e: Event) => {
cancelEvent(e);

View File

@ -0,0 +1,50 @@
import { formatNumber } from "../../lib/utils";
type Message = any;
export namespace MessageRender {
/* export const setText = () => {
}; */
export const setTime = (message: Message, bubble: HTMLElement, bubbleContainer: HTMLElement, messageDiv: HTMLElement) => {
let date = new Date(message.date * 1000);
let time = ('0' + date.getHours()).slice(-2) + ':' + ('0' + date.getMinutes()).slice(-2);
if(message.views) {
bubble.classList.add('channel-post');
time = formatNumber(message.views, 1) + ' <i class="tgico-channelviews"></i> ' + time;
if(!message.savedFrom) {
let forward = document.createElement('div');
forward.classList.add('bubble-beside-button', 'forward');
forward.innerHTML = `
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24">
<defs>
<path d="M13.55 3.24L13.64 3.25L13.73 3.27L13.81 3.29L13.9 3.32L13.98 3.35L14.06 3.39L14.14 3.43L14.22 3.48L14.29 3.53L14.36 3.59L14.43 3.64L22.23 10.85L22.36 10.99L22.48 11.15L22.57 11.31L22.64 11.48L22.69 11.66L22.72 11.85L22.73 12.04L22.71 12.22L22.67 12.41L22.61 12.59L22.53 12.76L22.42 12.93L22.29 13.09L22.23 13.15L14.43 20.36L14.28 20.48L14.12 20.58L13.95 20.66L13.77 20.72L13.58 20.76L13.4 20.77L13.22 20.76L13.03 20.73L12.85 20.68L12.68 20.61L12.52 20.52L12.36 20.4L12.22 20.27L12.16 20.2L12.1 20.13L12.05 20.05L12.01 19.98L11.96 19.9L11.93 19.82L11.89 19.73L11.87 19.65L11.84 19.56L11.83 19.47L11.81 19.39L11.81 19.3L11.8 19.2L11.8 16.42L11 16.49L10.23 16.58L9.51 16.71L8.82 16.88L8.18 17.09L7.57 17.33L7.01 17.6L6.48 17.91L5.99 18.26L5.55 18.64L5.14 19.05L4.77 19.51L4.43 19.99L4.29 20.23L4.21 20.35L4.11 20.47L4 20.57L3.88 20.65L3.75 20.72L3.62 20.78L3.48 20.82L3.33 20.84L3.19 20.84L3.04 20.83L2.9 20.79L2.75 20.74L2.62 20.68L2.53 20.62L2.45 20.56L2.38 20.5L2.31 20.43L2.25 20.36L2.2 20.28L2.15 20.19L2.11 20.11L2.07 20.02L2.04 19.92L2.02 19.83L2.01 19.73L2 19.63L2.04 17.99L2.19 16.46L2.46 15.05L2.85 13.75L3.35 12.58L3.97 11.53L4.7 10.6L5.55 9.8L6.51 9.12L7.59 8.56L8.77 8.13L10.07 7.83L11.48 7.65L11.8 7.63L11.8 4.8L11.91 4.56L12.02 4.35L12.14 4.16L12.25 3.98L12.37 3.82L12.48 3.68L12.61 3.56L12.73 3.46L12.85 3.38L12.98 3.31L13.11 3.27L13.24 3.24L13.37 3.23L13.46 3.23L13.55 3.24Z" id="b13RmHDQtl"></path>
</defs>
<use xlink:href="#b13RmHDQtl" opacity="1" fill="#fff" fill-opacity="1"></use>
</svg>`;
bubbleContainer.append(forward);
bubble.classList.add('with-beside-button');
}
}
if(message.edit_date) {
bubble.classList.add('is-edited');
time = '<i class="edited">edited</i> ' + time;
}
let timeSpan = document.createElement('span');
timeSpan.classList.add('time');
let timeInner = document.createElement('div');
timeInner.classList.add('inner', 'tgico');
timeInner.innerHTML = time;
timeSpan.appendChild(timeInner);
messageDiv.append(timeSpan);
return timeSpan;
};
}

View File

@ -0,0 +1,13 @@
const InputField = (placeholder: string, label: string, name: string) => {
const div = document.createElement('div');
div.classList.add('input-field');
div.innerHTML = `
<input type="text" name="${name}" id="input-${name}" placeholder="${placeholder}" autocomplete="off" required="">
<label for="input-${name}">${label}</label>
`;
return div;
};
export default InputField;

View File

@ -1,3 +1,5 @@
import $rootScope from "../lib/rootScope";
import { cancelEvent } from "../lib/utils";
import AvatarElement from "./avatar";
import { ripple } from "./ripple";
@ -12,6 +14,7 @@ export class PopupElement {
protected onClose: () => void;
protected onCloseAfterTimeout: () => void;
protected onEscape: () => boolean = () => true;
constructor(className: string, buttons?: Array<PopupButton>, options: Partial<{closable: boolean, withConfirm: string, body: boolean}> = {}) {
this.element.classList.add('popup');
@ -26,14 +29,14 @@ export class PopupElement {
if(options.closable) {
this.closeBtn = document.createElement('span');
this.closeBtn.classList.add('btn-icon', 'popup-close', 'tgico-close');
ripple(this.closeBtn);
//ripple(this.closeBtn);
this.header.prepend(this.closeBtn);
this.closeBtn.addEventListener('click', () => {
this.destroy();
}, {once: true});
this.closeBtn.addEventListener('click', this.destroy, {once: true});
}
window.addEventListener('keydown', this._onKeyDown, {capture: true});
if(options.withConfirm) {
this.confirmBtn = document.createElement('button');
this.confirmBtn.classList.add('btn-primary');
@ -80,20 +83,33 @@ export class PopupElement {
this.element.append(this.container);
}
private _onKeyDown = (e: KeyboardEvent) => {
if(e.key == 'Escape' && this.onEscape()) {
cancelEvent(e);
this.destroy();
}
};
public show() {
document.body.append(this.element);
void this.element.offsetWidth; // reflow
this.element.classList.add('active');
$rootScope.overlayIsActive = true;
}
public destroy() {
public destroy = () => {
this.onClose && this.onClose();
this.element.classList.remove('active');
window.removeEventListener('keydown', this._onKeyDown, {capture: true});
if(this.closeBtn) this.closeBtn.removeEventListener('click', this.destroy);
$rootScope.overlayIsActive = false;
setTimeout(() => {
this.element.remove();
this.onCloseAfterTimeout && this.onCloseAfterTimeout();
}, 1000);
}
};
}
export type PopupButton = {

View File

@ -1,11 +1,14 @@
import resizeableImage from "../lib/cropper";
import appDownloadManager from "../lib/appManagers/appDownloadManager";
import resizeableImage from "../lib/cropper";
import { PopupElement } from "./popup";
import { ripple } from "./ripple";
export default class PopupAvatar extends PopupElement {
private cropContainer: HTMLElement;
private input: HTMLInputElement;
private btnSubmit: HTMLElement;
private h6: HTMLElement;
export class PopupAvatar {
private container = document.getElementById('popup-avatar');
private input = this.container.querySelector('input') as HTMLInputElement;
private cropContainer = this.container.querySelector('.crop') as HTMLDivElement;
private closeBtn = this.container.querySelector('.popup-close') as HTMLButtonElement;
private image = new Image();
private canvas: HTMLCanvasElement;
@ -18,18 +21,31 @@ export class PopupAvatar {
private onCrop: (upload: () => ReturnType<typeof appDownloadManager.upload>) => void;
constructor() {
this.container.style.display = ''; // need for no blink
super('popup-avatar', null, {closable: true});
this.h6 = document.createElement('h6');
this.h6.innerText = 'Drag to Reposition';
this.closeBtn.classList.remove('btn-icon');
this.header.append(this.h6);
this.cropContainer = document.createElement('div');
this.cropContainer.classList.add('crop');
this.cropContainer.append(this.image);
this.input = document.createElement('input');
this.input.type = 'file';
this.input.style.display = 'none';
this.input.addEventListener('change', (e: any) => {
var file = e.target.files[0];
const file = e.target.files[0];
if(!file) {
return;
}
var reader = new FileReader();
const reader = new FileReader();
reader.onload = (e) => {
var contents = e.target.result as string;
const contents = e.target.result as string;
this.image = new Image();
this.cropContainer.append(this.image);
@ -39,9 +55,7 @@ export class PopupAvatar {
/* let {w, h} = calcImageInBox(this.image.naturalWidth, this.image.naturalHeight, 460, 554);
cropContainer.style.width = w + 'px';
cropContainer.style.height = h + 'px'; */
this.container.classList.remove('hide');
void this.container.offsetWidth; // reflow
this.container.classList.add('active');
this.show();
this.cropper = resizeableImage(this.image, this.canvas);
this.input.value = '';
@ -51,8 +65,10 @@ export class PopupAvatar {
reader.readAsDataURL(file);
}, false);
// apply
this.container.querySelector('.btn-crop').addEventListener('click', () => {
this.btnSubmit = document.createElement('button');
this.btnSubmit.className = 'btn-primary btn-circle btn-crop btn-icon tgico-check z-depth-1';
ripple(this.btnSubmit);
this.btnSubmit.addEventListener('click', () => {
this.cropper.crop();
this.closeBtn.click();
@ -63,16 +79,14 @@ export class PopupAvatar {
}, 'image/jpeg', 1);
});
this.closeBtn.addEventListener('click', () => {
setTimeout(() => {
this.cropper.removeHandlers();
if(this.image) {
this.image.remove();
}
this.container.append(this.cropContainer, this.btnSubmit, this.input);
this.container.classList.add('hide');
}, 200);
});
this.onCloseAfterTimeout = () => {
this.cropper.removeHandlers();
if(this.image) {
this.image.remove();
}
};
}
private resolve() {
@ -94,5 +108,3 @@ export class PopupAvatar {
ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
}
}
export default new PopupAvatar();

View File

@ -4,23 +4,12 @@ import appPollsManager, { Poll } from "../lib/appManagers/appPollsManager";
import $rootScope from "../lib/rootScope";
import { findUpTag, whichChild } from "../lib/utils";
import CheckboxField from "./checkbox";
import InputField from "./inputField";
import { PopupElement } from "./popup";
import RadioField from "./radioField";
import Scrollable from "./scrollable";
import { toast } from "./toast";
const InputField = (placeholder: string, label: string, name: string) => {
const div = document.createElement('div');
div.classList.add('input-field');
div.innerHTML = `
<input type="text" name="${name}" id="input-${name}" placeholder="${placeholder}" autocomplete="off" required="">
<label for="input-${name}">${label}</label>
`;
return div;
};
export default class PopupCreatePoll extends PopupElement {
private questionInput: HTMLInputElement;
private questions: HTMLElement;
@ -119,6 +108,19 @@ export default class PopupCreatePoll extends PopupElement {
this.scrollable = new Scrollable(this.body);
this.appendMoreField();
this.onEscape = () => {
return !this.getFilledAnswers().length;
};
}
private getFilledAnswers() {
const answers = Array.from(this.questions.children).map((el, idx) => {
const input = el.querySelector('input[type="text"]') as HTMLInputElement;
return input.value;
}).filter(v => !!v.trim());
return answers;
}
onSubmitClick = (e: MouseEvent) => {
@ -134,10 +136,7 @@ export default class PopupCreatePoll extends PopupElement {
return;
}
const answers = Array.from(this.questions.children).map((el, idx) => {
const input = el.querySelector('input[type="text"]') as HTMLInputElement;
return input.value;
}).filter(v => !!v.trim());
const answers = this.getFilledAnswers();
if(answers.length < 2) {
toast('Please enter at least two options.');

View File

@ -0,0 +1,285 @@
import { isTouchSupported } from "../helpers/touchSupport";
import appImManager from "../lib/appManagers/appImManager";
import appMessagesManager from "../lib/appManagers/appMessagesManager";
import { calcImageInBox } from "../lib/utils";
import { Layouter, RectPart } from "./groupedLayout";
import InputField from "./inputField";
import { PopupElement } from "./popup";
import { ripple } from "./ripple";
import { wrapDocument } from "./wrappers";
type SendFileParams = Partial<{
file: File,
objectURL: string,
width: number,
height: number,
duration: number
}>;
export default class PopupNewMedia extends PopupElement {
private btnSend: HTMLElement;
private input: HTMLInputElement;
private mediaContainer: HTMLElement;
private willAttach: Partial<{
type: 'media' | 'document',
isMedia: boolean,
sendFileDetails: SendFileParams[]
}> = {
sendFileDetails: []
};
constructor(files: File[], willAttachType: PopupNewMedia['willAttach']['type']) {
super('popup-send-photo popup-new-media', null, {closable: true});
this.willAttach.type = willAttachType;
this.btnSend = document.createElement('button');
this.btnSend.className = 'btn-primary';
this.btnSend.innerText = 'SEND';
ripple(this.btnSend);
this.btnSend.addEventListener('click', this.send, {once: true});
this.header.append(this.btnSend);
this.mediaContainer = document.createElement('div');
this.mediaContainer.classList.add('popup-photo');
const inputField = InputField('Add a caption...', 'Caption', 'photo-caption');
this.input = inputField.firstElementChild as HTMLInputElement;
this.container.append(this.mediaContainer, inputField);
this.attachFiles(files);
}
private onKeyDown = (e: KeyboardEvent) => {
const target = e.target as HTMLElement;
if(target.tagName != 'INPUT') {
this.input.focus();
}
if(e.key == 'Enter' && !isTouchSupported) {
this.btnSend.click();
}
};
public send = () => {
this.destroy();
let caption = this.input.value;
const willAttach = this.willAttach;
willAttach.isMedia = willAttach.type == 'media';
//console.log('will send files with options:', willAttach);
const peerID = appImManager.peerID;
const chatInputC = appImManager.chatInputC;
if(willAttach.sendFileDetails.length > 1 && willAttach.isMedia) {
appMessagesManager.sendAlbum(peerID, willAttach.sendFileDetails.map(d => d.file), Object.assign({
caption,
replyToMsgID: chatInputC.replyToMsgID
}, willAttach));
} else {
if(caption) {
if(willAttach.sendFileDetails.length > 1) {
appMessagesManager.sendText(peerID, caption, {replyToMsgID: chatInputC.replyToMsgID});
caption = '';
chatInputC.replyToMsgID = 0;
}
}
const promises = willAttach.sendFileDetails.map(params => {
const promise = appMessagesManager.sendFile(peerID, params.file, Object.assign({
//isMedia: willAttach.isMedia,
isMedia: params.file.type.includes('audio/') || willAttach.isMedia,
caption,
replyToMsgID: chatInputC.replyToMsgID
}, params));
caption = '';
chatInputC.replyToMsgID = 0;
return promise;
});
}
//Promise.all(promises);
//appMessagesManager.sendFile(appImManager.peerID, willAttach.file, willAttach);
chatInputC.onMessageSent();
};
public attachFile = (file: File) => {
const willAttach = this.willAttach;
return new Promise<HTMLDivElement>((resolve) => {
const params: SendFileParams = {};
params.file = file;
//console.log('selected file:', file, typeof(file), willAttach);
const itemDiv = document.createElement('div');
switch(willAttach.type) {
case 'media': {
const isVideo = file.type.indexOf('video/') === 0;
itemDiv.classList.add('popup-item-media');
if(isVideo) {
const video = document.createElement('video');
const source = document.createElement('source');
source.src = params.objectURL = URL.createObjectURL(file);
video.autoplay = false;
video.controls = false;
video.muted = true;
video.setAttribute('playsinline', '');
video.onloadeddata = () => {
params.width = video.videoWidth;
params.height = video.videoHeight;
params.duration = Math.floor(video.duration);
itemDiv.append(video);
resolve(itemDiv);
};
video.append(source);
} else {
const img = new Image();
img.src = params.objectURL = URL.createObjectURL(file);
img.onload = () => {
params.width = img.naturalWidth;
params.height = img.naturalHeight;
itemDiv.append(img);
resolve(itemDiv);
};
}
break;
}
case 'document': {
const isPhoto = file.type.indexOf('image/') !== -1;
if(isPhoto) {
params.objectURL = URL.createObjectURL(file);
}
const docDiv = wrapDocument({
file: file,
file_name: file.name || '',
size: file.size,
type: isPhoto ? 'photo' : 'doc',
url: params.objectURL
} as any, false, true);
const finish = () => {
itemDiv.append(docDiv);
resolve(itemDiv);
};
if(isPhoto) {
const img = new Image();
img.src = params.objectURL;
img.onload = () => {
params.width = img.naturalWidth;
params.height = img.naturalHeight;
finish();
};
img.onerror = finish;
} else {
finish();
}
break;
}
}
willAttach.sendFileDetails.push(params);
});
};
public attachFiles(files: File[]) {
const container = this.container;
const willAttach = this.willAttach;
files = files.filter(file => {
if(willAttach.type == 'media') {
return ['image/', 'video/'].find(s => file.type.indexOf(s) === 0);
} else {
return true;
}
});
if(files.length) {
if(willAttach.type == 'document') {
this.title.innerText = 'Send ' + (files.length > 1 ? files.length + ' Files' : 'File');
container.classList.add('is-document');
} else {
container.classList.add('is-media');
let foundPhotos = 0;
let foundVideos = 0;
files.forEach(file => {
if(file.type.indexOf('image/') === 0) ++foundPhotos;
else if(file.type.indexOf('video/') === 0) ++foundVideos;
});
if(foundPhotos && foundVideos) {
this.title.innerText = 'Send Album';
} else if(foundPhotos) {
this.title.innerText = 'Send ' + (foundPhotos > 1 ? foundPhotos + ' Photos' : 'Photo');
} else if(foundVideos) {
this.title.innerText = 'Send ' + (foundVideos > 1 ? foundVideos + ' Videos' : 'Video');
}
}
}
Promise.all(files.map(this.attachFile)).then(results => {
if(willAttach.type == 'media') {
if(willAttach.sendFileDetails.length > 1) {
container.classList.add('is-album');
const layouter = new Layouter(willAttach.sendFileDetails.map(o => ({w: o.width, h: o.height})), 380, 100, 4);
const layout = layouter.layout();
for(const {geometry, sides} of layout) {
const div = results.shift();
div.style.width = geometry.width + 'px';
div.style.height = geometry.height + 'px';
div.style.top = geometry.y + 'px';
div.style.left = geometry.x + 'px';
if(sides & RectPart.Right) {
this.mediaContainer.style.width = geometry.width + geometry.x + 'px';
}
if(sides & RectPart.Bottom) {
this.mediaContainer.style.height = geometry.height + geometry.y + 'px';
}
this.mediaContainer.append(div);
}
//console.log('chatInput album layout:', layout);
} else {
const params = willAttach.sendFileDetails[0];
const div = results[0];
const {w, h} = calcImageInBox(params.width, params.height, 380, 320);
div.style.width = w + 'px';
div.style.height = h + 'px';
this.mediaContainer.append(div);
}
} else {
this.mediaContainer.append(...results);
}
// show now
document.body.addEventListener('keydown', this.onKeyDown);
this.onClose = () => {
document.body.removeEventListener('keydown', this.onKeyDown);
};
this.show();
});
}
}

View File

@ -1,12 +1,12 @@
import { SliderTab } from "../../slider";
import popupAvatar from "../../popupAvatar";
import apiManager from "../../../lib/mtproto/mtprotoworker";
import appProfileManager from "../../../lib/appManagers/appProfileManager";
import appSidebarLeft from "..";
import Scrollable from "../../scrollable";
import appUsersManager from "../../../lib/appManagers/appUsersManager";
import $rootScope from "../../../lib/rootScope";
import { InputFile } from "../../../layer";
import appProfileManager from "../../../lib/appManagers/appProfileManager";
import appUsersManager from "../../../lib/appManagers/appUsersManager";
import apiManager from "../../../lib/mtproto/mtprotoworker";
import $rootScope from "../../../lib/rootScope";
import PopupAvatar from "../../popupAvatar";
import Scrollable from "../../scrollable";
import { SliderTab } from "../../slider";
// TODO: аватарка не поменяется в этой вкладке после изменения почему-то (если поставить в другом клиенте, и потом тут проверить, для этого ещё вышел в чатлист)
@ -36,7 +36,7 @@ export default class AppEditProfileTab implements SliderTab {
constructor() {
this.container.querySelector('.avatar-edit').addEventListener('click', () => {
popupAvatar.open(this.canvas, (_upload) => {
new PopupAvatar().open(this.canvas, (_upload) => {
this.uploadAvatar = _upload;
this.handleChange();
this.avatarElem.remove();

View File

@ -1,7 +1,7 @@
import { SliderTab } from "../../slider";
import popupAvatar from "../../popupAvatar";
import appChatsManager from "../../../lib/appManagers/appChatsManager";
import appSidebarLeft, { AppSidebarLeft } from "..";
import appChatsManager from "../../../lib/appManagers/appChatsManager";
import PopupAvatar from "../../popupAvatar";
import { SliderTab } from "../../slider";
export default class AppNewChannelTab implements SliderTab {
private container = document.querySelector('.new-channel-container') as HTMLDivElement;
@ -14,7 +14,7 @@ export default class AppNewChannelTab implements SliderTab {
constructor() {
this.container.querySelector('.avatar-edit').addEventListener('click', () => {
popupAvatar.open(this.canvas, (_upload) => {
new PopupAvatar().open(this.canvas, (_upload) => {
this.uploadAvatar = _upload;
});
});

View File

@ -1,11 +1,11 @@
import { SliderTab } from "../../slider";
import { SearchGroup } from "../../appSearch";
import popupAvatar from "../../popupAvatar";
import appChatsManager from "../../../lib/appManagers/appChatsManager";
import appSidebarLeft, { AppSidebarLeft } from "..";
import Scrollable from "../../scrollable";
import appChatsManager from "../../../lib/appManagers/appChatsManager";
import appDialogsManager from "../../../lib/appManagers/appDialogsManager";
import appUsersManager from "../../../lib/appManagers/appUsersManager";
import { SearchGroup } from "../../appSearch";
import PopupAvatar from "../../popupAvatar";
import Scrollable from "../../scrollable";
import { SliderTab } from "../../slider";
export default class AppNewGroupTab implements SliderTab {
private container = document.querySelector('.new-group-container') as HTMLDivElement;
@ -19,7 +19,7 @@ export default class AppNewGroupTab implements SliderTab {
constructor() {
this.container.querySelector('.avatar-edit').addEventListener('click', () => {
popupAvatar.open(this.canvas, (_upload) => {
new PopupAvatar().open(this.canvas, (_upload) => {
this.uploadAvatar = _upload;
});
});

View File

@ -93,7 +93,7 @@
<div class="container center-align">
<div class="scrollable scrollable-y">
<div class="auth-image"></div>
<h4 class="phone">Enter a password</h4>
<h4 class="phone">Enter Your Password</h4>
<p class="subtitle">Your account is protected with<br>an additional password</p>
<div class="input-wrapper">
<div class="input-field">
@ -131,17 +131,6 @@
</div>
</div>
</div>
<div class="popup popup-avatar hide" id="popup-avatar" style="display: none;">
<div class="popup-container z-depth-1">
<div class="popup-header">
<span class="popup-close tgico-close"></span>
<h6>Drag to Reposition</h6>
</div>
<div class="crop"></div>
<button class="btn-primary rp btn-circle btn-crop btn-icon tgico-check z-depth-1"></button>
<input type="file" style="display: none;" />
</div>
</div>
<div class="whole page-chats" style="display: none;" id="page-chats">
<svg style="position: absolute; top: -10000px; left: -10000px;">
<defs id="svg-defs">
@ -186,20 +175,6 @@
<div class="media-viewer-switcher media-viewer-switcher-right menu-next"><span class="tgico-down media-viewer-next-button"></span></div>
{{!-- </div> --}}
</div>
<div class="popup popup-send-photo popup-new-media">
<div class="popup-container z-depth-1">
<div class="popup-header">
<span class="btn-icon popup-close tgico-close"></span>
<div class="popup-title">Send Photo</div>
<button class="btn-primary rp">SEND</button>
</div>
<div class="popup-photo"></div>
<div class="input-field">
<input type="text" name="photo-caption" id="photo-caption" placeholder="Add a caption..." autocomplete="off" required />
<label for="photo-caption">Caption</label>
</div>
</div>
</div>
<div id="main-columns" class="tabs-container">
<div class="chats-container sidebar sidebar-left main-column" id="column-left">
<div class="sidebar-slider tabs-container">

View File

@ -6,6 +6,7 @@ import BubbleGroups from '../../components/bubbleGroups';
import { ChatAudio } from '../../components/chat/audio';
import { ChatContextMenu } from '../../components/chat/contextMenu';
import { ChatInput } from '../../components/chat/input';
import { MessageRender } from '../../components/chat/messageRender';
import PinnedContainer from '../../components/chat/pinnedContainer';
import ReplyContainer from '../../components/chat/replyContainer';
import { ChatSearch } from '../../components/chat/search';
@ -31,7 +32,7 @@ import apiManager from '../mtproto/mtprotoworker';
import { MOUNT_CLASS_TO } from '../mtproto/mtproto_config';
import { RichTextProcessor } from "../richtextprocessor";
import $rootScope from '../rootScope';
import { cancelEvent, findUpClassName, findUpTag, formatNumber, getObjectKeysAndSort, langPack, numberWithCommas, placeCaretAtEnd, whichChild } from "../utils";
import { cancelEvent, findUpClassName, findUpTag, getObjectKeysAndSort, langPack, numberWithCommas, placeCaretAtEnd, whichChild } from "../utils";
import apiUpdatesManager from './apiUpdatesManager';
import appChatsManager, { Channel, Chat } from "./appChatsManager";
import appDialogsManager from "./appDialogsManager";
@ -618,26 +619,14 @@ export class AppImManager {
});
let onKeyDown = (e: KeyboardEvent) => {
if($rootScope.overlayIsActive) return;
let target = e.target as HTMLElement;
//if(target.tagName == 'INPUT') return;
//this.log('onkeydown', e);
if(this.chatInputC.attachMediaPopUp.container.classList.contains('active')) {
if(target.tagName != 'INPUT') {
this.chatInputC.attachMediaPopUp.captionInput.focus();
}
if(e.key == 'Enter' && !isTouchSupported) {
this.chatInputC.attachMediaPopUp.sendBtn.click();
} else if(e.key == 'Escape') {
this.chatInputC.attachMediaPopUp.container.classList.remove('active');
}
return;
}
if(e.key == 'Escape') {
if(appMediaViewer.wholeDiv.classList.contains('active')) {
appMediaViewer.buttons.close.click();
@ -777,13 +766,14 @@ export class AppImManager {
}
public getAlbumBubble(groupID: string) {
let group = appMessagesManager.groupedMessagesStorage[groupID];
for(let i in group) {
let mid = +i;
if(this.bubbles[mid]) return {
bubble: this.bubbles[mid],
message: appMessagesManager.getMessage(mid)
};
const group = appMessagesManager.groupedMessagesStorage[groupID];
for(const mid in group) {
if(this.bubbles[mid]) {
return {
bubble: this.bubbles[mid],
message: appMessagesManager.getMessage(+mid)
};
}
}
return null;
@ -1622,42 +1612,6 @@ export class AppImManager {
return bubble;
}
// time section
let date = new Date(message.date * 1000);
let time = ('0' + date.getHours()).slice(-2) + ':' + ('0' + date.getMinutes()).slice(-2);
if(message.views) {
bubble.classList.add('channel-post');
time = formatNumber(message.views, 1) + ' <i class="tgico-channelviews"></i> ' + time;
if(!message.savedFrom) {
let forward = document.createElement('div');
forward.classList.add('bubble-beside-button', 'forward');
forward.innerHTML = `
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24">
<defs>
<path d="M13.55 3.24L13.64 3.25L13.73 3.27L13.81 3.29L13.9 3.32L13.98 3.35L14.06 3.39L14.14 3.43L14.22 3.48L14.29 3.53L14.36 3.59L14.43 3.64L22.23 10.85L22.36 10.99L22.48 11.15L22.57 11.31L22.64 11.48L22.69 11.66L22.72 11.85L22.73 12.04L22.71 12.22L22.67 12.41L22.61 12.59L22.53 12.76L22.42 12.93L22.29 13.09L22.23 13.15L14.43 20.36L14.28 20.48L14.12 20.58L13.95 20.66L13.77 20.72L13.58 20.76L13.4 20.77L13.22 20.76L13.03 20.73L12.85 20.68L12.68 20.61L12.52 20.52L12.36 20.4L12.22 20.27L12.16 20.2L12.1 20.13L12.05 20.05L12.01 19.98L11.96 19.9L11.93 19.82L11.89 19.73L11.87 19.65L11.84 19.56L11.83 19.47L11.81 19.39L11.81 19.3L11.8 19.2L11.8 16.42L11 16.49L10.23 16.58L9.51 16.71L8.82 16.88L8.18 17.09L7.57 17.33L7.01 17.6L6.48 17.91L5.99 18.26L5.55 18.64L5.14 19.05L4.77 19.51L4.43 19.99L4.29 20.23L4.21 20.35L4.11 20.47L4 20.57L3.88 20.65L3.75 20.72L3.62 20.78L3.48 20.82L3.33 20.84L3.19 20.84L3.04 20.83L2.9 20.79L2.75 20.74L2.62 20.68L2.53 20.62L2.45 20.56L2.38 20.5L2.31 20.43L2.25 20.36L2.2 20.28L2.15 20.19L2.11 20.11L2.07 20.02L2.04 19.92L2.02 19.83L2.01 19.73L2 19.63L2.04 17.99L2.19 16.46L2.46 15.05L2.85 13.75L3.35 12.58L3.97 11.53L4.7 10.6L5.55 9.8L6.51 9.12L7.59 8.56L8.77 8.13L10.07 7.83L11.48 7.65L11.8 7.63L11.8 4.8L11.91 4.56L12.02 4.35L12.14 4.16L12.25 3.98L12.37 3.82L12.48 3.68L12.61 3.56L12.73 3.46L12.85 3.38L12.98 3.31L13.11 3.27L13.24 3.24L13.37 3.23L13.46 3.23L13.55 3.24Z" id="b13RmHDQtl"></path>
</defs>
<use xlink:href="#b13RmHDQtl" opacity="1" fill="#fff" fill-opacity="1"></use>
</svg>`;
bubbleContainer.append(forward);
bubble.classList.add('with-beside-button');
}
}
if(message.edit_date) {
bubble.classList.add('is-edited');
time = '<i class="edited">edited</i> ' + time;
}
let timeSpan = document.createElement('span');
timeSpan.classList.add('time');
let timeInner = document.createElement('div');
timeInner.classList.add('inner', 'tgico');
timeInner.innerHTML = time;
let messageMessage: string, totalEntities: any[];
if(message.grouped_id) {
@ -1676,9 +1630,7 @@ export class AppImManager {
messageMessage = undefined;
totalEntities = undefined;
}
}
if(!messageMessage && !totalEntities) {
} else {
messageMessage = message.message;
totalEntities = message.totalEntities;
}
@ -1725,8 +1677,7 @@ export class AppImManager {
messageDiv.innerHTML = richText;
}
timeSpan.appendChild(timeInner);
messageDiv.append(timeSpan);
const timeSpan = MessageRender.setTime(message, bubble, bubbleContainer, messageDiv);
bubbleContainer.prepend(messageDiv);
//bubble.prepend(timeSpan, messageDiv); // that's bad

View File

@ -78,6 +78,7 @@ const $rootScope = {
document.removeEventListener(name, callback);
},
overlayIsActive: false,
selectedPeerID: 0,
myID: 0,
idle: {

View File

@ -1,9 +1,9 @@
import {putPreloader} from '../components/misc';
import pageIm from './pageIm';
import { putPreloader } from '../components/misc';
import PopupAvatar from '../components/popupAvatar';
//import apiManager from '../lib/mtproto/apiManager';
import apiManager from '../lib/mtproto/mtprotoworker';
import Page from './page';
import popupAvatar from '../components/popupAvatar';
import pageIm from './pageIm';
let authCode: {
'phone_number': string,
@ -17,7 +17,7 @@ let onFirstMount = () => import('../lib/appManagers/appProfileManager').then(imp
let uploadAvatar: () => Promise<any>;
pageElement.querySelector('.auth-image').addEventListener('click', () => {
popupAvatar.open(avatarPreview, (_uploadAvatar) => {
new PopupAvatar().open(avatarPreview, (_uploadAvatar) => {
uploadAvatar = _uploadAvatar;
});
});

View File

@ -681,10 +681,7 @@
padding: 1rem;
&-subtitle {
line-height: 13px !important;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
line-height: 1 !important;
max-width: 280px;
}
}
@ -714,7 +711,7 @@
}
&-border {
height: 32px;
height: 2rem;
border-radius: 1px;
min-width: 2px;
background: $color-blue;
@ -727,7 +724,7 @@
overflow: hidden;
pointer-events: none;
position: relative;
height: 32px;
height: 2rem;
display: flex;
flex-direction: column;
justify-content: space-between;
@ -752,14 +749,13 @@
}
&-subtitle {
white-space: nowrap;
color: #111;
}
&-media {
height: 32px;
width: 32px;
border-radius: 8px;
height: 2rem;
width: 2rem;
border-radius: .5rem;
overflow: hidden;
position: absolute;
left: 0;
@ -775,13 +771,13 @@
}
img.emoji {
height: 16px;
width: 16px;
height: 1rem;
width: 1rem;
vertical-align: top;
}
span.emoji {
font-size: 16px;
font-size: 1rem;
vertical-align: unset;
}
}