Browse Source

Merge branch 'master' into master

master
wang chenyu 4 years ago committed by GitHub
parent
commit
10a32650da
  1. 2
      server.js
  2. 7
      src/components/popups/deleteDialog.ts
  3. 2
      src/config/app.ts
  4. 47
      src/index.ts
  5. 52
      src/lib/appManagers/apiUpdatesManager.ts
  6. 9
      src/lib/appManagers/appChatsManager.ts
  7. 29
      src/lib/appManagers/appDialogsManager.ts
  8. 3
      src/lib/appManagers/appMessagesManager.ts
  9. 33
      src/lib/idb.ts
  10. 7
      src/lib/mtproto/apiManager.ts
  11. 8
      src/lib/mtproto/mtprotoworker.ts
  12. 10
      src/lib/mtproto/networker.ts
  13. 4
      src/lib/storage.ts
  14. 5
      src/scss/partials/pages/_pages.scss

2
server.js

@ -25,7 +25,7 @@ const server = useHttp ? http : https; @@ -25,7 +25,7 @@ const server = useHttp ? http : https;
let options = {};
if(!useHttp) {
options.key = fs.readFileSync(__dirname + '/certs/server-key.pem');
options.cert = s.readFileSync(__dirname + '/certs/server-cert.pem');
options.cert = fs.readFileSync(__dirname + '/certs/server-cert.pem');
}
server.createServer(options, app).listen(port, () => {

7
src/components/popups/deleteDialog.ts

@ -24,7 +24,10 @@ export default class PopupDeleteDialog { @@ -24,7 +24,10 @@ export default class PopupDeleteDialog {
}; */
const callbackLeave = (checked: PopupPeerButtonCallbackCheckboxes) => {
const promise = appChatsManager.leave(-peerId);
const promise = appChatsManager.leave(-peerId).then(() => {
return appMessagesManager.flushHistory(-peerId);
});
onSelect && onSelect(promise);
};
@ -37,7 +40,7 @@ export default class PopupDeleteDialog { @@ -37,7 +40,7 @@ export default class PopupDeleteDialog {
if(checked[checkboxes[0].text]) {
promise = appChatsManager.delete(-peerId);
} else {
promise = appChatsManager.leave(-peerId);
return callbackLeave(checked);
}
}

2
src/config/app.ts

@ -12,7 +12,7 @@ @@ -12,7 +12,7 @@
const App = {
id: 1025907,
hash: '452b0359b988148995f22ff0f4229750',
version: '0.4.3',
version: '0.5.0',
langPackVersion: '0.1.6',
langPack: 'macos',
langPackCode: 'en',

47
src/index.ts

@ -248,6 +248,11 @@ console.timeEnd('get storage1'); */ @@ -248,6 +248,11 @@ console.timeEnd('get storage1'); */
//import('./vendor/dateFormat');
const langPromise = I18n.default.getCacheLangPack();
function loadFonts(): Promise<void> {
// @ts-ignore
return 'fonts' in document ? Promise.all(['400 1rem Roboto', '500 1rem Roboto'].map(font => document.fonts.load(font))) : Promise.resolve();
}
const [state, langPack] = await Promise.all([
appStateManager.default.getState(),
@ -263,6 +268,19 @@ console.timeEnd('get storage1'); */ @@ -263,6 +268,19 @@ console.timeEnd('get storage1'); */
I18n.default.getLangPack(langPack.lang_code);
}
/**
* won't fire if font is loaded too fast
*/
function fadeInWhenFontsReady(elem: HTMLElement, promise: Promise<void>) {
elem.style.opacity = '0';
promise.then(() => {
window.requestAnimationFrame(() => {
elem.style.opacity = '';
});
});
}
console.log('got state, time:', performance.now() - perf);
const authState = state.authState;
@ -270,12 +288,16 @@ console.timeEnd('get storage1'); */ @@ -270,12 +288,16 @@ console.timeEnd('get storage1'); */
console.log('Will mount auth page:', authState._, Date.now() / 1000);
const el = document.getElementById('auth-pages');
let scrollable: HTMLElement;
if(el) {
const scrollable = el.querySelector('.scrollable');
scrollable = el.querySelector('.scrollable') as HTMLElement;
if((!touchSupport.isTouchSupported || isMobileSafari)) {
scrollable.classList.add('no-scrollbar');
}
// * don't remove this line
scrollable.style.opacity = '0';
const placeholder = document.createElement('div');
placeholder.classList.add('auth-placeholder');
@ -283,26 +305,38 @@ console.timeEnd('get storage1'); */ @@ -283,26 +305,38 @@ console.timeEnd('get storage1'); */
scrollable.append(placeholder.cloneNode());
}
let pagePromise: Promise<void>;
//langPromise.then(async() => {
switch(authState._) {
case 'authStateSignIn':
(await import('./pages/pageSignIn')).default.mount();
pagePromise = (await import('./pages/pageSignIn')).default.mount();
break;
case 'authStateSignQr':
(await import('./pages/pageSignQR')).default.mount();
pagePromise = (await import('./pages/pageSignQR')).default.mount();
break;
case 'authStateAuthCode':
(await import('./pages/pageAuthCode')).default.mount(authState.sentCode);
pagePromise = (await import('./pages/pageAuthCode')).default.mount(authState.sentCode);
break;
case 'authStatePassword':
(await import('./pages/pagePassword')).default.mount();
pagePromise = (await import('./pages/pagePassword')).default.mount();
break;
case 'authStateSignUp':
(await import('./pages/pageSignUp')).default.mount(authState.authCode);
pagePromise = (await import('./pages/pageSignUp')).default.mount(authState.authCode);
break;
}
//});
if(scrollable) {
// wait for text appear
if(pagePromise) {
await pagePromise;
}
// @ts-ignore
const promise = 'fonts' in document ? document.fonts.ready : Promise.resolve();
fadeInWhenFontsReady(scrollable, promise);
}
/* computeCheck(password, {
current_algo: {
salt1,
@ -348,6 +382,7 @@ console.timeEnd('get storage1'); */ @@ -348,6 +382,7 @@ console.timeEnd('get storage1'); */
}, 500); */
} else {
console.log('Will mount IM page:', Date.now() / 1000);
fadeInWhenFontsReady(document.getElementById('main-columns'), loadFonts());
(await import('./pages/pageIm')).default.mount();
//getNearestDc();
}

52
src/lib/appManagers/apiUpdatesManager.ts

@ -11,16 +11,15 @@ @@ -11,16 +11,15 @@
//import apiManager from '../mtproto/apiManager';
import DEBUG, { MOUNT_CLASS_TO } from '../../config/debug';
import { copy } from '../../helpers/object';
import { Update } from '../../layer';
import { logger, LogTypes } from '../logger';
import apiManager from '../mtproto/mtprotoworker';
import rootScope from '../rootScope';
//import networkerFactory from '../mtproto/networkerFactory';
import appUsersManager from "./appUsersManager";
import appChatsManager from "./appChatsManager";
import appPeersManager from "./appPeersManager";
import appStateManager from './appStateManager';
import appUsersManager from "./appUsersManager";
type UpdatesState = {
pendingPtsUpdates: {pts: number, pts_count: number}[],
@ -51,7 +50,7 @@ export class ApiUpdatesManager { @@ -51,7 +50,7 @@ export class ApiUpdatesManager {
public channelStates: {[channelId: number]: UpdatesState} = {};
private attached = false;
private log = logger('UPDATES', LogTypes.Error | LogTypes.Warn/* | LogTypes.Log | LogTypes.Debug */);
private log = logger('UPDATES', LogTypes.Error | LogTypes.Warn | LogTypes.Log/* | LogTypes.Debug */);
private debug = DEBUG;
private setProxy() {
@ -60,17 +59,21 @@ export class ApiUpdatesManager { @@ -60,17 +59,21 @@ export class ApiUpdatesManager {
set: function(target: ApiUpdatesManager['updatesState'], key: keyof ApiUpdatesManager['updatesState'], value: ApiUpdatesManager['updatesState'][typeof key]) {
// @ts-ignore
target[key] = value;
const us = self.updatesState;
appStateManager.pushToState('updates', {
seq: us.seq,
pts: us.pts,
date: us.date
});
self.saveUpdatesState();
return true;
}
});
}
public saveUpdatesState() {
const us = this.updatesState;
appStateManager.pushToState('updates', {
seq: us.seq,
pts: us.pts,
date: us.date
});
}
private popPendingSeqUpdate() {
const state = this.updatesState;
const nextSeq = state.seq + 1;
@ -132,7 +135,7 @@ export class ApiUpdatesManager { @@ -132,7 +135,7 @@ export class ApiUpdatesManager {
return false;
}
this.debug && this.log('pop pending pts updates', goodPts, curState.pendingPtsUpdates.slice(0, goodIndex + 1));
this.debug && this.log.debug('pop pending pts updates', goodPts, curState.pendingPtsUpdates.slice(0, goodIndex + 1));
curState.pts = goodPts;
for(let i = 0; i <= goodIndex; ++i) {
@ -172,7 +175,7 @@ export class ApiUpdatesManager { @@ -172,7 +175,7 @@ export class ApiUpdatesManager {
//ignoreSyncLoading: options.ignoreSyncLoading
};
this.debug && this.log('processUpdateMessage', updateMessage);
this.debug && this.log.debug('processUpdateMessage', updateMessage);
switch(updateMessage._) {
case 'updatesTooLong':
@ -186,7 +189,7 @@ export class ApiUpdatesManager { @@ -186,7 +189,7 @@ export class ApiUpdatesManager {
case 'updateShortMessage':
case 'updateShortChatMessage': {
this.debug && this.log('updateShortMessage | updateShortChatMessage', {...updateMessage});
this.debug && this.log.debug('updateShortMessage | updateShortChatMessage', {...updateMessage});
const isOut = updateMessage.pFlags.out;
const fromId = updateMessage.from_id || (isOut ? rootScope.myId : updateMessage.user_id);
const toId = updateMessage.chat_id
@ -249,10 +252,10 @@ export class ApiUpdatesManager { @@ -249,10 +252,10 @@ export class ApiUpdatesManager {
}, {
timeout: 0x7fffffff
}).then((differenceResult) => {
this.debug && this.log('Get diff result', differenceResult);
this.debug && this.log.debug('Get diff result', differenceResult);
if(differenceResult._ === 'updates.differenceEmpty') {
this.debug && this.log('apply empty diff', differenceResult.seq);
this.debug && this.log.debug('apply empty diff', differenceResult.seq);
updatesState.date = differenceResult.date;
updatesState.seq = differenceResult.seq;
return;
@ -307,7 +310,7 @@ export class ApiUpdatesManager { @@ -307,7 +310,7 @@ export class ApiUpdatesManager {
if(differenceResult._ === 'updates.differenceSlice') {
return this.getDifference();
} else {
this.debug && this.log('finished get diff');
this.debug && this.log.debug('finished get diff');
}
});
@ -337,16 +340,16 @@ export class ApiUpdatesManager { @@ -337,16 +340,16 @@ export class ApiUpdatesManager {
pts: channelState.pts,
limit: 30
}, {timeout: 0x7fffffff}).then((differenceResult) => {
this.debug && this.log('Get channel diff result', differenceResult)
this.debug && this.log.debug('Get channel diff result', differenceResult)
channelState.pts = 'pts' in differenceResult ? differenceResult.pts : undefined;
if(differenceResult._ === 'updates.channelDifferenceEmpty') {
this.debug && this.log('apply channel empty diff', differenceResult);
this.debug && this.log.debug('apply channel empty diff', differenceResult);
return;
}
if(differenceResult._ === 'updates.channelDifferenceTooLong') {
this.debug && this.log('channel diff too long', differenceResult);
this.debug && this.log.debug('channel diff too long', differenceResult);
delete this.channelStates[channelId];
// @ts-ignore
@ -358,12 +361,12 @@ export class ApiUpdatesManager { @@ -358,12 +361,12 @@ export class ApiUpdatesManager {
appChatsManager.saveApiChats(differenceResult.chats);
// Should be first because of updateMessageID
this.debug && this.log('applying', differenceResult.other_updates.length, 'channel other updates');
this.debug && this.log.debug('applying', differenceResult.other_updates.length, 'channel other updates');
differenceResult.other_updates.forEach((update) => {
this.saveUpdate(update);
});
this.debug && this.log('applying', differenceResult.new_messages.length, 'channel new messages');
this.debug && this.log.debug('applying', differenceResult.new_messages.length, 'channel new messages');
differenceResult.new_messages.forEach((apiMessage) => {
this.saveUpdate({
_: 'updateNewChannelMessage',
@ -373,13 +376,13 @@ export class ApiUpdatesManager { @@ -373,13 +376,13 @@ export class ApiUpdatesManager {
});
});
this.debug && this.log('apply channel diff', channelState.pts);
this.debug && this.log.debug('apply channel diff', channelState.pts);
if(differenceResult._ === 'updates.channelDifference' &&
!differenceResult.pFlags['final']) {
return this.getChannelDifference(channelId);
} else {
this.debug && this.log('finished channel get diff');
this.debug && this.log.debug('finished channel get diff');
}
});
@ -593,7 +596,7 @@ export class ApiUpdatesManager { @@ -593,7 +596,7 @@ export class ApiUpdatesManager {
}
public saveUpdate(update: Update) {
this.log('saveUpdate', update);
this.debug && this.log('saveUpdate', update);
rootScope.dispatchEvent(update._, update as any);
}
@ -618,6 +621,7 @@ export class ApiUpdatesManager { @@ -618,6 +621,7 @@ export class ApiUpdatesManager {
this.updatesState.seq = stateResult.seq;
this.updatesState.pts = stateResult.pts;
this.updatesState.date = stateResult.date;
this.saveUpdatesState();
//setTimeout(() => {
this.updatesState.syncLoading = null;
resolve();
@ -639,7 +643,7 @@ export class ApiUpdatesManager { @@ -639,7 +643,7 @@ export class ApiUpdatesManager {
Object.assign(this.updatesState, state);
this.log('will get difference', copy(state));
this.log('will get difference', Object.assign({}, state));
this.getDifference(true)/* .finally(() => {
if(this.updatesState.syncLoading) {

9
src/lib/appManagers/appChatsManager.ts

@ -19,7 +19,6 @@ import apiManager from '../mtproto/mtprotoworker'; @@ -19,7 +19,6 @@ import apiManager from '../mtproto/mtprotoworker';
import { RichTextProcessor } from "../richtextprocessor";
import rootScope from "../rootScope";
import apiUpdatesManager from "./apiUpdatesManager";
import appMessagesManager from "./appMessagesManager";
import appPeersManager from "./appPeersManager";
import appProfileManager from "./appProfileManager";
import appStateManager from "./appStateManager";
@ -637,12 +636,8 @@ export class AppChatsManager { @@ -637,12 +636,8 @@ export class AppChatsManager {
}).then(this.onChatUpdated.bind(this, id));
}
public leaveChat(id: number, flushHistory = true) {
let promise: Promise<any> = this.deleteChatUser(id, appUsersManager.getSelf().id)
if(flushHistory) promise = promise.then(() => {
return appMessagesManager.flushHistory(-id);
});
return promise;;
public leaveChat(id: number) {
return this.deleteChatUser(id, appUsersManager.getSelf().id);
}
public leave(id: number) {

29
src/lib/appManagers/appDialogsManager.ts

@ -213,7 +213,7 @@ export class AppDialogsManager { @@ -213,7 +213,7 @@ export class AppDialogsManager {
title: HTMLElement
}
} = {};
private showFiltersTimeout: number;
private showFiltersPromise: Promise<void>;
private allUnreadCount: HTMLElement;
private accumulateArchivedTimeout: number;
@ -491,16 +491,22 @@ export class AppDialogsManager { @@ -491,16 +491,22 @@ export class AppDialogsManager {
//selectTab(0);
(this.folders.menu.firstElementChild as HTMLElement).click();
appMessagesManager.construct();
appStateManager.getState().then((state) => {
appStateManager.getState().then(async(state) => {
appNotificationsManager.getNotifyPeerTypeSettings();
const getFiltersPromise = appMessagesManager.filtersStorage.getDialogFilters();
getFiltersPromise.then((filters) => {
const renderFiltersPromise = appMessagesManager.filtersStorage.getDialogFilters().then((filters) => {
for(const filter of filters) {
this.addFilter(filter);
}
});
if(state.filters && Object.keys(state.filters).length) {
await renderFiltersPromise;
if(this.showFiltersPromise) {
await this.showFiltersPromise;
}
}
if(appStateManager.storagesResults.dialogs.length) {
appDraftsManager.getAllDrafts();
appDraftsManager.addMissedDialogs();
@ -706,12 +712,15 @@ export class AppDialogsManager { @@ -706,12 +712,15 @@ export class AppDialogsManager {
title: titleSpan
};
if(!this.showFiltersTimeout && Object.keys(this.filtersRendered).length > 1) {
this.showFiltersTimeout = window.setTimeout(() => {
this.showFiltersTimeout = 0;
this.folders.menuScrollContainer.classList.remove('hide');
this.setFiltersUnreadCount();
}, 0);
if(!this.showFiltersPromise && Object.keys(this.filtersRendered).length > 1) {
this.showFiltersPromise = new Promise<void>((resolve) => {
window.setTimeout(() => {
this.showFiltersPromise = undefined;
this.folders.menuScrollContainer.classList.remove('hide');
this.setFiltersUnreadCount();
resolve();
}, 0);
});
}
}

3
src/lib/appManagers/appMessagesManager.ts

@ -305,8 +305,8 @@ export class AppMessagesManager { @@ -305,8 +305,8 @@ export class AppMessagesManager {
}
public construct() {
this.dialogsStorage = new DialogsStorage(this, appChatsManager, appPeersManager, appUsersManager, appDraftsManager, appNotificationsManager, appStateManager, apiUpdatesManager, serverTimeManager);
this.filtersStorage = new FiltersStorage(this, appPeersManager, appUsersManager, appNotificationsManager, appStateManager, apiUpdatesManager, /* apiManager, */ rootScope);
this.dialogsStorage = new DialogsStorage(this, appChatsManager, appPeersManager, appUsersManager, appDraftsManager, appNotificationsManager, appStateManager, apiUpdatesManager, serverTimeManager);
}
public getInputEntities(entities: MessageEntity[]) {
@ -2362,7 +2362,6 @@ export class AppMessagesManager { @@ -2362,7 +2362,6 @@ export class AppMessagesManager {
break;
case 'messageActionPhoneCall':
delete message.fromId;
message.action.type =
(message.pFlags.out ? 'out_' : 'in_') +
(

33
src/lib/idb.ts

@ -38,8 +38,9 @@ export type IDBOptions = { @@ -38,8 +38,9 @@ export type IDBOptions = {
const DEBUG = false;
export default class IDBStorage {
//private static STORAGES: IDBStorage[] = [];
private static STORAGES: IDBStorage[] = [];
private openDbPromise: Promise<IDBDatabase>;
private db: IDBDatabase;
private storageIsAvailable = true;
private log: ReturnType<typeof logger>;
@ -57,7 +58,33 @@ export default class IDBStorage { @@ -57,7 +58,33 @@ export default class IDBStorage {
this.openDatabase(true);
//IDBStorage.STORAGES.push(this);
IDBStorage.STORAGES.push(this);
}
public static closeDatabases() {
this.STORAGES.forEach(storage => {
const db = storage.db;
if(db) {
db.onclose = () => {};
db.close();
}
});
}
public static deleteDatabase() {
this.closeDatabases();
return new Promise<void>((resolve, reject) => {
const deleteRequest = indexedDB.deleteDatabase(Database.name);
deleteRequest.onerror = () => {
reject();
};
deleteRequest.onsuccess = () => {
resolve();
};
});
}
public isAvailable() {
@ -134,7 +161,7 @@ export default class IDBStorage { @@ -134,7 +161,7 @@ export default class IDBStorage {
this.log.error('onversionchange, lol?');
};
resolve(db);
resolve(this.db = db);
};
request.onerror = (event) => {

7
src/lib/mtproto/apiManager.ts

@ -26,6 +26,7 @@ import { bytesFromHex, bytesToHex } from '../../helpers/bytes'; @@ -26,6 +26,7 @@ import { bytesFromHex, bytesToHex } from '../../helpers/bytes';
import { ctx, isSafari } from '../../helpers/userAgent';
import App from '../../config/app';
import { MOUNT_CLASS_TO } from '../../config/debug';
import IDBStorage from '../idb';
/// #if !MTPROTO_WORKER
import rootScope from '../rootScope';
@ -149,10 +150,8 @@ export class ApiManager { @@ -149,10 +150,8 @@ export class ApiManager {
this.baseDcId = 0;
//this.telegramMeNotify(false);
const promise = sessionStorage.clear();
promise.finally(() => {
self.postMessage({type: 'reload'});
});
IDBStorage.closeDatabases();
self.postMessage({type: 'clear'});
};
setTimeout(clear, 1e3);

8
src/lib/mtproto/mtprotoworker.ts

@ -19,6 +19,7 @@ import { UserAuth } from './mtproto_config'; @@ -19,6 +19,7 @@ import { UserAuth } from './mtproto_config';
import type { MTMessage } from './networker';
import DEBUG, { MOUNT_CLASS_TO } from '../../config/debug';
import Socket from './transports/websocket';
import IDBStorage from '../idb';
type Task = {
taskId: number,
@ -87,8 +88,11 @@ export class ApiManagerProxy extends CryptoWorkerMethods { @@ -87,8 +88,11 @@ export class ApiManagerProxy extends CryptoWorkerMethods {
this.registerServiceWorker();
this.addTaskListener('reload', () => {
location.reload();
this.addTaskListener('clear', () => {
const promise = IDBStorage.deleteDatabase();
promise.finally(() => {
location.reload();
});
});
this.addTaskListener('connectionStatusChange', (task: any) => {

10
src/lib/mtproto/networker.ts

@ -141,7 +141,7 @@ export default class MTPNetworker { @@ -141,7 +141,7 @@ export default class MTPNetworker {
const suffix = this.isFileUpload ? '-U' : this.isFileDownload ? '-D' : '';
this.name = 'NET-' + dcId + suffix;
//this.log = logger(this.name, this.upload && this.dcId === 2 ? LogLevels.debug | LogLevels.warn | LogLevels.log | LogLevels.error : LogLevels.error);
this.log = logger(this.name, /* LogTypes.Log | LogTypes.Debug | */LogTypes.Error | LogTypes.Warn);
this.log = logger(this.name, LogTypes.Log | /* LogTypes.Debug | */LogTypes.Error | LogTypes.Warn);
this.log('constructor'/* , this.authKey, this.authKeyID, this.serverSalt */);
// Test resend after bad_server_salt
@ -596,7 +596,7 @@ export default class MTPNetworker { @@ -596,7 +596,7 @@ export default class MTPNetworker {
// this.log('parse for', message)
this.parseResponse(result).then((response) => {
if(Modes.debug) {
this.log('Server response', response);
this.log.debug('Server response', response);
}
this.processMessage(response.response, response.messageId, response.sessionId);
@ -735,7 +735,7 @@ export default class MTPNetworker { @@ -735,7 +735,7 @@ export default class MTPNetworker {
}
if(this.debug) {
this.log('pushResend:', messageId, sentMessage, this.pendingMessages, delay);
this.log.debug('pushResend:', messageId, sentMessage, this.pendingMessages, delay);
}
this.scheduleRequest(delay);
@ -1089,7 +1089,7 @@ export default class MTPNetworker { @@ -1089,7 +1089,7 @@ export default class MTPNetworker {
public sendEncryptedRequest(message: MTMessage) {
return this.getEncryptedOutput(message).then(requestData => {
this.debug && this.log('sendEncryptedRequest: launching message into space:', message, [message.msg_id].concat(message.inner || []));
this.debug && this.log.debug('sendEncryptedRequest: launching message into space:', message, [message.msg_id].concat(message.inner || []));
const promise: Promise<Uint8Array> = this.transport.send(requestData) as any;
/// #if !MTPROTO_HTTP && !MTPROTO_HTTP_UPLOAD
@ -1325,7 +1325,7 @@ export default class MTPNetworker { @@ -1325,7 +1325,7 @@ export default class MTPNetworker {
public reqResendMessage(msgId: string) {
if(this.debug) {
this.log('Req resend', msgId);
this.log.debug('Req resend', msgId);
}
this.pendingResends.push(msgId);

4
src/lib/storage.ts

@ -196,4 +196,8 @@ export default class AppStorage<Storage extends Record<string, any>/* Storage ex @@ -196,4 +196,8 @@ export default class AppStorage<Storage extends Record<string, any>/* Storage ex
}
}));
}
public deleteDatabase() {
return IDBStorage.deleteDatabase();
}
}

5
src/scss/partials/pages/_pages.scss

@ -263,3 +263,8 @@ @@ -263,3 +263,8 @@
margin-top: 1.1875rem; // * verified with mockup
}
}
#auth-pages > .scrollable, #main-columns {
opacity: 1;
transition: opacity var(--transition-standard-in);
}

Loading…
Cancel
Save