Limit popup
This commit is contained in:
parent
beabcb2a23
commit
6984d20674
|
@ -5875,8 +5875,8 @@ export default class ChatBubbles {
|
|||
const message = await this.generateLocalFirstMessage(false, (message) => {
|
||||
const botInfo = userFull.bot_info;
|
||||
message.message = botInfo.description;
|
||||
if(botInfo.description_document) message.media = {_: 'messageMediaDocument', document: botInfo.description_document};
|
||||
if(botInfo.description_photo) message.media = {_: 'messageMediaPhoto', photo: botInfo.description_photo};
|
||||
if(botInfo.description_document) message.media = {_: 'messageMediaDocument', document: botInfo.description_document, pFlags: {}};
|
||||
if(botInfo.description_photo) message.media = {_: 'messageMediaPhoto', photo: botInfo.description_photo, pFlags: {}};
|
||||
});
|
||||
|
||||
if(!middleware()) {
|
||||
|
|
|
@ -13,15 +13,13 @@ import {ButtonMenuItemOptionsVerifiable, ButtonMenuSync} from './buttonMenu';
|
|||
import PopupDeleteDialog from './popups/deleteDialog';
|
||||
import {i18n, LangPackKey, _i18n} from '../lib/langPack';
|
||||
import findUpTag from '../helpers/dom/findUpTag';
|
||||
import PopupPeer, {PopupPeerButton} from './popups/peer';
|
||||
import AppChatFoldersTab from './sidebarLeft/tabs/chatFolders';
|
||||
import appSidebarLeft from './sidebarLeft';
|
||||
import {toastNew} from './toast';
|
||||
import PopupMute from './popups/mute';
|
||||
import {AppManagers} from '../lib/appManagers/managers';
|
||||
import positionMenu from '../helpers/positionMenu';
|
||||
import contextMenuController from '../helpers/contextMenuController';
|
||||
import {GENERAL_TOPIC_ID} from '../lib/mtproto/mtproto_config';
|
||||
import showLimitPopup from './popups/limit';
|
||||
|
||||
export default class DialogsContextMenu {
|
||||
private element: HTMLElement;
|
||||
|
@ -182,121 +180,7 @@ export default class DialogsContextMenu {
|
|||
} else if(filterId >= 1) {
|
||||
toastNew({langPackKey: 'PinFolderLimitReached'});
|
||||
} else {
|
||||
// const a: {[type in ApiLimitType]?: {
|
||||
// title: LangPackKey,
|
||||
// description: LangPackKey,
|
||||
// descriptionPremium: LangPackKey,
|
||||
// descriptionLocked: LangPackKey,
|
||||
// icon: string
|
||||
// }} = {
|
||||
// pin: {
|
||||
// title: 'LimitReached',
|
||||
// description: 'LimitReachedPinDialogs',
|
||||
// descriptionPremium: 'LimitReachedPinDialogsPremium',
|
||||
// descriptionLocked: 'LimitReachedPinDialogsLocked',
|
||||
// icon: 'limit_pin'
|
||||
// }
|
||||
// };
|
||||
|
||||
// class P extends PopupPeer {
|
||||
// constructor(options: {
|
||||
// isPremium: boolean,
|
||||
// limit: number,
|
||||
// limitPremium: number
|
||||
// }, _a: typeof a[keyof typeof a]) {
|
||||
// super('popup-limit', {
|
||||
// buttons: options.isPremium === undefined ? [{
|
||||
// langKey: 'LimitReached.Ok',
|
||||
// isCancel: true
|
||||
// }] : (options.isPremium ? [{
|
||||
// langKey: 'OK',
|
||||
// isCancel: true
|
||||
// }] : [{
|
||||
// langKey: 'IncreaseLimit',
|
||||
// callback: () => {
|
||||
|
||||
// }
|
||||
// }, {
|
||||
// langKey: 'Cancel',
|
||||
// isCancel: true
|
||||
// }]),
|
||||
// descriptionLangKey: options.isPremium === undefined ? _a.descriptionLocked : (options.isPremium ? _a.descriptionPremium : _a.description),
|
||||
// descriptionLangArgs: options.isPremium ? [options.limitPremium] : [options.limit, options.limitPremium],
|
||||
// titleLangKey: _a.title
|
||||
// });
|
||||
|
||||
// const isLocked = options.isPremium === undefined;
|
||||
// if(isLocked) {
|
||||
// this.element.classList.add('is-locked');
|
||||
// }
|
||||
|
||||
// const limitContainer = document.createElement('div');
|
||||
// limitContainer.classList.add('popup-limit-line');
|
||||
|
||||
// const hint = document.createElement('div');
|
||||
// hint.classList.add('popup-limit-hint');
|
||||
// const i = document.createElement('span');
|
||||
// i.classList.add('popup-limit-hint-icon', 'tgico-' + _a.icon);
|
||||
// hint.append(i, '' + (options.isPremium ? options.limitPremium : options.limit));
|
||||
|
||||
// limitContainer.append(hint);
|
||||
|
||||
// if(!isLocked) {
|
||||
// const limit = document.createElement('div');
|
||||
// limit.classList.add('limit-line');
|
||||
|
||||
// const free = document.createElement('div');
|
||||
// free.classList.add('limit-line-free');
|
||||
|
||||
// const premium = document.createElement('div');
|
||||
// premium.classList.add('limit-line-premium');
|
||||
|
||||
// limit.append(free, premium);
|
||||
|
||||
// _i18n(free, 'LimitFree');
|
||||
// premium.append(i18n('LimitPremium'), '' + options.limitPremium);
|
||||
|
||||
// limitContainer.append(limit);
|
||||
// }
|
||||
|
||||
// this.container.insertBefore(limitContainer, this.description);
|
||||
|
||||
// if(options.isPremium === false) {
|
||||
// this.buttons.pop().element.remove();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// async function showLimitPopup(type: keyof typeof a) {
|
||||
// const _a = a[type];
|
||||
// const [appConfig, limit, limitPremium] = await Promise.all([
|
||||
// rootScope.managers.apiManager.getAppConfig(),
|
||||
// ...[false, true].map((v) => rootScope.managers.apiManager.getLimit(type, v))
|
||||
// ]);
|
||||
// const isLocked = appConfig.premium_purchase_blocked;
|
||||
// new P({
|
||||
// isPremium: isLocked ? undefined : rootScope.premium,
|
||||
// limit,
|
||||
// limitPremium
|
||||
// }, _a).show();
|
||||
// }
|
||||
|
||||
// showLimitPopup('pin');
|
||||
|
||||
const config = await this.managers.apiManager.getConfig();
|
||||
new PopupPeer('pinned-dialogs-too-much', {
|
||||
buttons: [{
|
||||
langKey: 'OK',
|
||||
isCancel: true
|
||||
}, {
|
||||
langKey: 'FiltersSetupPinAlert',
|
||||
callback: () => {
|
||||
appSidebarLeft.createTab(AppChatFoldersTab).open();
|
||||
}
|
||||
}],
|
||||
descriptionLangKey: 'PinToTopLimitReached2',
|
||||
descriptionLangArgs: [i18n('Chats', [config.pinned_dialogs_count_max])]
|
||||
}).show();
|
||||
showLimitPopup('pin');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -719,7 +719,7 @@ export default class EmojiTab extends EmoticonsTabC<EmojiTabCategory> {
|
|||
) {
|
||||
const a = document.createElement('a');
|
||||
a.onclick = () => {
|
||||
appImManager.openUsername({userName: 'premiumbot'});
|
||||
appImManager.openPremiumBot();
|
||||
hideToast();
|
||||
};
|
||||
toastNew({
|
||||
|
|
|
@ -28,9 +28,10 @@ export type PopupButton = {
|
|||
callback?: () => void,
|
||||
langKey?: LangPackKey,
|
||||
langArgs?: any[],
|
||||
isDanger?: true,
|
||||
isCancel?: true,
|
||||
element?: HTMLButtonElement
|
||||
isDanger?: boolean,
|
||||
isCancel?: boolean,
|
||||
element?: HTMLButtonElement,
|
||||
noRipple?: boolean
|
||||
};
|
||||
|
||||
export type PopupOptions = Partial<{
|
||||
|
@ -180,16 +181,18 @@ export default class PopupElement<T extends EventListenerListeners = {}> extends
|
|||
const button = document.createElement('button');
|
||||
button.className = 'btn' + (b.isDanger ? ' danger' : ' primary');
|
||||
|
||||
ripple(button);
|
||||
if(!b.noRipple) {
|
||||
ripple(button);
|
||||
}
|
||||
|
||||
if(b.text) {
|
||||
button.innerHTML = b.text;
|
||||
button.textContent = b.text;
|
||||
} else {
|
||||
button.append(i18n(b.langKey, b.langArgs));
|
||||
}
|
||||
|
||||
attachClickEvent(button, () => {
|
||||
b.callback && b.callback();
|
||||
b.callback?.();
|
||||
this.destroy();
|
||||
}, {listenerSetter: this.listenerSetter, once: true});
|
||||
|
||||
|
|
|
@ -0,0 +1,137 @@
|
|||
/*
|
||||
* https://github.com/morethanwords/tweb
|
||||
* Copyright (C) 2019-2021 Eduard Kuzmenko
|
||||
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
import {doubleRaf} from '../../helpers/schedulers';
|
||||
import appImManager from '../../lib/appManagers/appImManager';
|
||||
import {LangPackKey, _i18n, i18n} from '../../lib/langPack';
|
||||
import {ApiLimitType} from '../../lib/mtproto/api_methods';
|
||||
import rootScope from '../../lib/rootScope';
|
||||
import PopupPeer from './peer';
|
||||
|
||||
const a: {[type in ApiLimitType]?: {
|
||||
title: LangPackKey,
|
||||
description: LangPackKey,
|
||||
descriptionPremium: LangPackKey,
|
||||
descriptionLocked: LangPackKey,
|
||||
icon: string
|
||||
}} = {
|
||||
pin: {
|
||||
title: 'LimitReached',
|
||||
description: 'LimitReachedPinDialogs',
|
||||
descriptionPremium: 'LimitReachedPinDialogsPremium',
|
||||
descriptionLocked: 'LimitReachedPinDialogsLocked',
|
||||
icon: 'limit_pin'
|
||||
},
|
||||
folders: {
|
||||
title: 'LimitReached',
|
||||
description: 'LimitReachedFolders',
|
||||
descriptionPremium: 'LimitReachedFoldersPremium',
|
||||
descriptionLocked: 'LimitReachedFoldersLocked',
|
||||
icon: 'limit_folders'
|
||||
}
|
||||
};
|
||||
|
||||
class P extends PopupPeer {
|
||||
constructor(options: {
|
||||
isPremium: boolean,
|
||||
limit: number,
|
||||
limitPremium: number
|
||||
}, _a: typeof a[keyof typeof a]) {
|
||||
super('popup-limit', {
|
||||
buttons: options.isPremium === undefined ? [{
|
||||
langKey: 'LimitReached.Ok',
|
||||
isCancel: true
|
||||
}] : (options.isPremium ? [{
|
||||
langKey: 'OK',
|
||||
isCancel: true
|
||||
}] : [{
|
||||
langKey: 'IncreaseLimit',
|
||||
callback: () => {
|
||||
appImManager.openPremiumBot();
|
||||
},
|
||||
noRipple: true
|
||||
}, {
|
||||
langKey: 'Cancel',
|
||||
isCancel: true
|
||||
}]),
|
||||
descriptionLangKey: options.isPremium === undefined ? _a.descriptionLocked : (options.isPremium ? _a.descriptionPremium : _a.description),
|
||||
descriptionLangArgs: options.isPremium ? [options.limitPremium] : [options.limit, options.limitPremium],
|
||||
titleLangKey: _a.title
|
||||
});
|
||||
|
||||
const isLocked = options.isPremium === undefined;
|
||||
if(isLocked) {
|
||||
this.element.classList.add('is-locked');
|
||||
} else if(options.isPremium) {
|
||||
this.element.classList.add('is-premium');
|
||||
} else {
|
||||
const button = this.buttons.find((b) => !b.isCancel);
|
||||
button.element.classList.add('popup-limit-button');
|
||||
const i = document.createElement('i');
|
||||
i.classList.add('popup-limit-button-icon', 'tgico-premium_double');
|
||||
button.element.append(i);
|
||||
}
|
||||
|
||||
const limitContainer = document.createElement('div');
|
||||
limitContainer.classList.add('popup-limit-line');
|
||||
|
||||
const hint = document.createElement('div');
|
||||
hint.classList.add('popup-limit-hint');
|
||||
const i = document.createElement('span');
|
||||
i.classList.add('popup-limit-hint-icon', 'tgico-' + _a.icon);
|
||||
hint.append(i, '' + (options.isPremium ? options.limitPremium : options.limit));
|
||||
|
||||
limitContainer.append(hint);
|
||||
|
||||
if(!isLocked) {
|
||||
const limit = document.createElement('div');
|
||||
limit.classList.add('limit-line');
|
||||
|
||||
const free = document.createElement('div');
|
||||
free.classList.add('limit-line-free');
|
||||
|
||||
const premium = document.createElement('div');
|
||||
premium.classList.add('limit-line-premium');
|
||||
|
||||
limit.append(free, premium);
|
||||
|
||||
_i18n(free, 'LimitFree');
|
||||
premium.append(i18n('LimitPremium'), '' + options.limitPremium);
|
||||
|
||||
limitContainer.append(limit);
|
||||
}
|
||||
|
||||
this.container.insertBefore(limitContainer, this.description);
|
||||
|
||||
// if(options.isPremium === false) {
|
||||
// this.buttons.pop().element.remove();
|
||||
// }
|
||||
|
||||
const setHintActive = () => {
|
||||
hint.classList.add('active');
|
||||
};
|
||||
|
||||
if(rootScope.settings.animationsEnabled) {
|
||||
doubleRaf().then(setHintActive);
|
||||
} else {
|
||||
setHintActive();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default async function showLimitPopup(type: keyof typeof a) {
|
||||
const _a = a[type];
|
||||
const [appConfig, limit, limitPremium] = await Promise.all([
|
||||
rootScope.managers.apiManager.getAppConfig(),
|
||||
...[false, true].map((v) => rootScope.managers.apiManager.getLimit(type, v))
|
||||
]);
|
||||
const isLocked = appConfig.premium_purchase_blocked;
|
||||
new P({
|
||||
isPremium: isLocked ? undefined : rootScope.premium,
|
||||
limit,
|
||||
limitPremium
|
||||
}, _a).show();
|
||||
}
|
|
@ -9,7 +9,6 @@ import type {DialogFilterSuggested} from '../../../layer';
|
|||
import type _rootScope from '../../../lib/rootScope';
|
||||
import {SliderSuperTab} from '../../slider';
|
||||
import lottieLoader, {LottieLoader} from '../../../lib/rlottie/lottieLoader';
|
||||
import {toast} from '../../toast';
|
||||
import Button from '../../button';
|
||||
import rootScope from '../../../lib/rootScope';
|
||||
import AppEditFolderTab from './editFolder';
|
||||
|
@ -26,6 +25,7 @@ import SettingSection from '../../settingSection';
|
|||
import Sortable from '../../../helpers/dom/sortable';
|
||||
import whichChild from '../../../helpers/dom/whichChild';
|
||||
import indexOfAndSplice from '../../../helpers/array/indexOfAndSplice';
|
||||
import showLimitPopup from '../../popups/limit';
|
||||
|
||||
export default class AppChatFoldersTab extends SliderSuperTab {
|
||||
private createFolderBtn: HTMLElement;
|
||||
|
@ -160,7 +160,7 @@ export default class AppChatFoldersTab extends SliderSuperTab {
|
|||
this.foldersSection = new SettingSection({
|
||||
name: 'Filters'
|
||||
});
|
||||
this.foldersSection.container.style.display = 'none';
|
||||
this.foldersSection.container.classList.add('hide');
|
||||
|
||||
this.list = document.createElement('div');
|
||||
this.foldersSection.content.append(this.list);
|
||||
|
@ -168,20 +168,26 @@ export default class AppChatFoldersTab extends SliderSuperTab {
|
|||
this.suggestedSection = new SettingSection({
|
||||
name: 'FilterRecommended'
|
||||
});
|
||||
this.suggestedSection.container.style.display = 'none';
|
||||
this.suggestedSection.container.classList.add('hide');
|
||||
|
||||
this.scrollable.append(this.stickerContainer, caption, this.createFolderBtn, this.foldersSection.container, this.suggestedSection.container);
|
||||
this.scrollable.append(
|
||||
this.stickerContainer,
|
||||
caption,
|
||||
this.createFolderBtn,
|
||||
this.foldersSection.container,
|
||||
this.suggestedSection.container
|
||||
);
|
||||
|
||||
attachClickEvent(this.createFolderBtn, async() => {
|
||||
if(!(await this.canCreateFolder())) {
|
||||
toast('Sorry, you can\'t create more folders.');
|
||||
showLimitPopup('folders');
|
||||
} else {
|
||||
this.slider.createTab(AppEditFolderTab).open();
|
||||
}
|
||||
}, {listenerSetter: this.listenerSetter});
|
||||
|
||||
const onFiltersContainerUpdate = () => {
|
||||
this.foldersSection.container.style.display = Object.keys(this.filtersRendered).length ? '' : 'none';
|
||||
this.foldersSection.container.classList.toggle('hide', !Object.keys(this.filtersRendered).length);
|
||||
};
|
||||
|
||||
this.managers.filtersStorage.getDialogFilters().then(async(filters) => {
|
||||
|
@ -310,7 +316,7 @@ export default class AppChatFoldersTab extends SliderSuperTab {
|
|||
|
||||
private getSuggestedFilters() {
|
||||
return this.managers.filtersStorage.getSuggestedDialogsFilters().then(async(suggestedFilters) => {
|
||||
this.suggestedSection.container.style.display = suggestedFilters.length ? '' : 'none';
|
||||
this.suggestedSection.container.classList.toggle('hide', !suggestedFilters.length);
|
||||
Array.from(this.suggestedSection.content.children).slice(1).forEach((el) => el.remove());
|
||||
|
||||
for(const filter of suggestedFilters) {
|
||||
|
@ -322,7 +328,7 @@ export default class AppChatFoldersTab extends SliderSuperTab {
|
|||
cancelEvent(e);
|
||||
|
||||
if(!(await this.canCreateFolder())) {
|
||||
toast('Sorry, you can\'t create more folders.');
|
||||
showLimitPopup('folders');
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -333,10 +339,9 @@ export default class AppChatFoldersTab extends SliderSuperTab {
|
|||
f.excludePeerIds = [];
|
||||
f.pinnedPeerIds = [];
|
||||
|
||||
this.managers.filtersStorage.createDialogFilter(f, true).then((bool) => {
|
||||
if(bool) {
|
||||
row.container.remove();
|
||||
}
|
||||
this.managers.filtersStorage.createDialogFilter(f, true).then(() => {
|
||||
row.container.remove();
|
||||
this.suggestedSection.container.classList.toggle('hide', this.suggestedSection.content.childElementCount === 1);
|
||||
}).finally(() => {
|
||||
button.removeAttribute('disabled');
|
||||
});
|
||||
|
|
|
@ -179,7 +179,13 @@ export default class SwipeHandler {
|
|||
this.listenerSetter.add(attachGlobalListenerTo)('mousemove', this.handleMove, MOUSE_MOVE_OPTIONS);
|
||||
}
|
||||
|
||||
this.onStart?.();
|
||||
if(this.onStart) {
|
||||
this.onStart();
|
||||
|
||||
// have to initiate move instantly
|
||||
this.hadMove = true;
|
||||
this.handleMove(e);
|
||||
}
|
||||
};
|
||||
|
||||
protected handleMove = (_e: EE) => {
|
||||
|
@ -213,7 +219,6 @@ export default class SwipeHandler {
|
|||
this.onFirstSwipe?.(_e);
|
||||
}
|
||||
|
||||
/* reset values */
|
||||
const onSwipeResult = this.onSwipe(xDiff, yDiff, _e);
|
||||
if(onSwipeResult !== undefined && onSwipeResult) {
|
||||
this.reset();
|
||||
|
|
|
@ -21,7 +21,7 @@ const App = {
|
|||
version: process.env.VERSION,
|
||||
versionFull: process.env.VERSION_FULL,
|
||||
build: +process.env.BUILD,
|
||||
langPackVersion: '0.7.7',
|
||||
langPackVersion: '0.7.8',
|
||||
langPack: 'webk',
|
||||
langPackCode: 'en',
|
||||
domains: MAIN_DOMAINS,
|
||||
|
|
|
@ -869,6 +869,9 @@ const lang = {
|
|||
'TerminateWebSessionInfo': 'Tap to disconnect from your Telegram account.',
|
||||
'EnablePhotoSpoiler': 'Hide with spoiler',
|
||||
'DisablePhotoSpoiler': 'Remove spoiler',
|
||||
'LimitReachedFolders': 'You have reached the limit of **%1$d** folders. You can double the limit to **%2$d** folders by subscribing to **Telegram Premium**.',
|
||||
'LimitReachedFoldersPremium': 'You have reached the limit of **%1$d** folders for this account.',
|
||||
'LimitReachedFoldersLocked': 'You have reached the limit of **%1$d** folders for this account. We are working to let you increase this limit in the future.',
|
||||
|
||||
// * macos
|
||||
'AccountSettings.Filters': 'Chat Folders',
|
||||
|
|
|
@ -1293,6 +1293,12 @@ export class AppImManager extends EventListenerBase<{
|
|||
});
|
||||
}
|
||||
|
||||
public openPremiumBot() {
|
||||
return this.managers.apiManager.getAppConfig().then((appConfig) => {
|
||||
return this.openUsername({userName: appConfig.premium_bot_username});
|
||||
});
|
||||
}
|
||||
|
||||
public openUsername(options: {
|
||||
userName: string
|
||||
} & Omit<ChatSetPeerOptions, 'peerId'>) {
|
||||
|
|
|
@ -22,6 +22,15 @@
|
|||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: var(--font-weight-bold);
|
||||
transform: scale(.6) translate(-280px, 16px) rotate(-30deg);
|
||||
|
||||
@include animation-level(2) {
|
||||
transition: transform .3s cubic-bezier(.12,1.1,.56,1.2);
|
||||
}
|
||||
|
||||
&.active {
|
||||
transform: scale(1) translate(0, 0) rotate(0);
|
||||
}
|
||||
|
||||
&-icon {
|
||||
font-size: 1.25rem;
|
||||
|
@ -30,6 +39,21 @@
|
|||
}
|
||||
}
|
||||
|
||||
&-button {
|
||||
&.primary {
|
||||
background: linear-gradient(88.39deg, #6C93FF -2.56%, #976FFF 51.27%, #DF69D1 107.39%);
|
||||
color: #fff !important;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&-icon {
|
||||
margin-left: .625rem;
|
||||
font-size: 1.5rem;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.limit-line {
|
||||
align-self: stretch;
|
||||
margin: 1rem .5rem 0;
|
||||
|
@ -67,4 +91,26 @@
|
|||
margin-right: -.25rem;
|
||||
background-color: var(--primary-color);
|
||||
}
|
||||
|
||||
&.is-premium &-hint {
|
||||
align-self: flex-end;
|
||||
margin-right: .5rem;
|
||||
border-bottom-right-radius: 0;
|
||||
background: linear-gradient(84.4deg,#6c93ff -4.85%,#976fff 51.72%,#df69d1 110.7%);
|
||||
background-size: 200px 2rem;
|
||||
background-position-x: 100%;
|
||||
|
||||
@include animation-level(2) {
|
||||
transition: transform .3s cubic-bezier(.12,1.1,.56,1.1);
|
||||
}
|
||||
|
||||
&:after {
|
||||
height: 12px;
|
||||
left: 100%;
|
||||
bottom: -11.5px;
|
||||
margin-left: -20.6px;
|
||||
background-position-x: 134%;
|
||||
clip-path: path("M8.44528 0.5H20.5V10.1943C20.5 10.9154 19.9154 11.5 19.1943 11.5C18.8178 11.5 18.4597 11.3375 18.2117 11.0541L10.2274 1.92918C9.75146 1.38523 9.18812 0.924478 8.56057 0.565879L8.44528 0.5Z");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue