From 6475479390a0e22c972d7d84c0014eaca43fc4fa Mon Sep 17 00:00:00 2001 From: morethanwords Date: Wed, 31 Mar 2021 17:02:23 +0400 Subject: [PATCH] Temp commit --- src/index.hbs | 8 +- src/langSign.ts | 23 ++++++ src/lib/langPack.ts | 121 ++++++++++++++++++++-------- src/pages/pageSignIn.ts | 75 ++++++++++++++--- src/pages/pageSignQR.ts | 39 +++++++-- src/scss/partials/pages/_pages.scss | 30 ++++--- 6 files changed, 224 insertions(+), 72 deletions(-) create mode 100644 src/langSign.ts diff --git a/src/index.hbs b/src/index.hbs index 8437504d..659028f2 100644 --- a/src/index.hbs +++ b/src/index.hbs @@ -37,17 +37,11 @@ -

Sign in to Telegram

-

Please confirm your country and
enter your phone number.

-
- -
-

Scan from mobile Telegram

-

1. Open Telegram on your phone
2. Go to settings > Devices > Scan QR
3. Scan this image to Log in

+
diff --git a/src/langSign.ts b/src/langSign.ts new file mode 100644 index 00000000..c8c2e0fb --- /dev/null +++ b/src/langSign.ts @@ -0,0 +1,23 @@ +const lang = { + "Login.Title": "Sign in to Telegram", + "Login.CountrySelectorLabel": "Country", + "Login.PhoneLabel": "Phone Number", + "Login.PhoneLabelInvalid": "Phone Number Invalid", + "Login.KeepSigned": "Keep me signed in", + "Login.StartText": "Please confirm your country and\nenter your phone number.", + + // * android + + + // * macos + "Login.Next": "Next", + "Login.ContinueOnLanguage": "Continue in English", + "Login.QR.Title": "Log in to Telegram by QR Code", + "Login.QR.Help1": "Open Telegram on your phone", + "Login.QR.Help2": "Go to **Settings** > **Devices** > **Scan QR**", + "Login.QR.Help3": "Point your phone at this screen to confirm login", + "Login.QR.Cancel": "Log in by phone Number", + "Login.QR.Login": "Log in by QR Code", +}; + +export default lang; diff --git a/src/lib/langPack.ts b/src/lib/langPack.ts index 5f11af66..c6dfe931 100644 --- a/src/lib/langPack.ts +++ b/src/lib/langPack.ts @@ -2,6 +2,7 @@ import DEBUG, { MOUNT_CLASS_TO } from "../config/debug"; import { safeAssign } from "../helpers/object"; import { capitalizeFirstLetter } from "../helpers/string"; import type lang from "../lang"; +import type langSign from "../langSign"; import { LangPackDifference, LangPackString } from "../layer"; import apiManager from "./mtproto/mtprotoworker"; import sessionStorage from "./sessionStorage"; @@ -41,21 +42,23 @@ export const langPack: {[actionType: string]: LangPackKey} = { "messageActionBotAllowed": "Chat.Service.BotPermissionAllowed" }; -export type LangPackKey = string | keyof typeof lang; +export type LangPackKey = string | keyof typeof lang | keyof typeof langSign; namespace I18n { export const strings: Map = new Map(); let pluralRules: Intl.PluralRules; - let lastRequestedLangCode: string; + let cacheLangPackPromise: Promise; + export let lastRequestedLangCode: string; export function getCacheLangPack(): Promise { - return Promise.all([ + if(cacheLangPackPromise) return cacheLangPackPromise; + return cacheLangPackPromise = Promise.all([ sessionStorage.get('langPack'), polyfillPromise ]).then(([langPack]) => { if(!langPack/* || true */) { - return getLangPack('en'); - } else if(DEBUG) { + return loadLocalLangPack(); + } else if(DEBUG/* && false */) { return getLangPack(langPack.lang_code); } else if(langPack.appVersion !== App.langPackVersion) { return getLangPack(langPack.lang_code); @@ -67,41 +70,87 @@ namespace I18n { applyLangPack(langPack); return langPack; + }).finally(() => { + cacheLangPackPromise = undefined; }); } - export function getLangPack(langCode: string) { - lastRequestedLangCode = langCode; + export function loadLocalLangPack() { + const defaultCode = 'en'; + lastRequestedLangCode = defaultCode; return Promise.all([ - apiManager.invokeApi('langpack.getLangPack', { + import('../lang'), + import('../langSign') + ]).then(([lang, langSign]) => { + const strings: LangPackString[] = []; + formatLocalStrings(lang, strings); + formatLocalStrings(langSign, strings); + + const langPack: LangPackDifference = { + _: 'langPackDifference', + from_version: 0, + lang_code: defaultCode, + strings, + version: 0 + }; + return saveLangPack(langPack); + }); + } + + export function loadLangPack(langCode: string) { + return Promise.all([ + apiManager.invokeApiCacheable('langpack.getLangPack', { lang_code: langCode, - lang_pack: 'macos' + lang_pack: App.langPack }), - apiManager.invokeApi('langpack.getLangPack', { + apiManager.invokeApiCacheable('langpack.getLangPack', { lang_code: langCode, lang_pack: 'android' }), import('../lang'), + import('../langSign'), polyfillPromise - ]).then(([langPack, _langPack, __langPack, _]) => { - let strings: LangPackString[] = []; - for(const i in __langPack.default) { - // @ts-ignore - const v = __langPack.default[i]; - if(typeof(v) === 'string') { - strings.push({ - _: 'langPackString', - key: i, - value: v - }); - } else { - strings.push({ - _: 'langPackStringPluralized', - key: i, - ...v - }); - } + ]); + } + + export function getStrings(langCode: string, strings: string[]) { + return apiManager.invokeApi('langpack.getStrings', { + lang_pack: App.langPack, + lang_code: langCode, + keys: strings + }); + } + + export function formatLocalStrings(strings: any, pushTo: LangPackString[] = []) { + for(const i in strings) { + // @ts-ignore + const v = strings[i]; + if(typeof(v) === 'string') { + pushTo.push({ + _: 'langPackString', + key: i, + value: v + }); + } else { + pushTo.push({ + _: 'langPackStringPluralized', + key: i, + ...v + }); } + } + + return pushTo; + } + + export function getLangPack(langCode: string) { + lastRequestedLangCode = langCode; + return loadLangPack(langCode).then(([langPack, _langPack, __langPack, ___langPack, _]) => { + let strings: LangPackString[] = []; + + [__langPack, ___langPack].forEach(l => { + formatLocalStrings(l.default as any, strings); + }); strings = strings.concat(langPack.strings); @@ -110,13 +159,17 @@ namespace I18n { } langPack.strings = strings; - // @ts-ignore - langPack.appVersion = App.langPackVersion; + return saveLangPack(langPack); + }); + } - return sessionStorage.set({langPack}).then(() => { - applyLangPack(langPack); - return langPack; - }); + export function saveLangPack(langPack: LangPackDifference) { + // @ts-ignore + langPack.appVersion = App.langPackVersion; + + return sessionStorage.set({langPack}).then(() => { + applyLangPack(langPack); + return langPack; }); } diff --git a/src/pages/pageSignIn.ts b/src/pages/pageSignIn.ts index fb5c6b31..93e806c7 100644 --- a/src/pages/pageSignIn.ts +++ b/src/pages/pageSignIn.ts @@ -4,7 +4,7 @@ import Countries, { Country as _Country } from "../countries"; import appStateManager from "../lib/appManagers/appStateManager"; import apiManager from "../lib/mtproto/mtprotoworker"; import { RichTextProcessor } from '../lib/richtextprocessor'; -import { findUpTag } from "../helpers/dom"; +import { findUpTag, attachClickEvent, cancelEvent, replaceContent } from "../helpers/dom"; import Page from "./page"; import pageAuthCode from "./pageAuthCode"; import InputField from "../components/inputField"; @@ -15,6 +15,8 @@ import fastSmoothScroll from "../helpers/fastSmoothScroll"; import { isTouchSupported } from "../helpers/touchSupport"; import App from "../config/app"; import Modes from "../config/modes"; +import I18n, { _i18n, i18n } from "../lib/langPack"; +import { LangPackString } from "../layer"; type Country = _Country & { li?: HTMLLIElement[] @@ -45,7 +47,7 @@ let onFirstMount = () => { inputWrapper.classList.add('input-wrapper'); const countryInputField = new InputField({ - label: 'Country', + label: 'Login.CountrySelectorLabel', name: 'countryCode', plainText: true }); @@ -227,14 +229,13 @@ let onFirstMount = () => { let lastValue = ''; const telInputField = new InputField({ - label: 'Phone Number', + label: 'Login.PhoneLabel', plainText: true, name: 'phone' }); let telEl = telInputField.input as HTMLInputElement; telEl.type = 'tel'; telEl.autocomplete = 'rr55RandomRR55'; - const telLabel = telEl.nextElementSibling as HTMLLabelElement; telEl.addEventListener('input', function(this: typeof telEl, e) { //console.log('input', this.value); this.classList.remove('error'); @@ -247,7 +248,7 @@ let onFirstMount = () => { pasted = false; - telLabel.innerText = 'Phone Number'; + telInputField.setLabel(); let formatted: string, country: Country; if(this.value.replace(/\++/, '+') === '+') { @@ -299,19 +300,19 @@ let onFirstMount = () => { });*/ const signedCheckboxField = new CheckboxField({ - text: 'Keep me signed in', + text: 'Login.KeepSigned', name: 'keepSession', withRipple: true }); signedCheckboxField.input.checked = true; - btnNext = Button('btn-primary btn-color-primary', {text: 'NEXT'}); + btnNext = Button('btn-primary btn-color-primary', {text: 'Login.Next'}); btnNext.style.visibility = 'hidden'; btnNext.addEventListener('click', function(this: HTMLElement, e) { this.setAttribute('disabled', 'true'); - this.textContent = 'PLEASE WAIT...'; + replaceContent(this, i18n('PleaseWait')); putPreloader(this); //this.innerHTML = 'PLEASE WAIT...'; @@ -333,11 +334,12 @@ let onFirstMount = () => { }).catch(err => { this.removeAttribute('disabled'); - this.innerText = 'NEXT'; switch(err.type) { case 'PHONE_NUMBER_INVALID': - telLabel.innerText = 'Phone Number Invalid'; + telInputField.setError(); + replaceContent(telInputField.label, i18n('Login.PhoneLabelInvalid')); telEl.classList.add('error'); + replaceContent(this, i18n('Login.Next')); break; default: console.error('auth.sendCode error:', err); @@ -347,7 +349,7 @@ let onFirstMount = () => { }); }); - const btnQr = Button('btn-primary btn-secondary btn-primary-transparent primary', {text: 'Quick log in using QR code'}); + const btnQr = Button('btn-primary btn-secondary btn-primary-transparent primary', {text: 'Login.QR.Login'}); btnQr.addEventListener('click', () => { import('./pageSignQR').then(module => { @@ -357,7 +359,14 @@ let onFirstMount = () => { inputWrapper.append(countryInputField.container, telInputField.container, signedCheckboxField.label, btnNext, btnQr); - page.pageEl.querySelector('.container').append(inputWrapper); + const h4 = document.createElement('h4'); + _i18n(h4, 'Login.Title'); + + const subtitle = document.createElement('div'); + subtitle.classList.add('subtitle'); + _i18n(subtitle, 'Login.StartText'); + + page.pageEl.querySelector('.container').append(h4, subtitle, inputWrapper); let tryAgain = () => { apiManager.invokeApi('help.getNearestDc').then((nearestDcResult) => { @@ -405,12 +414,52 @@ let onFirstMount = () => { }, 0); } + apiManager.invokeApi('help.getConfig').then(config => { + if(config.suggested_lang_code !== I18n.lastRequestedLangCode) { + //I18n.loadLangPack(config.suggested_lang_code); + + Promise.all([ + I18n.getStrings(config.suggested_lang_code, ['Login.ContinueOnLanguage']), + I18n.getCacheLangPack() + ]).then(res => { + const backup: LangPackString[] = []; + res[0].forEach(string => { + const backupString = I18n.strings.get(string.key); + if(!backupString) { + return; + } + + backup.push(backupString); + I18n.strings.set(string.key, string); + }); + + const btnChangeLanguage = Button('btn-primary btn-secondary btn-primary-transparent primary', {text: 'Login.ContinueOnLanguage'}); + inputWrapper.append(btnChangeLanguage); + + backup.forEach(string => { + I18n.strings.set(string.key, string); + }); + + attachClickEvent(btnChangeLanguage, (e) => { + cancelEvent(e); + + btnChangeLanguage.disabled = true; + putPreloader(btnChangeLanguage); + + I18n.getLangPack(config.suggested_lang_code).then(() => { + btnChangeLanguage.remove(); + }); + }); + }); + } + }); + tryAgain(); }; const page = new Page('page-sign', true, onFirstMount, () => { if(btnNext) { - btnNext.textContent = 'NEXT'; + replaceContent(btnNext, i18n('Login.Next')); btnNext.removeAttribute('disabled'); } diff --git a/src/pages/pageSignQR.ts b/src/pages/pageSignQR.ts index 540b2b2e..e788c976 100644 --- a/src/pages/pageSignQR.ts +++ b/src/pages/pageSignQR.ts @@ -10,6 +10,7 @@ import { bytesCmp, bytesToBase64 } from '../helpers/bytes'; import { pause } from '../helpers/schedulers'; import App from '../config/app'; import Button from '../components/button'; +import { _i18n, i18n } from '../lib/langPack'; let onFirstMount = async() => { const pageElement = page.pageEl; @@ -18,9 +19,23 @@ let onFirstMount = async() => { const inputWrapper = document.createElement('div'); inputWrapper.classList.add('input-wrapper'); - const btnBack = Button('btn-primary btn-secondary btn-primary-transparent primary', {text: 'Or log in using your phone number'}); + const btnBack = Button('btn-primary btn-secondary btn-primary-transparent primary', {text: 'Login.QR.Cancel'}); inputWrapper.append(btnBack); - imageDiv.parentElement.append(inputWrapper); + + const container = imageDiv.parentElement; + + const h4 = document.createElement('h4'); + _i18n(h4, 'Login.QR.Title'); + + const helpList = document.createElement('ol'); + helpList.classList.add('qr-description'); + ['Login.QR.Help1', 'Login.QR.Help2', 'Login.QR.Help3'].forEach((key) => { + const li = document.createElement('li'); + li.append(i18n(key)); + helpList.append(li); + }); + + container.append(h4, helpList, inputWrapper); btnBack.addEventListener('click', () => { pageSignIn.mount(); @@ -86,8 +101,8 @@ let onFirstMount = async() => { let url = "tg://login?token=" + encoded.replace(/\+/g, "-").replace(/\//g, "_").replace(/\=+$/, ""); const qrCode = new QRCodeStyling({ - width: 166 * window.devicePixelRatio, - height: 166 * window.devicePixelRatio, + width: 240 * window.devicePixelRatio, + height: 240 * window.devicePixelRatio, data: url, image: "assets/img/logo_padded.svg", dotsOptions: { @@ -108,8 +123,22 @@ let onFirstMount = async() => { qrCode.append(imageDiv); (imageDiv.lastChild as HTMLCanvasElement).classList.add('qr-canvas'); + let promise: Promise; + if(qrCode._drawingPromise) { + promise = qrCode._drawingPromise; + } else { + promise = Promise.race([ + pause(1000), + new Promise((resolve) => { + qrCode._canvas._image.addEventListener('load', () => { + window.requestAnimationFrame(() => resolve()); + }, {once: true}); + }) + ]); + } + // * это костыль, но библиотека не предоставляет никаких событий - qrCode._drawingPromise.then(() => { + promise.then(() => { Array.from(imageDiv.children).slice(0, -1).forEach(el => { el.remove(); }); diff --git a/src/scss/partials/pages/_pages.scss b/src/scss/partials/pages/_pages.scss index 4071a2ae..047a0d7e 100644 --- a/src/scss/partials/pages/_pages.scss +++ b/src/scss/partials/pages/_pages.scss @@ -3,6 +3,8 @@ overflow: hidden; .btn-primary { + text-transform: uppercase; + @include respond-to(handhelds) { height: 50px; } @@ -89,7 +91,7 @@ flex-direction: column; @media screen and (max-height: 810px) { - height: 700px; + height: 760px; } } } @@ -156,22 +158,13 @@ .qr { margin-top: 1.5rem; } - - p.qr-description { - color: #707579; - line-height: 1.85; - text-align: left; - margin-left: auto; - margin-right: auto; - } } .page-signQR { .auth-image { - @include respond-to(handhelds) { - width: 166px; - height: 166px; - } + width: 240px !important; + height: 240px !important; + overflow: hidden; .qr-canvas { width: 100%; @@ -186,6 +179,17 @@ h4 { flex: 0 0 auto; } + + .qr-description { + max-width: 480px; + margin: 1rem auto; + line-height: 1.3125; + text-align: left; + + li { + margin-top: .5rem; + } + } } /* .page-signQR {