From f60d0f82ceca8d188348e74fc88d7f4cd16fd4a4 Mon Sep 17 00:00:00 2001 From: morethanwords Date: Wed, 6 Jan 2021 13:16:53 +0400 Subject: [PATCH] Privacy alpha version --- src/components/chat/bubbles.ts | 2 +- src/components/radioForm.ts | 15 +++ src/components/row.ts | 83 ++++++++++++++++ src/components/sidebarLeft/index.ts | 62 ++++++++++-- src/components/sidebarLeft/tabs/addMembers.ts | 95 +++++++++++------- .../sidebarLeft/tabs/chatFolders.ts | 11 +-- src/components/sidebarLeft/tabs/editFolder.ts | 11 +-- .../sidebarLeft/tabs/editProfile.ts | 2 +- .../sidebarLeft/tabs/generalSettings.ts | 93 ++---------------- src/components/sidebarLeft/tabs/newChannel.ts | 24 ++--- src/components/sidebarLeft/tabs/newGroup.ts | 64 ++++++------ .../sidebarLeft/tabs/privacyAndSecurity.ts | 98 +++++++++++++++++++ src/components/sidebarLeft/tabs/settings.ts | 6 +- src/components/slider.ts | 24 ++++- src/helpers/dom.ts | 4 +- src/lib/appManagers/appDialogsManager.ts | 4 +- src/lib/appManagers/appPrivacyManager.ts | 66 +++++++++++++ src/scss/partials/_leftSidebar.scss | 13 ++- src/scss/style.scss | 33 ++++++- 19 files changed, 498 insertions(+), 212 deletions(-) create mode 100644 src/components/radioForm.ts create mode 100644 src/components/row.ts create mode 100644 src/components/sidebarLeft/tabs/privacyAndSecurity.ts create mode 100644 src/lib/appManagers/appPrivacyManager.ts diff --git a/src/components/chat/bubbles.ts b/src/components/chat/bubbles.ts index 641401ae..48b75d53 100644 --- a/src/components/chat/bubbles.ts +++ b/src/components/chat/bubbles.ts @@ -970,7 +970,7 @@ export default class ChatBubbles { }); //if(scrolledDown) this.scrollable.scrollTop = this.scrollable.scrollHeight; - if(this.messagesQueuePromise && scrolledDown) { + if(this.messagesQueuePromise && scrolledDown/* && false */) { if(this.scrollable.isScrolledDown && !this.scrollable.scrollLocked) { //this.log('renderNewMessagesByIDs: messagesQueuePromise before will set prev max'); this.scrollable.scrollTo(this.scrollable.scrollHeight - 1, 'top', false, true); diff --git a/src/components/radioForm.ts b/src/components/radioForm.ts new file mode 100644 index 00000000..3f9f2e0d --- /dev/null +++ b/src/components/radioForm.ts @@ -0,0 +1,15 @@ +export default function RadioForm(radios: {container: HTMLElement, input: HTMLInputElement}[], onChange: (value: string) => void) { + const form = document.createElement('form'); + + radios.forEach(r => { + const {container, input} = r; + form.append(container); + input.addEventListener('change', () => { + if(input.checked) { + onChange(input.value); + } + }); + }); + + return form; +} \ No newline at end of file diff --git a/src/components/row.ts b/src/components/row.ts new file mode 100644 index 00000000..100b9f5a --- /dev/null +++ b/src/components/row.ts @@ -0,0 +1,83 @@ +import CheckboxField from "./checkbox"; +import RadioField from "./radioField"; +import { ripple } from "./ripple"; +import { SliderSuperTab } from "./slider"; +import RadioForm from "./radioForm"; + +export default class Row { + public container: HTMLElement; + public title: HTMLDivElement; + public subtitle: HTMLElement; + + public checkboxField: ReturnType; + public radioField: ReturnType; + + constructor(options: Partial<{ + icon: string, + subtitle: string, + radioField: Row['radioField'], + checkboxField: Row['checkboxField'], + title: string, + clickable: boolean, + navigationTab: SliderSuperTab + }> = {}) { + this.container = document.createElement('div'); + this.container.classList.add('row'); + + this.subtitle = document.createElement('div'); + this.subtitle.classList.add('row-subtitle'); + if(options.subtitle) { + this.subtitle.innerHTML = options.subtitle; + } + + let havePadding = false; + if(options.radioField || options.checkboxField) { + havePadding = true; + if(options.radioField) { + this.radioField = options.radioField; + this.container.append(this.radioField.label); + } + + if(options.checkboxField) { + this.checkboxField = options.checkboxField; + this.container.append(this.checkboxField.label); + } + } else { + if(options.title) { + this.title = document.createElement('div'); + this.title.classList.add('row-title'); + this.title.innerHTML = options.title; + this.container.append(this.title); + } + + if(options.icon) { + havePadding = true; + this.title.classList.add('tgico', 'tgico-' + options.icon); + } + } + + if(havePadding) { + this.container.classList.add('row-with-padding'); + } + + if(options.navigationTab) { + this.container.addEventListener('click', () => { + options.navigationTab.open(); + }); + options.clickable = true; + } + + if(options.clickable) { + this.container.classList.add('row-clickable', 'hover-effect'); + ripple(this.container); + } + + this.container.append(this.subtitle); + } + + +} + +export const RadioFormFromRows = (rows: Row[], onChange: (value: string) => void) => { + return RadioForm(rows.map(r => ({container: r.container, input: r.radioField.input})), onChange); +}; \ No newline at end of file diff --git a/src/components/sidebarLeft/index.ts b/src/components/sidebarLeft/index.ts index d7a0f71d..1d7a805e 100644 --- a/src/components/sidebarLeft/index.ts +++ b/src/components/sidebarLeft/index.ts @@ -29,16 +29,15 @@ import apiManagerProxy from "../../lib/mtproto/mtprotoworker"; import AppSearchSuper from "../appSearchSuper."; import { DateData, fillTipDates } from "../../helpers/date"; import AppGeneralSettingsTab from "./tabs/generalSettings"; +import AppPrivacyAndSecurityTab from "./tabs/privacyAndSecurity"; -const addMembersTab = new AppAddMembersTab(); const contactsTab = new AppContactsTab(); const archivedTab = new AppArchivedTab(); export class AppSidebarLeft extends SidebarSlider { public static SLIDERITEMSIDS = { archived: 1, - contacts: 2, - addMembers: 3 + contacts: 2 }; private toolsBtn: HTMLButtonElement; @@ -75,6 +74,7 @@ export class AppSidebarLeft extends SidebarSlider { public editFolderTab: AppEditFolderTab; public includedChatsTab: AppIncludedChatsTab; public generalSettingsTab: AppGeneralSettingsTab; + public privacyAndSecurityTab: AppPrivacyAndSecurityTab; //private log = logger('SL'); @@ -86,8 +86,7 @@ export class AppSidebarLeft extends SidebarSlider { Object.assign(this.tabs, { [AppSidebarLeft.SLIDERITEMSIDS.archived]: archivedTab, - [AppSidebarLeft.SLIDERITEMSIDS.contacts]: contactsTab, - [AppSidebarLeft.SLIDERITEMSIDS.addMembers]: addMembersTab + [AppSidebarLeft.SLIDERITEMSIDS.contacts]: contactsTab }); //this._selectTab(0); // make first tab as default @@ -101,7 +100,6 @@ export class AppSidebarLeft extends SidebarSlider { this.archivedTab = archivedTab; this.newChannelTab = new AppNewChannelTab(this); - this.addMembersTab = addMembersTab; this.contactsTab = contactsTab; this.newGroupTab = new AppNewGroupTab(this); this.settingsTab = new AppSettingsTab(this); @@ -110,6 +108,8 @@ export class AppSidebarLeft extends SidebarSlider { this.includedChatsTab = new AppIncludedChatsTab(this); this.editProfileTab = new AppEditProfileTab(this); this.generalSettingsTab = new AppGeneralSettingsTab(this); + this.privacyAndSecurityTab = new AppPrivacyAndSecurityTab(this); + this.addMembersTab = new AppAddMembersTab(this); this.menuEl = this.toolsBtn.querySelector('.btn-menu'); this.newBtnMenu = this.sidebarEl.querySelector('#new-menu'); @@ -146,8 +146,15 @@ export class AppSidebarLeft extends SidebarSlider { [this.newButtons.group, this.buttons.newGroup].forEach(btn => { attachClickEvent(btn, (e) => { - this.addMembersTab.init(0, 'chat', false, (peerIds) => { - this.newGroupTab.init(peerIds); + this.addMembersTab.open({ + peerId: 0, + type: 'chat', + skippable: false, + takeOut: (peerIds) => { + this.newGroupTab.open(peerIds); + }, + title: 'Add Members', + placeholder: 'Add People...' }); }); }); @@ -429,6 +436,45 @@ export class AppSidebarLeft extends SidebarSlider { } } +export class SettingSection { + public container: HTMLElement; + public content: HTMLElement; + public title: HTMLElement; + public caption: HTMLElement; + + constructor(name: string, caption?: string) { + this.container = document.createElement('div'); + this.container.classList.add('sidebar-left-section'); + + const hr = document.createElement('hr'); + + this.content = document.createElement('div'); + this.content.classList.add('sidebar-left-section-content'); + + if(name) { + this.title = document.createElement('div'); + this.title.classList.add('sidebar-left-h2', 'sidebar-left-section-name'); + this.title.innerHTML = name; + this.content.append(this.title); + } + + this.container.append(hr, this.content); + + if(caption) { + this.caption = document.createElement('div'); + this.caption.classList.add('sidebar-left-section-caption'); + this.caption.innerHTML = caption; + this.container.append(this.caption); + } + } +} + +export const generateSection = (appendTo: Scrollable, name: string, caption?: string) => { + const section = new SettingSection(name, caption); + appendTo.append(section.container); + return section.content; +}; + const appSidebarLeft = new AppSidebarLeft(); MOUNT_CLASS_TO && (MOUNT_CLASS_TO.appSidebarLeft = appSidebarLeft); export default appSidebarLeft; diff --git a/src/components/sidebarLeft/tabs/addMembers.ts b/src/components/sidebarLeft/tabs/addMembers.ts index 83860dc5..1f36f6dc 100644 --- a/src/components/sidebarLeft/tabs/addMembers.ts +++ b/src/components/sidebarLeft/tabs/addMembers.ts @@ -1,42 +1,44 @@ -import { SliderTab } from "../../slider"; +import SidebarSlider, { SliderSuperTab } from "../../slider"; import AppSelectPeers from "../../appSelectPeers"; import { putPreloader } from "../../misc"; -import appChatsManager from "../../../lib/appManagers/appChatsManager"; -import appSidebarLeft, { AppSidebarLeft } from ".."; +import Button from "../../button"; -export default class AppAddMembersTab implements SliderTab { - private container = document.querySelector('.addmembers-container') as HTMLDivElement; - private contentDiv = this.container.querySelector('.sidebar-content') as HTMLDivElement; - private backBtn = this.container.querySelector('.sidebar-close-button') as HTMLButtonElement; - private nextBtn = this.contentDiv.querySelector('.btn-corner') as HTMLButtonElement; +export default class AppAddMembersTab extends SliderSuperTab { + private nextBtn: HTMLButtonElement; private selector: AppSelectPeers; - private peerType: 'channel' | 'chat'; - private peerId: number; // always positive - private takeOut: (peerIds: number[]) => void + private peerType: 'channel' | 'chat' | 'privacy'; + private takeOut: (peerIds: number[]) => Promise | any; private skippable: boolean; - constructor() { - this.nextBtn.addEventListener('click', () => { - if(this.skippable) { - appSidebarLeft.closeTab(AppSidebarLeft.SLIDERITEMSIDS.addMembers); - return; - } + constructor(slider: SidebarSlider) { + super(slider); + } + protected init() { + this.nextBtn = Button('btn-corner btn-circle', {icon: 'arrow-next'}); + this.content.append(this.nextBtn); + + this.nextBtn.addEventListener('click', () => { const peerIds = this.selector.getSelected(); - if(peerIds.length) { - if(this.takeOut) { - this.takeOut(peerIds); - return; - } - this.nextBtn.classList.remove('tgico-arrow-next'); - this.nextBtn.disabled = true; - putPreloader(this.nextBtn); - this.selector.freezed = true; + if(this.skippable) { + this.takeOut(peerIds); + this.close(); + } else { + const promise = this.takeOut(peerIds); - appChatsManager.inviteToChannel(this.peerId, peerIds).then(() => { - appSidebarLeft.closeTab(AppSidebarLeft.SLIDERITEMSIDS.addMembers); - }); + if(promise instanceof Promise) { + this.nextBtn.classList.remove('tgico-arrow-next'); + this.nextBtn.disabled = true; + putPreloader(this.nextBtn); + this.selector.freezed = true; + + promise.then(() => { + this.close(); + }); + } else { + this.close(); + } } }); } @@ -48,22 +50,39 @@ export default class AppAddMembersTab implements SliderTab { } } - public init(id: number, type: 'channel' | 'chat', skippable: boolean, takeOut?: AppAddMembersTab['takeOut']) { - this.peerId = Math.abs(id); - this.peerType = type; - this.takeOut = takeOut; - this.skippable = skippable; + public open(options: { + title: string, + placeholder: string, + peerId?: number, + type: AppAddMembersTab['peerType'], + takeOut?: AppAddMembersTab['takeOut'], + skippable: boolean, + selectedPeerIds?: number[] + }) { + const ret = super.open(); + + this.title.innerHTML = options.title; + this.peerType = options.type; + this.takeOut = options.takeOut; + this.skippable = options.skippable; this.onCloseAfterTimeout(); - this.selector = new AppSelectPeers(this.contentDiv, skippable ? null : (length) => { + this.selector = new AppSelectPeers(this.content, this.skippable ? null : (length) => { this.nextBtn.classList.toggle('is-visible', !!length); }, ['contacts']); + this.selector.input.placeholder = options.placeholder; + + if(options.selectedPeerIds) { + options.selectedPeerIds.forEach(peerId => { + this.selector.add(peerId); + }); + } + this.nextBtn.classList.add('tgico-arrow-next'); this.nextBtn.innerHTML = ''; this.nextBtn.disabled = false; - this.nextBtn.classList.add('tgico-arrow-next'); - this.nextBtn.classList.toggle('is-visible', skippable); + this.nextBtn.classList.toggle('is-visible', this.skippable); - appSidebarLeft.selectTab(AppSidebarLeft.SLIDERITEMSIDS.addMembers); + return ret; } } \ No newline at end of file diff --git a/src/components/sidebarLeft/tabs/chatFolders.ts b/src/components/sidebarLeft/tabs/chatFolders.ts index 5e9d0bef..4a1be446 100644 --- a/src/components/sidebarLeft/tabs/chatFolders.ts +++ b/src/components/sidebarLeft/tabs/chatFolders.ts @@ -96,7 +96,7 @@ export default class AppChatFoldersTab extends SliderSuperTab { return div; } - init() { + protected init() { this.container.classList.add('chat-folders-container'); this.title.innerText = 'Chat Folders'; @@ -232,13 +232,8 @@ export default class AppChatFoldersTab extends SliderSuperTab { } onOpen() { - if(this.init) { - this.init(); - this.init = null; - } else { - if(this.animation) { - this.animation.restart(); - } + if(this.animation) { + this.animation.restart(); } } } diff --git a/src/components/sidebarLeft/tabs/editFolder.ts b/src/components/sidebarLeft/tabs/editFolder.ts index 4f2736c3..b9ccbcd1 100644 --- a/src/components/sidebarLeft/tabs/editFolder.ts +++ b/src/components/sidebarLeft/tabs/editFolder.ts @@ -41,7 +41,7 @@ export default class AppEditFolderTab extends SliderSuperTab { super(appSidebarLeft); } - init() { + protected init() { this.container.classList.add('edit-folder-container'); this.caption = document.createElement('div'); this.caption.classList.add('caption'); @@ -227,13 +227,8 @@ export default class AppEditFolderTab extends SliderSuperTab { } onOpen() { - if(this.init) { - this.init(); - this.init = null; - } else { - if(this.animation) { - this.animation.restart(); - } + if(this.animation) { + this.animation.restart(); } } diff --git a/src/components/sidebarLeft/tabs/editProfile.ts b/src/components/sidebarLeft/tabs/editProfile.ts index 95e5ce3f..a4f4be65 100644 --- a/src/components/sidebarLeft/tabs/editProfile.ts +++ b/src/components/sidebarLeft/tabs/editProfile.ts @@ -44,7 +44,7 @@ export default class AppEditProfileTab extends SliderSuperTab { super(slider); } - public init() { + protected init() { this.container.classList.add('edit-profile-container'); this.title.innerText = 'Edit Profile'; //this.scrollWrapper = this.container.querySelector('.scroll-wrapper'); diff --git a/src/components/sidebarLeft/tabs/generalSettings.ts b/src/components/sidebarLeft/tabs/generalSettings.ts index a60bf69d..525f7e39 100644 --- a/src/components/sidebarLeft/tabs/generalSettings.ts +++ b/src/components/sidebarLeft/tabs/generalSettings.ts @@ -1,5 +1,5 @@ import { SliderSuperTab } from "../../slider" -import { AppSidebarLeft } from ".."; +import { AppSidebarLeft, generateSection } from ".."; import RangeSelector from "../../rangeSelector"; import { clamp } from "../../../helpers/number"; import Button from "../../button"; @@ -8,6 +8,7 @@ import RadioField from "../../radioField"; import appStateManager from "../../../lib/appManagers/appStateManager"; import rootScope from "../../../lib/rootScope"; import { isApple } from "../../../helpers/userAgent"; +import Row from "../../row"; export class RangeSettingSelector { public container: HTMLDivElement; @@ -59,28 +60,10 @@ export default class AppGeneralSettingsTab extends SliderSuperTab { this.container.classList.add('general-settings-container'); this.title.innerText = 'General'; - const generateSection = (name: string) => { - const container = document.createElement('div'); - container.classList.add('sidebar-left-section'); - - const hr = document.createElement('hr'); - const h2 = document.createElement('div'); - h2.classList.add('sidebar-left-h2', 'sidebar-left-section-name'); - h2.innerHTML = name; - - const content = document.createElement('div'); - content.classList.add('sidebar-left-section-content'); - content.append(h2); - - container.append(hr, content); - - this.scrollable.append(container); - - return content; - }; + const section = generateSection.bind(null, this.scrollable); { - const container = generateSection('Settings'); + const container = section('Settings'); const range = new RangeSettingSelector('Message Text Size', 1, rootScope.settings.messagesTextSize, 12, 20); range.onChange = (value) => { @@ -95,67 +78,7 @@ export default class AppGeneralSettingsTab extends SliderSuperTab { } { - const container = generateSection('Keyboard'); - - class Row { - public container: HTMLElement; - public title: HTMLDivElement; - public subtitle: HTMLElement; - - public checkboxField: ReturnType; - public radioField: ReturnType; - - constructor(options: Partial<{ - icon: string, - subtitle: string, - radioField: Row['radioField'], - checkboxField: Row['checkboxField'], - title: string, - }> = {}) { - this.container = document.createElement('div'); - this.container.classList.add('row'); - - this.subtitle = document.createElement('div'); - this.subtitle.classList.add('row-subtitle'); - if(options.subtitle) { - this.subtitle.innerHTML = options.subtitle; - } - - let havePadding = false; - if(options.radioField || options.checkboxField) { - havePadding = true; - if(options.radioField) { - this.radioField = options.radioField; - this.container.append(this.radioField.label); - } - - if(options.checkboxField) { - this.checkboxField = options.checkboxField; - this.container.append(this.checkboxField.label); - } - } else { - if(options.icon) { - havePadding = true; - this.container.classList.add('tgico-', options.icon); - } - - if(options.title) { - this.title = document.createElement('div'); - this.title.classList.add('row-title'); - this.title.innerHTML = options.title; - this.container.append(this.title); - } - } - - if(havePadding) { - this.container.classList.add('row-with-padding'); - } - - this.container.append(this.subtitle); - } - - - } + const container = section('Keyboard'); const form = document.createElement('form'); @@ -174,7 +97,7 @@ export default class AppGeneralSettingsTab extends SliderSuperTab { } { - const container = generateSection('Auto-Download Media'); + const container = section('Auto-Download Media'); const contactsCheckboxField = CheckboxField('Contacts', 'contacts', false, 'settings.autoDownload.contacts'); const privateCheckboxField = CheckboxField('Private Chats', 'private', false, 'settings.autoDownload.private'); @@ -185,7 +108,7 @@ export default class AppGeneralSettingsTab extends SliderSuperTab { } { - const container = generateSection('Auto-Play Media'); + const container = section('Auto-Play Media'); const gifsCheckboxField = CheckboxField('GIFs', 'gifs', false, 'settings.autoPlay.gifs'); const videosCheckboxField = CheckboxField('Videos', 'videos', false, 'settings.autoPlay.videos'); @@ -194,7 +117,7 @@ export default class AppGeneralSettingsTab extends SliderSuperTab { } { - const container = generateSection('Stickers'); + const container = section('Stickers'); const suggestCheckboxField = CheckboxField('Suggest Stickers by Emoji', 'suggest', false, 'settings.stickers.suggest'); const loopCheckboxField = CheckboxField('Loop Animated Stickers', 'loop', false, 'settings.stickers.loop'); diff --git a/src/components/sidebarLeft/tabs/newChannel.ts b/src/components/sidebarLeft/tabs/newChannel.ts index 45250469..eeedcc34 100644 --- a/src/components/sidebarLeft/tabs/newChannel.ts +++ b/src/components/sidebarLeft/tabs/newChannel.ts @@ -3,12 +3,10 @@ import { InputFile } from "../../../layer"; import appChatsManager from "../../../lib/appManagers/appChatsManager"; import Button from "../../button"; import InputField from "../../inputField"; -import PopupAvatar from "../../popups/avatar"; -import { SliderTab, SliderSuperTab } from "../../slider"; +import { SliderSuperTab } from "../../slider"; import AvatarEdit from "../../avatarEdit"; export default class AppNewChannelTab extends SliderSuperTab { - private canvas = this.container.querySelector('.avatar-edit-canvas') as HTMLCanvasElement; private uploadAvatar: () => Promise = null; private channelNameInputField: InputField; @@ -20,7 +18,7 @@ export default class AppNewChannelTab extends SliderSuperTab { super(appSidebarLeft); } - private init() { + protected init() { this.container.classList.add('new-channel-container'); this.title.innerText = 'New Channel'; @@ -71,7 +69,16 @@ export default class AppNewChannelTab extends SliderSuperTab { } appSidebarLeft.removeTabFromHistory(this.id); - appSidebarLeft.addMembersTab.init(channelId, 'channel', true); + appSidebarLeft.addMembersTab.open({ + peerId: channelId, + type: 'channel', + skippable: true, + title: 'Add Members', + placeholder: 'Add People...', + takeOut: (peerIds) => { + return appChatsManager.inviteToChannel(Math.abs(channelId), peerIds); + } + }); }); }); @@ -86,11 +93,4 @@ export default class AppNewChannelTab extends SliderSuperTab { this.channelDescriptionInputField.value = ''; this.nextBtn.disabled = false; } - - onOpen() { - if(this.init) { - this.init(); - this.init = null; - } - } } \ No newline at end of file diff --git a/src/components/sidebarLeft/tabs/newGroup.ts b/src/components/sidebarLeft/tabs/newGroup.ts index ecd6555b..66ca3dc1 100644 --- a/src/components/sidebarLeft/tabs/newGroup.ts +++ b/src/components/sidebarLeft/tabs/newGroup.ts @@ -6,13 +6,10 @@ import appUsersManager from "../../../lib/appManagers/appUsersManager"; import { SearchGroup } from "../../appSearch"; import Button from "../../button"; import InputField from "../../inputField"; -import PopupAvatar from "../../popups/avatar"; -import Scrollable from "../../scrollable"; import { SliderSuperTab } from "../../slider"; import AvatarEdit from "../../avatarEdit"; export default class AppNewGroupTab extends SliderSuperTab { - private canvas = this.container.querySelector('.avatar-edit-canvas') as HTMLCanvasElement; private searchGroup = new SearchGroup(' ', 'contacts', true, 'new-group-members disable-hover', false); private avatarEdit: AvatarEdit; private uploadAvatar: () => Promise = null; @@ -24,7 +21,7 @@ export default class AppNewGroupTab extends SliderSuperTab { super(appSidebarLeft); } - private construct() { + protected init() { this.container.classList.add('new-group-container'); this.title.innerText = 'New Group'; @@ -73,10 +70,6 @@ export default class AppNewGroupTab extends SliderSuperTab { this.scrollable.append(this.avatarEdit.container, inputWrapper, chatsContainer); } - public onClose() { - - } - public onCloseAfterTimeout() { this.searchGroup.clear(); this.avatarEdit.clear(); @@ -85,36 +78,35 @@ export default class AppNewGroupTab extends SliderSuperTab { this.nextBtn.disabled = false; } - public init(userIds: number[]) { - if(this.construct) { - this.construct(); - this.construct = null; - } - - this.userIds = userIds; - - this.open(); - this.userIds.forEach(userId => { - let {dom} = appDialogsManager.addDialogNew({ - dialog: userId, - container: this.searchGroup.list, - drawStatus: false, - rippleEnabled: false, - avatarSize: 48 - }); + public open(userIds: number[]) { + const result = super.open(); + result.then(() => { + this.userIds = userIds; + + this.userIds.forEach(userId => { + let {dom} = appDialogsManager.addDialogNew({ + dialog: userId, + container: this.searchGroup.list, + drawStatus: false, + rippleEnabled: false, + avatarSize: 48 + }); + + let subtitle = ''; + subtitle = appUsersManager.getUserStatusString(userId); + if(subtitle == 'online') { + subtitle = `${subtitle}`; + } - let subtitle = ''; - subtitle = appUsersManager.getUserStatusString(userId); - if(subtitle == 'online') { - subtitle = `${subtitle}`; - } + if(subtitle) { + dom.lastMessageSpan.innerHTML = subtitle; + } + }); - if(subtitle) { - dom.lastMessageSpan.innerHTML = subtitle; - } + this.searchGroup.nameEl.innerText = this.userIds.length + ' members'; + this.searchGroup.setActive(); }); - - this.searchGroup.nameEl.innerText = this.userIds.length + ' members'; - this.searchGroup.setActive(); + + return result; } } \ No newline at end of file diff --git a/src/components/sidebarLeft/tabs/privacyAndSecurity.ts b/src/components/sidebarLeft/tabs/privacyAndSecurity.ts new file mode 100644 index 00000000..8d3cd704 --- /dev/null +++ b/src/components/sidebarLeft/tabs/privacyAndSecurity.ts @@ -0,0 +1,98 @@ +import SidebarSlider, { SliderSuperTab } from "../../slider"; +import { generateSection } from ".."; +import Row from "../../row"; +import { InputPrivacyKey, PrivacyRule } from "../../../layer"; +import appPrivacyManager from "../../../lib/appManagers/appPrivacyManager"; +import AppPrivacyPhoneNumberTab from "./privacy/phoneNumber"; + +export default class AppPrivacyAndSecurityTab extends SliderSuperTab { + constructor(slider: SidebarSlider) { + super(slider); + } + + protected init() { + this.container.classList.add('privacy-container'); + this.title.innerText = 'Privacy and Security'; + + const section = generateSection.bind(null, this.scrollable); + + { + const container = section(''); + + const blockedUsersRow = new Row({ + icon: 'deleteuser', + title: 'Blocked Users', + subtitle: '6 users', + clickable: true + }); + + const twoFactorRow = new Row({ + icon: 'lock', + title: 'Two-Step Verification', + subtitle: 'Off', + clickable: true + }); + + const activeSessionRow = new Row({ + icon: 'activesessions', + title: 'Active Sessions', + subtitle: '3 devices', + clickable: true + }); + + container.append(blockedUsersRow.container, twoFactorRow.container, activeSessionRow.container); + } + + { + const container = section('Privacy'); + + const rowsByKeys: Partial<{ + [key in InputPrivacyKey['_']]: Row + }> = {}; + + const numberVisibilityRow = rowsByKeys['inputPrivacyKeyPhoneNumber'] = new Row({ + title: 'Who can see my phone number?', + subtitle: 'My Contacts', + navigationTab: new AppPrivacyPhoneNumberTab(this.slider) + }); + + const lastSeenTimeRow = rowsByKeys['inputPrivacyKeyStatusTimestamp'] = new Row({ + title: 'Who can see your Last Seen time?', + subtitle: 'Everybody', + clickable: true + }); + + const photoVisibilityRow = rowsByKeys['inputPrivacyKeyProfilePhoto'] = new Row({ + title: 'Who can see my profile photo?', + subtitle: 'Everybody', + clickable: true + }); + + const linkAccountRow = rowsByKeys['inputPrivacyKeyForwards'] = new Row({ + title: 'Who can add a link to my account when forwarding my messages?', + subtitle: 'Everybody', + clickable: true + }); + + const groupChatsAddRow = rowsByKeys['inputPrivacyKeyChatInvite'] = new Row({ + title: 'Who can add me to group chats?', + subtitle: 'Everybody', + clickable: true + }); + + for(const key in rowsByKeys) { + const row = rowsByKeys[key as keyof typeof rowsByKeys]; + appPrivacyManager.getPrivacy(key as keyof typeof rowsByKeys).then(rules => { + const details = appPrivacyManager.getPrivacyRulesDetails(rules); + const type = details.type === 2 ? 'Everybody' : (details.type === 1 ? 'My Contacts' : 'Nobody'); + const disallowLength = details.disallowLengths.users + details.disallowLengths.chats; + const allowLength = details.allowLengths.users + details.allowLengths.chats; + const str = type + (disallowLength || allowLength ? ` (${[-disallowLength, allowLength ? '+' + allowLength : 0].filter(Boolean).join(', ')})` : ''); + row.subtitle.innerHTML = str; + }); + } + + container.append(numberVisibilityRow.container, lastSeenTimeRow.container, photoVisibilityRow.container, linkAccountRow.container, groupChatsAddRow.container); + } + } +} \ No newline at end of file diff --git a/src/components/sidebarLeft/tabs/settings.ts b/src/components/sidebarLeft/tabs/settings.ts index a478ade9..181983fc 100644 --- a/src/components/sidebarLeft/tabs/settings.ts +++ b/src/components/sidebarLeft/tabs/settings.ts @@ -56,7 +56,7 @@ export default class AppSettingsTab extends SliderSuperTab { buttonsDiv.append(this.buttons.folders = Button(className, {icon: 'folder', rippleSquare: true, text: 'Chat Folders'})); buttonsDiv.append(this.buttons.general = Button(className, {icon: 'settings', rippleSquare: true, text: 'General Settings'})); buttonsDiv.append(this.buttons.notifications = Button(className + ' btn-disabled', {icon: 'unmute', rippleSquare: true, text: 'Notifications'})); - buttonsDiv.append(this.buttons.privacy = Button(className + ' btn-disabled', {icon: 'lock', rippleSquare: true, text: 'Privacy and Security'})); + buttonsDiv.append(this.buttons.privacy = Button(className, {icon: 'lock', rippleSquare: true, text: 'Privacy and Security'})); buttonsDiv.append(this.buttons.language = Button(className + ' btn-disabled', {icon: 'language', rippleSquare: true, text: 'Language'})); this.scrollable.append(this.avatarElem, this.nameDiv, this.phoneDiv, buttonsDiv); @@ -78,6 +78,10 @@ export default class AppSettingsTab extends SliderSuperTab { this.buttons.general.addEventListener('click', () => { appSidebarLeft.generalSettingsTab.open(); }); + + this.buttons.privacy.addEventListener('click', () => { + appSidebarLeft.privacyAndSecurityTab.open(); + }); } public fillElements() { diff --git a/src/components/slider.ts b/src/components/slider.ts index 05b3f183..c2508a9d 100644 --- a/src/components/slider.ts +++ b/src/components/slider.ts @@ -2,7 +2,6 @@ import { attachClickEvent } from "../helpers/dom"; import { horizontalMenu } from "./horizontalMenu"; import ButtonIcon from "./buttonIcon"; import Scrollable from "./scrollable"; -import { p } from "../mock/srp"; export interface SliderTab { onOpen?: () => void, @@ -23,7 +22,7 @@ export class SliderSuperTab implements SliderTab { public id: number; - constructor(protected slider: SidebarSlider) { + constructor(protected slider: SidebarSlider, protected destroyable = false) { this.container = document.createElement('div'); this.container.classList.add('sidebar-slider-item'); @@ -51,20 +50,37 @@ export class SliderSuperTab implements SliderTab { return this.slider.closeTab(this.id); } - public open() { + public async open(...args: any[]) { + if(this.init) { + const result = this.init(); + this.init = null; + await (result instanceof Promise ? result : Promise.resolve()); + } + return this.slider.selectTab(this); } + protected init(): Promise | any { + + } + // * fix incompability public onOpen() { } + + /* public onCloseAfterTimeout() { + if(this.destroyable) { // ! WARNING, пока что это будет работать только с самой последней внутренней вкладкой ! + delete this.slider.tabs[this.id]; + this.container.remove(); + } + } */ } const TRANSITION_TIME = 250; export default class SidebarSlider { - protected _selectTab: (id: number) => void; + protected _selectTab: ReturnType; public historyTabIds: number[] = []; public tabsContainer: HTMLElement; diff --git a/src/helpers/dom.ts b/src/helpers/dom.ts index 942bc4ed..806ec69a 100644 --- a/src/helpers/dom.ts +++ b/src/helpers/dom.ts @@ -674,7 +674,7 @@ export async function getFilesFromEvent(e: ClipboardEvent | DragEvent, onlyTypes return files; } -export function radiosHandleChange(inputs: HTMLInputElement[], onChange: (value: string) => void) { +/* export function radiosHandleChange(inputs: HTMLInputElement[], onChange: (value: string) => void) { inputs.forEach(input => { input.addEventListener('change', () => { if(input.checked) { @@ -682,7 +682,7 @@ export function radiosHandleChange(inputs: HTMLInputElement[], onChange: (value: } }); }); -} +} */ export function isSendShortcutPressed(e: KeyboardEvent) { if(e.key == 'Enter' && !isTouchSupported) { diff --git a/src/lib/appManagers/appDialogsManager.ts b/src/lib/appManagers/appDialogsManager.ts index 15a9a498..8fd13eac 100644 --- a/src/lib/appManagers/appDialogsManager.ts +++ b/src/lib/appManagers/appDialogsManager.ts @@ -8,7 +8,7 @@ import Scrollable, { ScrollableX, SliceSides } from "../../components/scrollable import appSidebarLeft from "../../components/sidebarLeft"; import { formatDateAccordingToToday } from "../../helpers/date"; import { escapeRegExp } from "../../helpers/string"; -import { isApple } from "../../helpers/userAgent"; +import { isSafari } from "../../helpers/userAgent"; import { logger, LogLevels } from "../logger"; import { RichTextProcessor } from "../richtextprocessor"; import rootScope from "../rootScope"; @@ -745,7 +745,7 @@ export class AppDialogsManager { const saveLength = 10; - const sliceFromStart = isApple ? [] : children.slice(0, Math.max(0, firstIndex - saveLength)); + const sliceFromStart = isSafari ? [] : children.slice(0, Math.max(0, firstIndex - saveLength)); const sliceFromEnd = children.slice(lastIndex + saveLength); /* if(sliceFromStart.length != sliceFromEnd.length) { diff --git a/src/lib/appManagers/appPrivacyManager.ts b/src/lib/appManagers/appPrivacyManager.ts new file mode 100644 index 00000000..72e5be62 --- /dev/null +++ b/src/lib/appManagers/appPrivacyManager.ts @@ -0,0 +1,66 @@ +import { MOUNT_CLASS_TO } from "../mtproto/mtproto_config"; +import { InputPrivacyKey, PrivacyRule } from "../../layer"; +import apiManager from "../mtproto/mtprotoworker"; +import appChatsManager from "./appChatsManager"; +import appUsersManager from "./appUsersManager"; + +export class AppPrivacyManager { + constructor() { + + } + + public getPrivacy(inputKey: InputPrivacyKey['_']) { + return apiManager.invokeApi('account.getPrivacy', { + key: { + _: inputKey + } + }).then(privacyRules => { + appUsersManager.saveApiUsers(privacyRules.users); + appChatsManager.saveApiChats(privacyRules.chats); + + console.log('privacy rules', inputKey, privacyRules, privacyRules.rules); + + return privacyRules.rules; + }); + } + + public getPrivacyRulesDetails(rules: PrivacyRule[]) { + const types: number[] = []; + + let allowLengths = {users: 0, chats: 0}, disallowLengths = {users: 0, chats: 0}; + rules.forEach(rule => { + switch(rule._) { + case 'privacyValueAllowAll': + types.push(2); + break; + case 'privacyValueDisallowAll': + types.push(0); + break; + case 'privacyValueAllowContacts': + types.push(1); + break; + /* case 'privacyValueDisallowContacts': + types.push('Except My Contacts'); + break; */ + case 'privacyValueAllowChatParticipants': + allowLengths.chats += rule.chats.length; + break; + case 'privacyValueAllowUsers': + allowLengths.users += rule.users.length; + break; + case 'privacyValueDisallowChatParticipants': + disallowLengths.chats += rule.chats.length; + break; + case 'privacyValueDisallowUsers': + disallowLengths.users += rule.users.length; + break; + } + }); + + return {type: types[0], disallowLengths, allowLengths}; + } +} + +const appPrivacyManager = new AppPrivacyManager(); +MOUNT_CLASS_TO && (MOUNT_CLASS_TO.appPrivacyManager = appPrivacyManager); +export default appPrivacyManager; \ No newline at end of file diff --git a/src/scss/partials/_leftSidebar.scss b/src/scss/partials/_leftSidebar.scss index 8f1f52b9..050a4570 100644 --- a/src/scss/partials/_leftSidebar.scss +++ b/src/scss/partials/_leftSidebar.scss @@ -853,8 +853,8 @@ &-section { padding: .5rem 0 1rem; - &-content { - margin: 0 0.125rem; + &-content, &-caption { + margin: 0 .125rem; @include respond-to(not-handhelds) { margin: 0 .625rem; @@ -865,6 +865,13 @@ padding: 1rem .875rem; } + &-caption { + margin-top: 1rem; + font-size: 0.875rem; + color: #707579; + line-height: 1.2; + } + .btn-primary, .checkbox-field, .radio-field { margin: 0; } @@ -884,7 +891,7 @@ height: 54px; } - .checkbox-field { + .checkbox-field, &-caption { padding: 0 .875rem; } } diff --git a/src/scss/style.scss b/src/scss/style.scss index 30bd73ef..facae31f 100644 --- a/src/scss/style.scss +++ b/src/scss/style.scss @@ -1117,9 +1117,30 @@ middle-ellipsis-element { } .row { - padding-left: 4.375rem; min-height: 3.375rem; - margin-top: 1rem; + margin-top: .5rem; + position: relative; + padding: .5rem .75rem; + display: flex; + flex-direction: column; + justify-content: center; + + &-with-padding { + padding-left: 4.375rem; + + .row-title.tgico:before { + position: absolute; + left: .75rem; + font-size: 1.5rem; + color: #707579; + } + } + + &-clickable { + cursor: pointer; + border-radius: $border-radius-medium; + overflow: hidden; + } .radio-field { &-main { @@ -1131,6 +1152,12 @@ middle-ellipsis-element { &-subtitle { color: #707579 !important; font-size: 14px !important; - line-height: 1.6; + } +} + +.hover-effect { + html.no-touch &:hover { + transition: .2s background-color; + background-color: var(--color-gray-hover); } }