morethanwords
4 years ago
42 changed files with 1777 additions and 1024 deletions
@ -0,0 +1,230 @@
@@ -0,0 +1,230 @@
|
||||
import appSidebarRight from "../lib/appManagers/appSidebarRight"; |
||||
import Scrollable from "./scrollable_new"; |
||||
import appProfileManager from "../lib/appManagers/appProfileManager"; |
||||
import { appPeersManager } from "../lib/services"; |
||||
import appMessagesManager from "../lib/appManagers/appMessagesManager"; |
||||
import appDialogsManager from "../lib/appManagers/appDialogsManager"; |
||||
import appChatsManager from "../lib/appManagers/appChatsManager"; |
||||
import appUsersManager from "../lib/appManagers/appUsersManager"; |
||||
import { $rootScope, findUpTag, findUpClassName, cancelEvent } from "../lib/utils"; |
||||
import { putPreloader } from "./misc"; |
||||
|
||||
class AppSelectPeers { |
||||
public container = document.createElement('div'); |
||||
private chatList = document.createElement('ul'); |
||||
private chatsContainer = document.createElement('div'); |
||||
private scrollable: Scrollable; |
||||
private selectedScrollable: Scrollable; |
||||
|
||||
private selectedContainer = document.createElement('div'); |
||||
private searchInput = document.createElement('input'); |
||||
|
||||
private selected: {[peerID: number]: HTMLDivElement} = {}; |
||||
|
||||
public freezed = false; |
||||
|
||||
constructor(private appendTo: HTMLDivElement, private onChange: (length: number) => void) { |
||||
this.container.classList.add('selector'); |
||||
|
||||
let topContainer = document.createElement('div'); |
||||
topContainer.classList.add('selector-search-container'); |
||||
|
||||
this.selectedContainer.classList.add('selector-search'); |
||||
this.searchInput.placeholder = 'Select chat'; |
||||
this.searchInput.type = 'text'; |
||||
this.selectedContainer.append(this.searchInput); |
||||
topContainer.append(this.selectedContainer); |
||||
this.selectedScrollable = new Scrollable(topContainer); |
||||
|
||||
let delimiter = document.createElement('hr'); |
||||
|
||||
this.chatsContainer.classList.add('chats-container'); |
||||
this.chatsContainer.append(this.chatList); |
||||
this.scrollable = new Scrollable(this.chatsContainer); |
||||
|
||||
// в десктопе - сначала без группы, потом архивные, потом контакты без сообщений
|
||||
let offsetIndex = 0; |
||||
appMessagesManager.getConversations(offsetIndex, 50, 0).then(value => { |
||||
let dialogs = value.dialogs; |
||||
let myID = $rootScope.myID; |
||||
|
||||
offsetIndex = dialogs[value.dialogs.length - 1].index || 0; |
||||
|
||||
if(dialogs[0].peerID != myID) { |
||||
dialogs.findAndSplice(d => d.peerID == myID); |
||||
dialogs.unshift({ |
||||
peerID: myID, |
||||
pFlags: {} |
||||
} as any); |
||||
} |
||||
|
||||
dialogs.forEach(dialog => { |
||||
let peerID = dialog.peerID; |
||||
|
||||
let {dom} = appDialogsManager.addDialog(dialog, this.chatList, false, false); |
||||
dom.containerEl.insertAdjacentHTML('afterbegin', '<div class="checkbox"><label><input type="checkbox"><span></span></label></div>'); |
||||
|
||||
let subtitle = ''; |
||||
if(peerID < 0) { |
||||
subtitle = appChatsManager.getChatMembersString(-peerID); |
||||
} else if(peerID == myID) { |
||||
subtitle = 'chat with yourself'; |
||||
} else { |
||||
subtitle = appUsersManager.getUserStatusString(peerID); |
||||
if(subtitle == 'online') { |
||||
subtitle = `<i>${subtitle}</i>`; |
||||
} |
||||
} |
||||
|
||||
dom.lastMessageSpan.innerHTML = subtitle; |
||||
}); |
||||
}); |
||||
|
||||
this.chatList.addEventListener('click', (e) => { |
||||
let target = e.target as HTMLElement; |
||||
cancelEvent(e); |
||||
|
||||
if(this.freezed) return; |
||||
|
||||
if(target.tagName != 'LI') { |
||||
target = findUpTag(target, 'LI'); |
||||
} |
||||
|
||||
if(!target) return; |
||||
|
||||
let peerID = +target.getAttribute('data-peerID'); |
||||
target.classList.toggle('active'); |
||||
if(peerID in this.selected) { |
||||
this.remove(peerID); |
||||
} else { |
||||
this.add(peerID); |
||||
} |
||||
|
||||
let checkbox = target.querySelector('input') as HTMLInputElement; |
||||
checkbox.checked = !checkbox.checked; |
||||
}); |
||||
|
||||
this.selectedContainer.addEventListener('click', (e) => { |
||||
if(this.freezed) return; |
||||
let target = e.target as HTMLElement; |
||||
target = findUpClassName(target, 'selector-user'); |
||||
|
||||
if(!target) return; |
||||
|
||||
let peerID = target.dataset.peerID; |
||||
let li = this.chatList.querySelector('[data-peerid="' + peerID + '"]') as HTMLElement; |
||||
li.click(); |
||||
}); |
||||
|
||||
this.container.append(topContainer, delimiter, this.chatsContainer); |
||||
appendTo.append(this.container); |
||||
} |
||||
|
||||
private add(peerID: number) { |
||||
let div = document.createElement('div'); |
||||
div.classList.add('selector-user', 'scale-in'); |
||||
div.dataset.peerID = '' + peerID; |
||||
this.selected[peerID] = div; |
||||
|
||||
let title = appPeersManager.getPeerTitle(peerID, false, true); |
||||
|
||||
let avatarDiv = document.createElement('div'); |
||||
avatarDiv.classList.add('user-avatar', 'tgico'); |
||||
appProfileManager.putPhoto(avatarDiv, peerID); |
||||
|
||||
div.innerHTML = title; |
||||
div.insertAdjacentElement('afterbegin', avatarDiv); |
||||
|
||||
this.selectedContainer.insertBefore(div, this.searchInput); |
||||
this.selectedScrollable.scrollTop = this.selectedScrollable.scrollHeight; |
||||
this.onChange(Object.keys(this.selected).length); |
||||
} |
||||
|
||||
private remove(peerID: number) { |
||||
let div = this.selected[peerID]; |
||||
div.classList.remove('scale-in'); |
||||
void div.offsetWidth; |
||||
div.classList.add('scale-out'); |
||||
div.addEventListener('animationend', () => { |
||||
delete this.selected[peerID]; |
||||
div.remove(); |
||||
this.onChange(Object.keys(this.selected).length); |
||||
}, {once: true}); |
||||
} |
||||
|
||||
public getSelected() { |
||||
return Object.keys(this.selected).map(p => +p); |
||||
} |
||||
} |
||||
|
||||
class AppForward { |
||||
private container = document.getElementById('forward-container') as HTMLDivElement; |
||||
private closeBtn = this.container.querySelector('.sidebar-close-button') as HTMLButtonElement; |
||||
private sendBtn = this.container.querySelector('.btn-circle') as HTMLButtonElement; |
||||
|
||||
private selector: AppSelectPeers; |
||||
private msgIDs: number[] = []; |
||||
|
||||
constructor() { |
||||
this.closeBtn.addEventListener('click', () => { |
||||
this.cleanup(); |
||||
this.container.classList.remove('active'); |
||||
appSidebarRight.onSidebarScroll(); |
||||
}); |
||||
|
||||
this.sendBtn.addEventListener('click', () => { |
||||
let peerIDs = this.selector.getSelected(); |
||||
|
||||
if(this.msgIDs.length && peerIDs.length) { |
||||
this.sendBtn.classList.remove('tgico-send'); |
||||
this.sendBtn.disabled = true; |
||||
putPreloader(this.sendBtn); |
||||
this.selector.freezed = true; |
||||
|
||||
let s = () => { |
||||
let promises = peerIDs.splice(0, 3).map(peerID => { |
||||
return appMessagesManager.forwardMessages(peerID, this.msgIDs); |
||||
}); |
||||
|
||||
Promise.all(promises).then(() => { |
||||
if(peerIDs.length) { |
||||
return s(); |
||||
} else { |
||||
this.closeBtn.click(); |
||||
} |
||||
}); |
||||
}; |
||||
|
||||
s(); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
public cleanup() { |
||||
if(this.selector) { |
||||
this.selector.container.remove(); |
||||
this.selector = null; |
||||
} |
||||
} |
||||
|
||||
public init(ids: number[]) { |
||||
this.cleanup(); |
||||
this.msgIDs = ids; |
||||
|
||||
appSidebarRight.toggleSidebar(true); |
||||
this.container.classList.add('active'); |
||||
this.sendBtn.innerHTML = ''; |
||||
this.sendBtn.classList.add('tgico-send'); |
||||
this.sendBtn.disabled = false; |
||||
|
||||
this.selector = new AppSelectPeers(this.container, (length) => { |
||||
if(length) { |
||||
this.sendBtn.classList.add('is-visible'); |
||||
} else { |
||||
this.sendBtn.classList.remove('is-visible'); |
||||
} |
||||
}); |
||||
} |
||||
} |
||||
|
||||
export default new AppForward(); |
@ -0,0 +1,158 @@
@@ -0,0 +1,158 @@
|
||||
//import apiManager from '../lib/mtproto/apiManager';
|
||||
import apiManager from '../lib/mtproto/mtprotoworker'; |
||||
import Page from './page'; |
||||
import pageIm from './pageIm'; |
||||
import pagePassword from './pagePassword'; |
||||
import { App } from '../lib/mtproto/mtproto_config'; |
||||
import { bytesToBase64, bytesCmp } from '../lib/bin_utils'; |
||||
import serverTimeManager from '../lib/mtproto/serverTimeManager'; |
||||
import { User } from '../lib/appManagers/appUsersManager'; |
||||
|
||||
/* interface Authorization { |
||||
_: 'authorization', |
||||
flags: number, |
||||
pFlags: Partial<{current: true, official_app: true, password_pending: true}>, |
||||
hash: number[], |
||||
device_model: string, |
||||
platform: string, |
||||
system_version: string, |
||||
api_id: number, |
||||
app_name: string, |
||||
app_version: string, |
||||
date_created: number, |
||||
date_active: number, |
||||
ip: string, |
||||
country: string, |
||||
region: string |
||||
}; */ |
||||
|
||||
interface AuthAuthorization { |
||||
flags: number, |
||||
pFlags: Partial<{tmp_sessions: number}>, |
||||
user: User |
||||
} |
||||
|
||||
interface LoginToken { |
||||
_: 'auth.loginToken', |
||||
expires: number, |
||||
token: Uint8Array |
||||
}; |
||||
|
||||
interface LoginTokenMigrateTo { |
||||
_: 'auth.loginTokenMigrateTo', |
||||
dc_id: number, |
||||
token: Uint8Array |
||||
}; |
||||
|
||||
interface LoginTokenSuccess { |
||||
_: 'auth.loginTokenSuccess', |
||||
authorization: AuthAuthorization |
||||
}; |
||||
|
||||
let onFirstMount = async() => { |
||||
const pageElement = page.pageEl; |
||||
const imageDiv = pageElement.querySelector('.auth-image') as HTMLDivElement; |
||||
|
||||
const results = await Promise.all([ |
||||
import('qr-code-styling' as any) |
||||
]); |
||||
const QRCodeStyling = results[0].default; |
||||
|
||||
let stop = false; |
||||
document.addEventListener('user_auth', () => { |
||||
stop = true; |
||||
}, {once: true}); |
||||
|
||||
let options: {dcID?: number} = {}; |
||||
let prevToken: Uint8Array; |
||||
|
||||
do { |
||||
if(stop) { |
||||
break; |
||||
} |
||||
|
||||
try { |
||||
let loginToken: LoginToken | LoginTokenMigrateTo | LoginTokenSuccess = await apiManager.invokeApi('auth.exportLoginToken', { |
||||
api_id: App.id, |
||||
api_hash: App.hash, |
||||
except_ids: [] |
||||
}/* , options */); |
||||
|
||||
if(loginToken._ == 'auth.loginTokenMigrateTo') { |
||||
if(!options.dcID) { |
||||
options.dcID = loginToken.dc_id; |
||||
apiManager.setBaseDcID(loginToken.dc_id); |
||||
//continue;
|
||||
} |
||||
|
||||
loginToken = await apiManager.invokeApi('auth.importLoginToken', { |
||||
token: loginToken.token |
||||
}, options) as LoginToken; |
||||
} |
||||
|
||||
if(loginToken._ == 'auth.loginTokenSuccess') { |
||||
let authorization = loginToken.authorization; |
||||
apiManager.setUserAuth({ |
||||
id: authorization.user.id |
||||
}); |
||||
pageIm.mount(); |
||||
break; |
||||
} |
||||
|
||||
/* // to base64 |
||||
var decoder = new TextDecoder('utf8'); |
||||
var b64encoded = btoa(String.fromCharCode.apply(null, [...loginToken.token])); */ |
||||
|
||||
if(!prevToken || !bytesCmp(prevToken, loginToken.token)) { |
||||
prevToken = loginToken.token; |
||||
|
||||
let encoded = bytesToBase64(loginToken.token); |
||||
let url = "tg://login?token=" + encoded.replace(/\+/g, "-").replace(/\//g, "_").replace(/\=+$/, ""); |
||||
|
||||
imageDiv.innerHTML = ''; |
||||
const qrCode = new QRCodeStyling({ |
||||
width: 166, |
||||
height: 166, |
||||
data: url, |
||||
image: "assets/img/logo_padded.svg", |
||||
dotsOptions: { |
||||
color: "#000000", |
||||
type: "rounded" |
||||
}, |
||||
imageOptions: { |
||||
imageSize: .75 |
||||
}, |
||||
backgroundOptions: { |
||||
color: "#ffffff" |
||||
}, |
||||
qrOptions: { |
||||
errorCorrectionLevel: "L" |
||||
} |
||||
}); |
||||
qrCode.append(imageDiv); |
||||
} |
||||
|
||||
let timestamp = Date.now() / 1000; |
||||
let diff = loginToken.expires - timestamp - serverTimeManager.serverTimeOffset; |
||||
|
||||
await new Promise((resolve, reject) => setTimeout(resolve, diff > 5 ? 5e3 : 1e3 * diff | 0)); |
||||
} catch(err) { |
||||
switch(err.type) { |
||||
case 'SESSION_PASSWORD_NEEDED': |
||||
console.warn('pageSignQR: SESSION_PASSWORD_NEEDED'); |
||||
err.handled = true; |
||||
pagePassword.mount(); |
||||
break; |
||||
default: |
||||
console.error('pageSignQR: default error:', err); |
||||
break; |
||||
} |
||||
} |
||||
} while(true); |
||||
}; |
||||
|
||||
const page = new Page('page-signQR', true, () => { |
||||
onFirstMount(); |
||||
}); |
||||
|
||||
export default page; |
@ -0,0 +1,151 @@
@@ -0,0 +1,151 @@
|
||||
@keyframes scaleIn { |
||||
0% { |
||||
transform: scale(.2); |
||||
} |
||||
|
||||
to { |
||||
transform: scale(1); |
||||
} |
||||
} |
||||
|
||||
.selector { |
||||
height: 100%; |
||||
display: flex; |
||||
flex-direction: column; |
||||
|
||||
&-search-container { |
||||
flex: 1 1 auto; |
||||
position: relative; |
||||
max-height: 132px; |
||||
|
||||
.scrollable { |
||||
position: relative; |
||||
} |
||||
} |
||||
|
||||
&-search { |
||||
padding: 0 24px 0 24px; |
||||
display: flex; |
||||
flex-flow: wrap; |
||||
|
||||
input { |
||||
border: none; |
||||
padding: 7px 0px 19px 0px; |
||||
outline: none; |
||||
flex: 1 1 auto; |
||||
} |
||||
} |
||||
|
||||
&-user { |
||||
color: #000; |
||||
background-color: rgba(112, 117, 121, 0.08); |
||||
font-size: 16px; |
||||
padding: 0 17px 0px 0px; |
||||
line-height: 31px; |
||||
margin-left: -4px; |
||||
margin-right: 12px; |
||||
height: 32px; |
||||
margin-bottom: 7px; |
||||
border-radius: 24px; |
||||
user-select: none; |
||||
flex: 0 0 auto; |
||||
transition: .2s all; |
||||
|
||||
&:hover { |
||||
background-color: #fae2e3; |
||||
cursor: pointer; |
||||
|
||||
.user-avatar:after { |
||||
opacity: 1; |
||||
} |
||||
} |
||||
|
||||
&.scale-in { |
||||
animation: scaleIn .15s ease forwards; |
||||
} |
||||
|
||||
&.scale-out { |
||||
animation: scaleIn .1s ease forwards; |
||||
animation-direction: reverse; |
||||
} |
||||
|
||||
.user-avatar { |
||||
height: 32px !important; |
||||
width: 32px !important; |
||||
float: left; |
||||
margin-right: 8px; |
||||
overflow: hidden; |
||||
font-size: 14px; |
||||
|
||||
&:after { |
||||
position: absolute; |
||||
content: $tgico-close; |
||||
left: 0; |
||||
top: 0; |
||||
background-color: #df3f40; |
||||
height: 100%; |
||||
width: 100%; |
||||
z-index: 2; |
||||
font-size: 23px; |
||||
line-height: 32px; |
||||
opacity: 0; |
||||
transition: .2s opacity; |
||||
transform: scaleX(-1); |
||||
} |
||||
} |
||||
} |
||||
|
||||
.chats-container { |
||||
height: 100%; |
||||
flex: 1 1 auto; |
||||
} |
||||
|
||||
ul { |
||||
.user-avatar { |
||||
height: 48px; |
||||
width: 48px; |
||||
} |
||||
|
||||
.user-caption { |
||||
padding: 1px 3.5px 1px 12px; |
||||
} |
||||
|
||||
p { |
||||
height: 24px; |
||||
} |
||||
|
||||
span.user-title { |
||||
font-weight: normal; |
||||
} |
||||
|
||||
span.user-last-message { |
||||
font-size: 14px; |
||||
} |
||||
|
||||
li { |
||||
padding-bottom: 0; |
||||
|
||||
> .rp { |
||||
margin: 0px 9px 0px 8px; |
||||
padding: 12px 8.5px; |
||||
} |
||||
} |
||||
} |
||||
|
||||
hr { |
||||
width: 100%; |
||||
height: 1px; |
||||
border: none; |
||||
background-color: #DADCE0; |
||||
margin: 0 0 8px; |
||||
} |
||||
|
||||
[type="checkbox"] + span { |
||||
padding-left: calc(9px + 2.25rem); |
||||
} |
||||
|
||||
.checkbox { |
||||
margin-top: 11px; |
||||
padding-left: 11px; |
||||
} |
||||
} |
@ -0,0 +1,79 @@
@@ -0,0 +1,79 @@
|
||||
.menu-horizontal { |
||||
color: $darkgrey; |
||||
border-bottom: 1px solid $lightgrey; |
||||
position: relative; |
||||
|
||||
ul { |
||||
width: 100%; |
||||
height: 100%; |
||||
margin: 0; |
||||
display: flex; |
||||
justify-content: space-around; |
||||
align-items: center; |
||||
position: relative; |
||||
z-index: 2; |
||||
} |
||||
|
||||
li { |
||||
display: inline-block; |
||||
padding: .75rem 1rem; |
||||
cursor: pointer; |
||||
text-align: center; |
||||
flex: 1; |
||||
user-select: none; |
||||
font-size: 1rem; |
||||
font-weight: 500; |
||||
|
||||
&.active { |
||||
color: $blue; |
||||
} |
||||
} |
||||
|
||||
&__stripe { |
||||
position: absolute; |
||||
background: $blue; |
||||
//left: 0; |
||||
left: -2px; |
||||
transition: .3s transform, .3s width; |
||||
//transition: .3s transform; |
||||
bottom: -1px; |
||||
height: 4px; |
||||
width: 1px; // need if using transform |
||||
transform: scaleX(1) translateX(0px); |
||||
border-top-left-radius: 2px; |
||||
border-top-right-radius: 2px; |
||||
z-index: 1; |
||||
} |
||||
} |
||||
|
||||
.tabs-container { |
||||
min-width: 100%; |
||||
width: 100%; |
||||
display: flex; |
||||
/* overflow: hidden; */ |
||||
overflow-x: hidden; |
||||
|
||||
&.animated { |
||||
transition: .3s transform; |
||||
} |
||||
|
||||
> div { |
||||
width: 100%; |
||||
max-width: 100%; |
||||
overflow: hidden; |
||||
/* transition: .2s all; */ |
||||
display: none; |
||||
|
||||
&.active { |
||||
display: flex; |
||||
flex-direction: column; |
||||
} |
||||
|
||||
> div:not(.scroll-padding) { |
||||
width: 100%; |
||||
max-width: 100%; |
||||
/* overflow: hidden; */ |
||||
position: relative; |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue