Eduard Kuzmenko
4 years ago
19 changed files with 580 additions and 213 deletions
@ -0,0 +1,5 @@ |
|||||||
|
import InputField from "../inputField"; |
||||||
|
|
||||||
|
export default interface Monkey { |
||||||
|
attachToInputField: (inputField: InputField) => void; |
||||||
|
} |
@ -0,0 +1,59 @@ |
|||||||
|
import lottieLoader, { RLottiePlayer } from "../../lib/lottieLoader"; |
||||||
|
import PasswordInputField from "../passwordInputField"; |
||||||
|
|
||||||
|
export default class PasswordMonkey { |
||||||
|
public container: HTMLElement; |
||||||
|
public animation: RLottiePlayer; |
||||||
|
public needFrame = 0; |
||||||
|
protected loadPromise: Promise<any>; |
||||||
|
|
||||||
|
constructor(protected passwordInputField: PasswordInputField, protected size: number) { |
||||||
|
this.container = document.createElement('div'); |
||||||
|
this.container.classList.add('media-sticker-wrapper'); |
||||||
|
} |
||||||
|
|
||||||
|
public load() { |
||||||
|
if(this.loadPromise) return this.loadPromise; |
||||||
|
return this.loadPromise = lottieLoader.loadAnimationFromURL({ |
||||||
|
container: this.container, |
||||||
|
loop: false, |
||||||
|
autoplay: false, |
||||||
|
width: this.size, |
||||||
|
height: this.size, |
||||||
|
noCache: true |
||||||
|
//}, 'assets/img/TwoFactorSetupMonkeyClose.tgs').then(_animation => {
|
||||||
|
}, 'assets/img/TwoFactorSetupMonkeyPeek.tgs').then(_animation => { |
||||||
|
//return;
|
||||||
|
this.animation = _animation; |
||||||
|
this.animation.addListener('enterFrame', currentFrame => { |
||||||
|
//console.log('enterFrame', currentFrame, this.needFrame);
|
||||||
|
|
||||||
|
if((this.animation.direction === 1 && currentFrame >= this.needFrame) || |
||||||
|
(this.animation.direction === -1 && currentFrame <= this.needFrame)) { |
||||||
|
this.animation.setSpeed(1); |
||||||
|
this.animation.pause(); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
this.passwordInputField.onVisibilityClickAdditional = () => { |
||||||
|
if(this.passwordInputField.passwordVisible) { |
||||||
|
this.animation.setDirection(1); |
||||||
|
this.animation.curFrame = 0; |
||||||
|
this.needFrame = 16; |
||||||
|
this.animation.play(); |
||||||
|
} else { |
||||||
|
this.animation.setDirection(-1); |
||||||
|
this.animation.curFrame = 16; |
||||||
|
this.needFrame = 0; |
||||||
|
this.animation.play(); |
||||||
|
} |
||||||
|
}; |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
public remove() { |
||||||
|
if(this.animation) { |
||||||
|
this.animation.remove(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,41 @@ |
|||||||
|
import { cancelEvent } from "../helpers/dom"; |
||||||
|
import InputField from "./inputField"; |
||||||
|
|
||||||
|
export default class PasswordInputField extends InputField { |
||||||
|
public passwordVisible = false; |
||||||
|
public toggleVisible: HTMLElement; |
||||||
|
public onVisibilityClickAdditional: () => void; |
||||||
|
|
||||||
|
constructor(options: { |
||||||
|
label?: string, |
||||||
|
name?: string |
||||||
|
} = {}) { |
||||||
|
super({ |
||||||
|
plainText: true, |
||||||
|
...options |
||||||
|
}); |
||||||
|
|
||||||
|
const input = this.input as HTMLInputElement; |
||||||
|
input.type = 'password'; |
||||||
|
input.setAttribute('required', ''); |
||||||
|
input.autocomplete = 'off'; |
||||||
|
|
||||||
|
const toggleVisible = this.toggleVisible = document.createElement('span'); |
||||||
|
toggleVisible.classList.add('toggle-visible', 'tgico'); |
||||||
|
|
||||||
|
this.container.classList.add('input-field-password'); |
||||||
|
this.container.append(toggleVisible); |
||||||
|
|
||||||
|
toggleVisible.addEventListener('click', this.onVisibilityClick); |
||||||
|
toggleVisible.addEventListener('touchend', this.onVisibilityClick); |
||||||
|
} |
||||||
|
|
||||||
|
public onVisibilityClick = (e: Event) => { |
||||||
|
cancelEvent(e); |
||||||
|
this.passwordVisible = !this.passwordVisible; |
||||||
|
|
||||||
|
this.toggleVisible.classList.toggle('eye-hidden', this.passwordVisible); |
||||||
|
(this.input as HTMLInputElement).type = this.passwordVisible ? 'text' : 'password'; |
||||||
|
this.onVisibilityClickAdditional && this.onVisibilityClickAdditional(); |
||||||
|
}; |
||||||
|
} |
@ -0,0 +1,18 @@ |
|||||||
|
import PopupElement, { addCancelButton, PopupButton, PopupOptions } from "."; |
||||||
|
|
||||||
|
export default class PopupConfirmAction extends PopupElement { |
||||||
|
constructor(className: string, buttons: PopupButton[], options: PopupOptions & Partial<{title: string, text: string}> = {}) { |
||||||
|
super('popup-peer popup-confirm-action ' + className, addCancelButton(buttons), { |
||||||
|
overlayClosable: true, |
||||||
|
...options |
||||||
|
}); |
||||||
|
|
||||||
|
this.title.innerHTML = options.title || 'Warning'; |
||||||
|
|
||||||
|
const p = document.createElement('p'); |
||||||
|
p.classList.add('popup-description'); |
||||||
|
p.innerHTML = options.text; |
||||||
|
|
||||||
|
this.container.insertBefore(p, this.header.nextElementSibling); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,135 @@ |
|||||||
|
import AppTwoStepVerificationTab from "."; |
||||||
|
import { SettingSection } from "../.."; |
||||||
|
import { attachClickEvent, cancelEvent } from "../../../../helpers/dom"; |
||||||
|
import { AccountPassword } from "../../../../layer"; |
||||||
|
import passwordManager from "../../../../lib/mtproto/passwordManager"; |
||||||
|
import Button from "../../../button"; |
||||||
|
import { putPreloader } from "../../../misc"; |
||||||
|
import PasswordMonkey from "../../../monkeys/password"; |
||||||
|
import PasswordInputField from "../../../passwordInputField"; |
||||||
|
import { ripple } from "../../../ripple"; |
||||||
|
import SidebarSlider, { SliderSuperTab } from "../../../slider"; |
||||||
|
import AppTwoStepVerificationReEnterPasswordTab from "./reEnterPassword"; |
||||||
|
|
||||||
|
export default class AppTwoStepVerificationEnterPasswordTab extends SliderSuperTab { |
||||||
|
public state: AccountPassword; |
||||||
|
|
||||||
|
constructor(slider: SidebarSlider) { |
||||||
|
super(slider, true); |
||||||
|
} |
||||||
|
|
||||||
|
protected init() { |
||||||
|
this.container.classList.add('two-step-verification-enter-password'); |
||||||
|
this.title.innerHTML = this.state.pFlags.has_password ? 'Enter Your Password' : 'Enter a Password'; |
||||||
|
|
||||||
|
const section = new SettingSection({ |
||||||
|
noDelimiter: true |
||||||
|
}); |
||||||
|
|
||||||
|
const inputWrapper = document.createElement('div'); |
||||||
|
inputWrapper.classList.add('input-wrapper'); |
||||||
|
|
||||||
|
const passwordInputField = new PasswordInputField({ |
||||||
|
name: 'first-password', |
||||||
|
label: this.state.pFlags.has_password ? this.state.hint ?? 'Password' : 'Enter a Password' |
||||||
|
}); |
||||||
|
|
||||||
|
const monkey = new PasswordMonkey(passwordInputField, 157); |
||||||
|
monkey.load(); |
||||||
|
|
||||||
|
const btnContinue = Button('btn-primary', {text: 'CONTINUE'}); |
||||||
|
|
||||||
|
inputWrapper.append(passwordInputField.container, btnContinue); |
||||||
|
section.content.append(monkey.container, inputWrapper); |
||||||
|
|
||||||
|
this.scrollable.container.append(section.container); |
||||||
|
|
||||||
|
passwordInputField.input.addEventListener('keypress', (e) => { |
||||||
|
if(passwordInputField.input.classList.contains('error')) { |
||||||
|
passwordInputField.input.classList.remove('error'); |
||||||
|
btnContinue.innerText = 'CONTINUE'; |
||||||
|
ripple(btnContinue); |
||||||
|
} |
||||||
|
|
||||||
|
if(e.key === 'Enter') { |
||||||
|
return btnContinue.click(); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
const verifyInput = () => { |
||||||
|
if(!passwordInputField.value.length) { |
||||||
|
passwordInputField.input.classList.add('error'); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
return true; |
||||||
|
}; |
||||||
|
|
||||||
|
if(this.state.pFlags.has_password) { |
||||||
|
let getStateInterval: number; |
||||||
|
|
||||||
|
let getState = () => { |
||||||
|
// * just to check session relevance
|
||||||
|
if(!getStateInterval) { |
||||||
|
getStateInterval = window.setInterval(getState, 10e3); |
||||||
|
} |
||||||
|
|
||||||
|
return passwordManager.getState().then(_state => { |
||||||
|
this.state = _state; |
||||||
|
|
||||||
|
passwordInputField.label.innerText = this.state.hint ?? 'Password'; |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
const submit = (e?: Event) => { |
||||||
|
if(!verifyInput()) { |
||||||
|
cancelEvent(e); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
btnContinue.setAttribute('disabled', 'true'); |
||||||
|
btnContinue.textContent = 'PLEASE WAIT...'; |
||||||
|
putPreloader(btnContinue); |
||||||
|
|
||||||
|
const plainPassword = passwordInputField.value; |
||||||
|
passwordManager.check(passwordInputField.value, this.state).then(auth => { |
||||||
|
console.log(auth); |
||||||
|
|
||||||
|
if(auth._ === 'auth.authorization') { |
||||||
|
clearInterval(getStateInterval); |
||||||
|
if(monkey) monkey.remove(); |
||||||
|
const tab = new AppTwoStepVerificationTab(this.slider); |
||||||
|
tab.state = this.state; |
||||||
|
tab.plainPassword = plainPassword; |
||||||
|
tab.open(); |
||||||
|
} |
||||||
|
}, (err) => { |
||||||
|
btnContinue.removeAttribute('disabled'); |
||||||
|
passwordInputField.input.classList.add('error'); |
||||||
|
|
||||||
|
switch(err.type) { |
||||||
|
default: |
||||||
|
//btnContinue.innerText = err.type;
|
||||||
|
btnContinue.innerText = 'INVALID PASSWORD'; |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
getState(); |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
attachClickEvent(btnContinue, submit); |
||||||
|
|
||||||
|
getState(); |
||||||
|
} else { |
||||||
|
attachClickEvent(btnContinue, (e) => { |
||||||
|
cancelEvent(e); |
||||||
|
if(!verifyInput()) return; |
||||||
|
|
||||||
|
const tab = new AppTwoStepVerificationReEnterPasswordTab(this.slider); |
||||||
|
tab.state = this.state; |
||||||
|
tab.open(); |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,74 @@ |
|||||||
|
import AppTwoStepVerificationTab from "."; |
||||||
|
import { SettingSection } from "../.."; |
||||||
|
import { attachClickEvent, cancelEvent } from "../../../../helpers/dom"; |
||||||
|
import { AccountPassword } from "../../../../layer"; |
||||||
|
import passwordManager from "../../../../lib/mtproto/passwordManager"; |
||||||
|
import Button from "../../../button"; |
||||||
|
import { putPreloader } from "../../../misc"; |
||||||
|
import PasswordMonkey from "../../../monkeys/password"; |
||||||
|
import PasswordInputField from "../../../passwordInputField"; |
||||||
|
import { ripple } from "../../../ripple"; |
||||||
|
import SidebarSlider, { SliderSuperTab } from "../../../slider"; |
||||||
|
|
||||||
|
export default class AppTwoStepVerificationReEnterPasswordTab extends SliderSuperTab { |
||||||
|
public state: AccountPassword; |
||||||
|
|
||||||
|
constructor(slider: SidebarSlider) { |
||||||
|
super(slider, true); |
||||||
|
} |
||||||
|
|
||||||
|
protected init() { |
||||||
|
this.container.classList.add('two-step-verification-enter-password'); |
||||||
|
this.title.innerHTML = 'Re-Enter your Password'; |
||||||
|
|
||||||
|
const section = new SettingSection({ |
||||||
|
noDelimiter: true |
||||||
|
}); |
||||||
|
|
||||||
|
const inputWrapper = document.createElement('div'); |
||||||
|
inputWrapper.classList.add('input-wrapper'); |
||||||
|
|
||||||
|
const passwordInputField = new PasswordInputField({ |
||||||
|
name: 're-enter-password', |
||||||
|
label: 'Re-Enter your Password' |
||||||
|
}); |
||||||
|
|
||||||
|
const monkey = new PasswordMonkey(passwordInputField, 157); |
||||||
|
monkey.load(); |
||||||
|
|
||||||
|
const btnContinue = Button('btn-primary', {text: 'CONTINUE'}); |
||||||
|
|
||||||
|
inputWrapper.append(passwordInputField.container, btnContinue); |
||||||
|
section.content.append(monkey.container, inputWrapper); |
||||||
|
|
||||||
|
this.scrollable.container.append(section.container); |
||||||
|
|
||||||
|
passwordInputField.input.addEventListener('keypress', (e) => { |
||||||
|
if(passwordInputField.input.classList.contains('error')) { |
||||||
|
passwordInputField.input.classList.remove('error'); |
||||||
|
btnContinue.innerText = 'CONTINUE'; |
||||||
|
ripple(btnContinue); |
||||||
|
} |
||||||
|
|
||||||
|
if(e.key === 'Enter') { |
||||||
|
return btnContinue.click(); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
const verifyInput = () => { |
||||||
|
if(!passwordInputField.value.length) { |
||||||
|
passwordInputField.input.classList.add('error'); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
return true; |
||||||
|
}; |
||||||
|
|
||||||
|
attachClickEvent(btnContinue, (e) => { |
||||||
|
cancelEvent(e); |
||||||
|
if(!verifyInput()) return; |
||||||
|
|
||||||
|
|
||||||
|
}); |
||||||
|
} |
||||||
|
} |
@ -1,49 +1,3 @@ |
|||||||
.page-password { |
/* .page-password { |
||||||
.input-field-input { |
|
||||||
padding-right: 2.5rem; |
|
||||||
max-height: 54px; |
|
||||||
|
|
||||||
&[type="password"] { |
} */ |
||||||
font-size: 2.25rem; |
|
||||||
padding-left: 10px; |
|
||||||
|
|
||||||
@media (-webkit-min-device-pixel-ratio: 2) { |
|
||||||
font-size: 1.75rem; |
|
||||||
letter-spacing: .125rem; |
|
||||||
} |
|
||||||
|
|
||||||
html.is-ios & { |
|
||||||
// ! меньше 1rem будет зумить поле |
|
||||||
font-size: 1rem; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
.toggle-visible { |
|
||||||
position: absolute; |
|
||||||
right: .25rem; |
|
||||||
z-index: 2; |
|
||||||
font-size: 1.25rem; |
|
||||||
color: $placeholder-color; |
|
||||||
cursor: pointer; |
|
||||||
transition: color .2s; |
|
||||||
padding: .5rem; |
|
||||||
display: flex; |
|
||||||
align-items: center; |
|
||||||
justify-content: center; |
|
||||||
top: 50%; |
|
||||||
transform: translateY(-50%); |
|
||||||
|
|
||||||
&:before { |
|
||||||
content: $tgico-eye1; |
|
||||||
} |
|
||||||
|
|
||||||
&.eye-hidden:before { |
|
||||||
content: $tgico-eye2; |
|
||||||
} |
|
||||||
|
|
||||||
@include hover() { |
|
||||||
color: #000; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
Loading…
Reference in new issue