Browse Source

Calls improvements

master
Eduard Kuzmenko 3 years ago
parent
commit
c4827d50f6
  1. 5
      src/components/avatar.ts
  2. 1
      src/components/call/description.ts
  3. 28
      src/components/call/index.ts
  4. 4
      src/components/chat/patternRenderer.ts
  5. 2
      src/components/chat/reactions.ts
  6. 16
      src/components/peerTitle.ts
  7. 20
      src/components/popups/index.ts
  8. 3
      src/components/sidebarLeft/tabs/dataAndStorage.ts
  9. 25
      src/components/topbarCall.ts
  10. 5
      src/helpers/eventListenerBase.ts
  11. 4
      src/index.ts
  12. 4
      src/lib/appManagers/appAvatarsManager.ts
  13. 24
      src/lib/appManagers/appCallsManager.ts
  14. 45
      src/lib/appManagers/appImManager.ts
  15. 7
      src/lib/appManagers/appPeersManager.ts
  16. 2
      src/lib/appManagers/appReactionsManager.ts
  17. 29
      src/lib/calls/callInstance.ts
  18. 8
      src/lib/calls/callInstanceBase.ts
  19. 2
      src/lib/calls/groupCallInstance.ts
  20. 2
      src/lib/richtextprocessor.ts
  21. 5
      src/lib/rootScope.ts
  22. 16
      src/scss/partials/_avatar.scss
  23. 3
      src/scss/partials/_chatBubble.scss
  24. 26
      src/scss/partials/_chatTopbar.scss
  25. 7
      src/scss/partials/_fonts.scss
  26. 46
      src/scss/partials/popups/_call.scss

5
src/components/avatar.ts

@ -119,10 +119,11 @@ const seen: Set<PeerId> = new Set();
export default class AvatarElement extends HTMLElement { export default class AvatarElement extends HTMLElement {
private peerId: PeerId; private peerId: PeerId;
private isDialog = false; private isDialog: boolean;
private peerTitle: string; private peerTitle: string;
public loadPromises: Promise<any>[]; public loadPromises: Promise<any>[];
public lazyLoadQueue: LazyLoadQueueIntersector; public lazyLoadQueue: LazyLoadQueueIntersector;
public isBig: boolean;
private addedToQueue = false; private addedToQueue = false;
connectedCallback() { connectedCallback() {
@ -196,7 +197,7 @@ export default class AvatarElement extends HTMLElement {
} }
private r(onlyThumb = false) { private r(onlyThumb = false) {
const res = appAvatarsManager.putPhoto(this, this.peerId, this.isDialog, this.peerTitle, onlyThumb); const res = appAvatarsManager.putPhoto(this, this.peerId, this.isDialog, this.peerTitle, onlyThumb, this.isBig);
const promise = res ? res.loadPromise : Promise.resolve(); const promise = res ? res.loadPromise : Promise.resolve();
if(this.loadPromises) { if(this.loadPromises) {
if(res && res.cached) { if(res && res.cached) {

1
src/components/call/description.ts

@ -75,6 +75,7 @@ export default class CallDescriptionElement {
} }
} }
this.container.classList.toggle('has-duration', connectionState === CALL_STATE.CONNECTED);
replaceContent(this.container, element); replaceContent(this.container, element);
if(!this.container.parentElement) { if(!this.container.parentElement) {

28
src/components/call/index.ts

@ -5,6 +5,7 @@
*/ */
import IS_SCREEN_SHARING_SUPPORTED from "../../environment/screenSharingSupport"; import IS_SCREEN_SHARING_SUPPORTED from "../../environment/screenSharingSupport";
import { IS_MOBILE } from "../../environment/userAgent";
import { attachClickEvent } from "../../helpers/dom/clickEvent"; import { attachClickEvent } from "../../helpers/dom/clickEvent";
import ControlsHover from "../../helpers/dom/controlsHover"; import ControlsHover from "../../helpers/dom/controlsHover";
import findUpClassName from "../../helpers/dom/findUpClassName"; import findUpClassName from "../../helpers/dom/findUpClassName";
@ -14,6 +15,7 @@ import MovablePanel from "../../helpers/movablePanel";
import safeAssign from "../../helpers/object/safeAssign"; import safeAssign from "../../helpers/object/safeAssign";
import toggleClassName from "../../helpers/toggleClassName"; import toggleClassName from "../../helpers/toggleClassName";
import type { AppAvatarsManager } from "../../lib/appManagers/appAvatarsManager"; import type { AppAvatarsManager } from "../../lib/appManagers/appAvatarsManager";
import type { AppCallsManager } from "../../lib/appManagers/appCallsManager";
import type { AppPeersManager } from "../../lib/appManagers/appPeersManager"; import type { AppPeersManager } from "../../lib/appManagers/appPeersManager";
import CallInstance from "../../lib/calls/callInstance"; import CallInstance from "../../lib/calls/callInstance";
import CALL_STATE from "../../lib/calls/callState"; import CALL_STATE from "../../lib/calls/callState";
@ -21,6 +23,7 @@ import I18n, { i18n } from "../../lib/langPack";
import RichTextProcessor from "../../lib/richtextprocessor"; import RichTextProcessor from "../../lib/richtextprocessor";
import rootScope from "../../lib/rootScope"; import rootScope from "../../lib/rootScope";
import animationIntersector from "../animationIntersector"; import animationIntersector from "../animationIntersector";
import AvatarElement from "../avatar";
import ButtonIcon from "../buttonIcon"; import ButtonIcon from "../buttonIcon";
import GroupCallMicrophoneIconMini from "../groupCall/microphoneIconMini"; import GroupCallMicrophoneIconMini from "../groupCall/microphoneIconMini";
import { MovableState } from "../movableElement"; import { MovableState } from "../movableElement";
@ -40,6 +43,7 @@ let previousState: MovableState = {
export default class PopupCall extends PopupElement { export default class PopupCall extends PopupElement {
private instance: CallInstance; private instance: CallInstance;
private appCallsManager: AppCallsManager;
private appAvatarsManager: AppAvatarsManager; private appAvatarsManager: AppAvatarsManager;
private appPeersManager: AppPeersManager; private appPeersManager: AppPeersManager;
private peerId: PeerId; private peerId: PeerId;
@ -76,6 +80,7 @@ export default class PopupCall extends PopupElement {
private controlsHover: ControlsHover; private controlsHover: ControlsHover;
constructor(options: { constructor(options: {
appCallsManager: AppCallsManager,
appAvatarsManager: AppAvatarsManager, appAvatarsManager: AppAvatarsManager,
appPeersManager: AppPeersManager, appPeersManager: AppPeersManager,
instance: CallInstance instance: CallInstance
@ -96,8 +101,11 @@ export default class PopupCall extends PopupElement {
avatarContainer.classList.add(className + '-avatar'); avatarContainer.classList.add(className + '-avatar');
const peerId = this.peerId = this.instance.interlocutorUserId.toPeerId(); const peerId = this.peerId = this.instance.interlocutorUserId.toPeerId();
const photo = this.appPeersManager.getPeerPhoto(peerId); const avatar = new AvatarElement();
this.appAvatarsManager.putAvatar(avatarContainer, peerId, photo, 'photo_big'); avatar.isBig = true;
avatar.setAttribute('peer', '' + peerId);
avatar.classList.add('avatar-full');
avatarContainer.append(avatar);
const title = new PeerTitle({ const title = new PeerTitle({
peerId peerId
@ -113,8 +121,9 @@ export default class PopupCall extends PopupElement {
const emojisSubtitle = this.emojisSubtitle = document.createElement('div'); const emojisSubtitle = this.emojisSubtitle = document.createElement('div');
emojisSubtitle.classList.add(className + '-emojis'); emojisSubtitle.classList.add(className + '-emojis');
container.append(avatarContainer, title, subtitle, emojisSubtitle); container.append(avatarContainer, title, subtitle);
if(!IS_MOBILE) {
this.btnFullScreen = ButtonIcon('fullscreen'); this.btnFullScreen = ButtonIcon('fullscreen');
this.btnExitFullScreen = ButtonIcon('smallscreen hide'); this.btnExitFullScreen = ButtonIcon('smallscreen hide');
attachClickEvent(this.btnFullScreen, this.onFullScreenClick, {listenerSetter}); attachClickEvent(this.btnFullScreen, this.onFullScreenClick, {listenerSetter});
@ -123,12 +132,17 @@ export default class PopupCall extends PopupElement {
this.header.prepend(this.btnExitFullScreen); this.header.prepend(this.btnExitFullScreen);
this.header.append(this.btnFullScreen); this.header.append(this.btnFullScreen);
container.append(emojisSubtitle);
} else {
this.header.append(emojisSubtitle);
}
this.partyStates = document.createElement('div'); this.partyStates = document.createElement('div');
this.partyStates.classList.add(className + '-party-states'); this.partyStates.classList.add(className + '-party-states');
this.partyMutedState = document.createElement('div'); this.partyMutedState = document.createElement('div');
this.partyMutedState.classList.add(className + '-party-state'); this.partyMutedState.classList.add(className + '-party-state');
const stateText = i18n('VoipUserMicrophoneIsOff', [new PeerTitle({peerId, onlyFirstName: true}).element]); const stateText = i18n('VoipUserMicrophoneIsOff', [new PeerTitle({peerId, onlyFirstName: true, limitSymbols: 18}).element]);
stateText.classList.add(className + '-party-state-text'); stateText.classList.add(className + '-party-state-text');
const mutedIcon = new GroupCallMicrophoneIconMini(false, true); const mutedIcon = new GroupCallMicrophoneIconMini(false, true);
mutedIcon.setState(false, false); mutedIcon.setState(false, false);
@ -193,6 +207,10 @@ export default class PopupCall extends PopupElement {
this.updateInstance(); this.updateInstance();
} }
public getCallInstance() {
return this.instance;
}
private constructFirstButtons() { private constructFirstButtons() {
const buttons = this.firstButtonsRow = document.createElement('div'); const buttons = this.firstButtonsRow = document.createElement('div');
buttons.classList.add(className + '-buttons', 'is-first'); buttons.classList.add(className + '-buttons', 'is-first');
@ -260,7 +278,7 @@ export default class PopupCall extends PopupElement {
const btnAccept = this.btnAccept = this.makeButton({ const btnAccept = this.btnAccept = this.makeButton({
text: 'Call.Accept', text: 'Call.Accept',
icon: 'phone', icon: 'phone_filled',
callback: () => { callback: () => {
this.instance.acceptCall(); this.instance.acceptCall();
}, },

4
src/components/chat/patternRenderer.ts

@ -5,9 +5,9 @@
*/ */
import { IS_SAFARI } from "../../environment/userAgent"; import { IS_SAFARI } from "../../environment/userAgent";
import { indexOfAndSplice } from "../../helpers/array"; import indexOfAndSplice from "../../helpers/array/indexOfAndSplice";
import { renderImageFromUrlPromise } from "../../helpers/dom/renderImageFromUrl"; import { renderImageFromUrlPromise } from "../../helpers/dom/renderImageFromUrl";
import { deepEqual } from "../../helpers/object"; import deepEqual from "../../helpers/object/deepEqual";
type ChatBackgroundPatternRendererInitOptions = { type ChatBackgroundPatternRendererInitOptions = {
url: string, url: string,

2
src/components/chat/reactions.ts

@ -4,7 +4,7 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE * https://github.com/morethanwords/tweb/blob/master/LICENSE
*/ */
import { forEachReverse } from "../../helpers/array"; import forEachReverse from "../../helpers/array/forEachReverse";
import positionElementByIndex from "../../helpers/dom/positionElementByIndex"; import positionElementByIndex from "../../helpers/dom/positionElementByIndex";
import { Message, ReactionCount } from "../../layer"; import { Message, ReactionCount } from "../../layer";
import appReactionsManager from "../../lib/appManagers/appReactionsManager"; import appReactionsManager from "../../lib/appManagers/appReactionsManager";

16
src/components/peerTitle.ts

@ -12,13 +12,15 @@ import replaceContent from "../helpers/dom/replaceContent";
import appUsersManager from "../lib/appManagers/appUsersManager"; import appUsersManager from "../lib/appManagers/appUsersManager";
import RichTextProcessor from "../lib/richtextprocessor"; import RichTextProcessor from "../lib/richtextprocessor";
import { NULL_PEER_ID } from "../lib/mtproto/mtproto_config"; import { NULL_PEER_ID } from "../lib/mtproto/mtproto_config";
import { limitSymbols } from "../helpers/string";
export type PeerTitleOptions = { export type PeerTitleOptions = {
peerId?: PeerId, peerId?: PeerId,
fromName?: string, fromName?: string,
plainText?: boolean, plainText?: boolean,
onlyFirstName?: boolean, onlyFirstName?: boolean,
dialog?: boolean dialog?: boolean,
limitSymbols?: number
}; };
const weakMap: WeakMap<HTMLElement, PeerTitle> = new WeakMap(); const weakMap: WeakMap<HTMLElement, PeerTitle> = new WeakMap();
@ -44,6 +46,7 @@ export default class PeerTitle {
public plainText = false; public plainText = false;
public onlyFirstName = false; public onlyFirstName = false;
public dialog = false; public dialog = false;
public limitSymbols: number;
constructor(options: PeerTitleOptions) { constructor(options: PeerTitleOptions) {
this.element = document.createElement('span'); this.element = document.createElement('span');
@ -64,8 +67,13 @@ export default class PeerTitle {
} }
} }
if(this.fromName !== undefined) { let fromName = this.fromName;
this.element.innerHTML = RichTextProcessor.wrapEmojiText(this.fromName); if(fromName !== undefined) {
if(this.limitSymbols !== undefined) {
fromName = limitSymbols(fromName, this.limitSymbols, this.limitSymbols);
}
this.element.innerHTML = RichTextProcessor.wrapEmojiText(fromName);
return; return;
} }
@ -77,7 +85,7 @@ export default class PeerTitle {
if(this.peerId.isUser() && appUsersManager.getUser(this.peerId).pFlags.deleted) { if(this.peerId.isUser() && appUsersManager.getUser(this.peerId).pFlags.deleted) {
replaceContent(this.element, i18n(this.onlyFirstName ? 'Deleted' : 'HiddenName')); replaceContent(this.element, i18n(this.onlyFirstName ? 'Deleted' : 'HiddenName'));
} else { } else {
this.element.innerHTML = appPeersManager.getPeerTitle(this.peerId, this.plainText, this.onlyFirstName); this.element.innerHTML = appPeersManager.getPeerTitle(this.peerId, this.plainText, this.onlyFirstName, this.limitSymbols);
} }
} else { } else {
replaceContent(this.element, i18n(this.onlyFirstName ? 'Saved' : 'SavedMessages')); replaceContent(this.element, i18n(this.onlyFirstName ? 'Saved' : 'SavedMessages'));

20
src/components/popups/index.ts

@ -15,7 +15,7 @@ import ListenerSetter from "../../helpers/listenerSetter";
import { attachClickEvent, simulateClickEvent } from "../../helpers/dom/clickEvent"; import { attachClickEvent, simulateClickEvent } from "../../helpers/dom/clickEvent";
import isSendShortcutPressed from "../../helpers/dom/isSendShortcutPressed"; import isSendShortcutPressed from "../../helpers/dom/isSendShortcutPressed";
import { cancelEvent } from "../../helpers/dom/cancelEvent"; import { cancelEvent } from "../../helpers/dom/cancelEvent";
import EventListenerBase from "../../helpers/eventListenerBase"; import EventListenerBase, { EventListenerListeners } from "../../helpers/eventListenerBase";
import { addFullScreenListener, getFullScreenElement } from "../../helpers/dom/fullScreen"; import { addFullScreenListener, getFullScreenElement } from "../../helpers/dom/fullScreen";
import indexOfAndSplice from "../../helpers/array/indexOfAndSplice"; import indexOfAndSplice from "../../helpers/array/indexOfAndSplice";
@ -52,11 +52,13 @@ const onFullScreenChange = () => {
addFullScreenListener(DEFAULT_APPEND_TO, onFullScreenChange); addFullScreenListener(DEFAULT_APPEND_TO, onFullScreenChange);
export default class PopupElement extends EventListenerBase<{ type PopupListeners = {
close: () => void, close: () => void,
closeAfterTimeout: () => void closeAfterTimeout: () => void
}> { };
private static POPUPS: PopupElement[] = [];
export default class PopupElement<T extends EventListenerListeners = {}> extends EventListenerBase<PopupListeners & T> {
private static POPUPS: PopupElement<any>[] = [];
protected element = document.createElement('div'); protected element = document.createElement('div');
protected container = document.createElement('div'); protected container = document.createElement('div');
protected header = document.createElement('div'); protected header = document.createElement('div');
@ -181,7 +183,7 @@ export default class PopupElement extends EventListenerBase<{
public show() { public show() {
this.navigationItem = { this.navigationItem = {
type: 'popup', type: 'popup',
onPop: this.destroy, onPop: () => this.destroy(),
onEscape: this.onEscape onEscape: this.onEscape
}; };
@ -214,8 +216,8 @@ export default class PopupElement extends EventListenerBase<{
appNavigationController.backByItem(this.navigationItem); appNavigationController.backByItem(this.navigationItem);
}; };
private destroy = () => { protected destroy() {
this.dispatchEvent('close'); this.dispatchEvent<PopupListeners>('close');
this.element.classList.add('hiding'); this.element.classList.add('hiding');
this.element.classList.remove('active'); this.element.classList.remove('active');
this.listenerSetter.removeAll(); this.listenerSetter.removeAll();
@ -234,14 +236,14 @@ export default class PopupElement extends EventListenerBase<{
setTimeout(() => { setTimeout(() => {
this.element.remove(); this.element.remove();
this.dispatchEvent('closeAfterTimeout'); this.dispatchEvent<PopupListeners>('closeAfterTimeout');
this.cleanup(); this.cleanup();
if(!this.withoutOverlay) { if(!this.withoutOverlay) {
animationIntersector.checkAnimations(false); animationIntersector.checkAnimations(false);
} }
}, 150); }, 150);
}; }
public static reAppend() { public static reAppend() {
this.POPUPS.forEach(popup => { this.POPUPS.forEach(popup => {

3
src/components/sidebarLeft/tabs/dataAndStorage.ts

@ -9,7 +9,8 @@ import { attachClickEvent } from "../../../helpers/dom/clickEvent";
import replaceContent from "../../../helpers/dom/replaceContent"; import replaceContent from "../../../helpers/dom/replaceContent";
import toggleDisability from "../../../helpers/dom/toggleDisability"; import toggleDisability from "../../../helpers/dom/toggleDisability";
import formatBytes from "../../../helpers/formatBytes"; import formatBytes from "../../../helpers/formatBytes";
import { copy, deepEqual } from "../../../helpers/object"; import copy from "../../../helpers/object/copy";
import deepEqual from "../../../helpers/object/deepEqual";
import appStateManager, { AutoDownloadPeerTypeSettings, STATE_INIT } from "../../../lib/appManagers/appStateManager"; import appStateManager, { AutoDownloadPeerTypeSettings, STATE_INIT } from "../../../lib/appManagers/appStateManager";
import { FormatterArguments, i18n, join, LangPackKey } from "../../../lib/langPack"; import { FormatterArguments, i18n, join, LangPackKey } from "../../../lib/langPack";
import rootScope from "../../../lib/rootScope"; import rootScope from "../../../lib/rootScope";

25
src/components/topbarCall.ts

@ -29,6 +29,7 @@ import PopupCall from "./call";
import type { AppAvatarsManager } from "../lib/appManagers/appAvatarsManager"; import type { AppAvatarsManager } from "../lib/appManagers/appAvatarsManager";
import GroupCallMicrophoneIconMini from "./groupCall/microphoneIconMini"; import GroupCallMicrophoneIconMini from "./groupCall/microphoneIconMini";
import CallInstance from "../lib/calls/callInstance"; import CallInstance from "../lib/calls/callInstance";
import type { AppCallsManager } from "../lib/appManagers/appCallsManager";
function convertCallStateToGroupState(state: CALL_STATE, isMuted: boolean) { function convertCallStateToGroupState(state: CALL_STATE, isMuted: boolean) {
switch(state) { switch(state) {
@ -64,11 +65,18 @@ export default class TopbarCall {
private appPeersManager: AppPeersManager, private appPeersManager: AppPeersManager,
private appChatsManager: AppChatsManager, private appChatsManager: AppChatsManager,
private appAvatarsManager: AppAvatarsManager, private appAvatarsManager: AppAvatarsManager,
private appCallsManager: AppCallsManager
) { ) {
const listenerSetter = this.listenerSetter = new ListenerSetter(); const listenerSetter = this.listenerSetter = new ListenerSetter();
listenerSetter.add(rootScope)('call_instance', ({instance, hasCurrent}) => { listenerSetter.add(rootScope)('call_instance', ({instance}) => {
if(!hasCurrent) { if(!this.instance) {
this.updateInstance(instance);
}
});
listenerSetter.add(rootScope)('call_accepting', (instance) => {
if(this.instance !== instance) {
this.updateInstance(instance); this.updateInstance(instance);
} }
}); });
@ -121,7 +129,8 @@ export default class TopbarCall {
this.construct = undefined; this.construct = undefined;
} }
if(this.instance !== instance) { const isChangingInstance = this.instance !== instance;
if(isChangingInstance) {
this.clearCurrentInstance(); this.clearCurrentInstance();
this.instance = instance; this.instance = instance;
@ -135,6 +144,8 @@ export default class TopbarCall {
this.currentDescription = this.callDescription; this.currentDescription = this.callDescription;
this.instanceListenerSetter.add(instance)('muted', this.onState); this.instanceListenerSetter.add(instance)('muted', this.onState);
} }
this.container.classList.toggle('is-call', !(instance instanceof GroupCallInstance));
} }
const isMuted = this.instance.isMuted; const isMuted = this.instance.isMuted;
@ -145,7 +156,7 @@ export default class TopbarCall {
weave.componentDidMount(); weave.componentDidMount();
const isClosed = state === GROUP_CALL_STATE.CLOSED; const isClosed = state === GROUP_CALL_STATE.CLOSED;
if(!document.body.classList.contains('is-calling') || isClosed) { if((!document.body.classList.contains('is-calling') || isChangingInstance) || isClosed) {
if(isClosed) { if(isClosed) {
weave.setAmplitude(0); weave.setAmplitude(0);
} }
@ -257,7 +268,13 @@ export default class TopbarCall {
appChatsManager: this.appChatsManager appChatsManager: this.appChatsManager
}).show(); }).show();
} else if(this.instance instanceof CallInstance) { } else if(this.instance instanceof CallInstance) {
const hasPopup = PopupElement.getPopup(PopupCall) as PopupCall;
if(hasPopup && hasPopup.getCallInstance() === this.instance) {
return;
}
new PopupCall({ new PopupCall({
appCallsManager: this.appCallsManager,
appAvatarsManager: this.appAvatarsManager, appAvatarsManager: this.appAvatarsManager,
appPeersManager: this.appPeersManager, appPeersManager: this.appPeersManager,
instance: this.instance instance: this.instance

5
src/helpers/eventListenerBase.ts

@ -50,6 +50,8 @@ import type { ArgumentTypes, SuperReturnType } from "../types";
// MOUNT_CLASS_TO.e = e; // MOUNT_CLASS_TO.e = e;
export type EventListenerListeners = Record<string, Function>; export type EventListenerListeners = Record<string, Function>;
// export type EventListenerListeners = Record<string, (...args: any[]) => any>;
// export type EventListenerListeners = {[name in string]: Function};
/** /**
* Better not to remove listeners during setting * Better not to remove listeners during setting
@ -151,7 +153,8 @@ export default class EventListenerBase<Listeners extends EventListenerListeners>
} }
// * must be protected, but who cares // * must be protected, but who cares
public dispatchEvent<T extends keyof Listeners>(name: T, ...args: ArgumentTypes<Listeners[T]>) { public dispatchEvent<L extends EventListenerListeners = Listeners, T extends keyof L = keyof L>(name: T, ...args: ArgumentTypes<L[T]>) {
// @ts-ignore
this._dispatchEvent(name, false, ...args); this._dispatchEvent(name, false, ...args);
} }

4
src/index.ts

@ -170,6 +170,10 @@ console.timeEnd('get storage1'); */
document.documentElement.classList.add('is-firefox'); document.documentElement.classList.add('is-firefox');
} }
if(userAgent.IS_MOBILE) {
document.documentElement.classList.add('is-mobile');
}
if(userAgent.IS_APPLE) { if(userAgent.IS_APPLE) {
if(userAgent.IS_SAFARI) { if(userAgent.IS_SAFARI) {
document.documentElement.classList.add('is-safari'); document.documentElement.classList.add('is-safari');

4
src/lib/appManagers/appAvatarsManager.ts

@ -169,7 +169,7 @@ export class AppAvatarsManager {
} }
// peerId === peerId || title // peerId === peerId || title
public putPhoto(div: HTMLElement, peerId: PeerId, isDialog = false, title = '', onlyThumb = false) { public putPhoto(div: HTMLElement, peerId: PeerId, isDialog = false, title = '', onlyThumb = false, isBig?: boolean) {
const myId = rootScope.myId; const myId = rootScope.myId;
//console.log('loadDialogPhoto location:', location, inputPeer); //console.log('loadDialogPhoto location:', location, inputPeer);
@ -213,7 +213,7 @@ export class AppAvatarsManager {
} }
if(avatarAvailable/* && false */) { if(avatarAvailable/* && false */) {
const size: PeerPhotoSize = 'photo_small'; const size: PeerPhotoSize = isBig ? 'photo_big' : 'photo_small';
return this.putAvatar(div, peerId, photo, size, undefined, onlyThumb); return this.putAvatar(div, peerId, photo, size, undefined, onlyThumb);
} }
} }

24
src/lib/appManagers/appCallsManager.ts

@ -11,6 +11,8 @@
import { MOUNT_CLASS_TO } from "../../config/debug"; import { MOUNT_CLASS_TO } from "../../config/debug";
import IS_CALL_SUPPORTED from "../../environment/callSupport"; import IS_CALL_SUPPORTED from "../../environment/callSupport";
import indexOfAndSplice from "../../helpers/array/indexOfAndSplice";
import insertInDescendSortedArray from "../../helpers/array/insertInDescendSortedArray";
import AudioAssetPlayer from "../../helpers/audioAssetPlayer"; import AudioAssetPlayer from "../../helpers/audioAssetPlayer";
import bytesCmp from "../../helpers/bytes/bytesCmp"; import bytesCmp from "../../helpers/bytes/bytesCmp";
import safeReplaceObject from "../../helpers/object/safeReplaceObject"; import safeReplaceObject from "../../helpers/object/safeReplaceObject";
@ -39,6 +41,7 @@ export class AppCallsManager {
private log: ReturnType<typeof logger>; private log: ReturnType<typeof logger>;
private calls: Map<CallId, MyPhoneCall>; private calls: Map<CallId, MyPhoneCall>;
private instances: Map<CallId, CallInstance>; private instances: Map<CallId, CallInstance>;
private sortedInstances: Array<CallInstance>;
private tempId: number; private tempId: number;
private audioAsset: AudioAssetPlayer<CallAudioAssetName>; private audioAsset: AudioAssetPlayer<CallAudioAssetName>;
@ -48,6 +51,7 @@ export class AppCallsManager {
this.tempId = 0; this.tempId = 0;
this.calls = new Map(); this.calls = new Map();
this.instances = new Map(); this.instances = new Map();
this.sortedInstances = [];
if(!IS_CALL_SUPPORTED) { if(!IS_CALL_SUPPORTED) {
return; return;
@ -139,15 +143,7 @@ export class AppCallsManager {
} }
public get currentCall() { public get currentCall() {
let lastInstance: CallInstance; return this.sortedInstances[0];
for(const [callId, instance] of this.instances) {
lastInstance = instance;
if(instance.connectionState !== CALL_STATE.PENDING) {
break;
}
}
return lastInstance;
} }
public getCallByUserId(userId: UserId) { public getCallByUserId(userId: UserId) {
@ -207,15 +203,17 @@ export class AppCallsManager {
...options, ...options,
}); });
let wasTryingToJoin = false;
call.addEventListener('state', (state) => { call.addEventListener('state', (state) => {
const currentCall = this.currentCall; const currentCall = this.currentCall;
if(state === CALL_STATE.CLOSED) { if(state === CALL_STATE.CLOSED) {
this.instances.delete(call.id); this.instances.delete(call.id);
indexOfAndSplice(this.sortedInstances, call);
} else {
insertInDescendSortedArray(this.sortedInstances, call, 'sortIndex');
} }
if(state === CALL_STATE.EXCHANGING_KEYS) { if(state === CALL_STATE.EXCHANGING_KEYS) {
wasTryingToJoin = true; call.wasTryingToJoin = true;
} }
const hasConnected = call.connectedAt !== undefined; const hasConnected = call.connectedAt !== undefined;
@ -227,9 +225,9 @@ export class AppCallsManager {
if(currentCall === call || !currentCall) { if(currentCall === call || !currentCall) {
if(state === CALL_STATE.CLOSED) { if(state === CALL_STATE.CLOSED) {
if(!call.isOutgoing && !wasTryingToJoin) { // incoming call has been accepted on other device or ended if(!call.isOutgoing && !call.wasTryingToJoin) { // incoming call has been accepted on other device or ended
this.audioAsset.stopSound(); this.audioAsset.stopSound();
} else if(wasTryingToJoin && !hasConnected) { // something has happened during the key exchanging } else if(call.wasTryingToJoin && !hasConnected) { // something has happened during the key exchanging
this.audioAsset.playSound('voip_failed.mp3'); this.audioAsset.playSound('voip_failed.mp3');
} else { } else {
this.audioAsset.playSound(call.discardReason === 'phoneCallDiscardReasonBusy' ? 'call_busy.mp3' : 'call_end.mp3'); this.audioAsset.playSound(call.discardReason === 'phoneCallDiscardReasonBusy' ? 'call_busy.mp3' : 'call_end.mp3');

45
src/lib/appManagers/appImManager.ts

@ -87,6 +87,8 @@ import appReactionsManager from './appReactionsManager';
import PopupCall from '../../components/call'; import PopupCall from '../../components/call';
import copy from '../../helpers/object/copy'; import copy from '../../helpers/object/copy';
import getObjectKeysAndSort from '../../helpers/object/getObjectKeysAndSort'; import getObjectKeysAndSort from '../../helpers/object/getObjectKeysAndSort';
import type GroupCallInstance from '../calls/groupCallInstance';
import type CallInstance from '../calls/callInstance';
//console.log('appImManager included33!'); //console.log('appImManager included33!');
@ -341,20 +343,39 @@ export class AppImManager {
}); });
if(IS_CALL_SUPPORTED || IS_GROUP_CALL_SUPPORTED) { if(IS_CALL_SUPPORTED || IS_GROUP_CALL_SUPPORTED) {
this.topbarCall = new TopbarCall(appGroupCallsManager, appPeersManager, appChatsManager, appAvatarsManager); this.topbarCall = new TopbarCall(appGroupCallsManager, appPeersManager, appChatsManager, appAvatarsManager, appCallsManager);
} }
if(IS_CALL_SUPPORTED) { if(IS_CALL_SUPPORTED) {
rootScope.addEventListener('call_instance', ({instance, hasCurrent}) => { rootScope.addEventListener('call_instance', ({instance/* , hasCurrent */}) => {
if(hasCurrent) { // if(hasCurrent) {
return; // return;
} // }
new PopupCall({ const popup = new PopupCall({
appCallsManager,
appAvatarsManager, appAvatarsManager,
appPeersManager, appPeersManager,
instance instance
}).show(); });
instance.addEventListener('acceptCallOverride', () => {
return this.discardCurrentCall(instance.interlocutorUserId.toPeerId(), undefined, instance)
.then(() => {
rootScope.dispatchEvent('call_accepting', instance);
return true;
})
.catch(() => false);
});
popup.addEventListener('close', () => {
const currentCall = appCallsManager.currentCall;
if(currentCall && currentCall !== instance && !instance.wasTryingToJoin) {
instance.hangUp('phoneCallDiscardReasonBusy');
}
}, {once: true});
popup.show();
}); });
} }
@ -944,9 +965,9 @@ export class AppImManager {
appCallsManager.startCallInternal(userId, type === 'video'); appCallsManager.startCallInternal(userId, type === 'video');
} }
private discardCurrentCall(toPeerId: PeerId) { private discardCurrentCall(toPeerId: PeerId, ignoreGroupCall?: GroupCallInstance, ignoreCall?: CallInstance) {
if(appCallsManager.currentCall) return this.discardCallConfirmation(toPeerId); if(appGroupCallsManager.groupCall && appGroupCallsManager.groupCall !== ignoreGroupCall) return this.discardGroupCallConfirmation(toPeerId);
else if(appGroupCallsManager.groupCall) return this.discardGroupCallConfirmation(toPeerId); else if(appCallsManager.currentCall && appCallsManager.currentCall !== ignoreCall) return this.discardCallConfirmation(toPeerId);
else return Promise.resolve(); else return Promise.resolve();
} }
@ -965,8 +986,8 @@ export class AppImManager {
} }
}); });
if(appCallsManager.currentCall === currentCall) { if(!currentCall.isClosing) {
await currentCall.hangUp(); await currentCall.hangUp('phoneCallDiscardReasonDisconnect');
} }
} }
} }

7
src/lib/appManagers/appPeersManager.ts

@ -20,6 +20,7 @@ import I18n from '../langPack';
import { NULL_PEER_ID } from "../mtproto/mtproto_config"; import { NULL_PEER_ID } from "../mtproto/mtproto_config";
import { getRestrictionReason } from "../../helpers/restrictions"; import { getRestrictionReason } from "../../helpers/restrictions";
import isObject from "../../helpers/object/isObject"; import isObject from "../../helpers/object/isObject";
import { limitSymbols } from "../../helpers/string";
// https://github.com/eelcohn/Telegram-API/wiki/Calculating-color-for-a-Telegram-user-on-IRC // https://github.com/eelcohn/Telegram-API/wiki/Calculating-color-for-a-Telegram-user-on-IRC
/* /*
@ -73,7 +74,7 @@ export class AppPeersManager {
return false; return false;
} }
public getPeerTitle(peerId: PeerId, plainText = false, onlyFirstName = false) { public getPeerTitle(peerId: PeerId, plainText = false, onlyFirstName = false, _limitSymbols?: number) {
if(!peerId) { if(!peerId) {
peerId = rootScope.myId; peerId = rootScope.myId;
} }
@ -95,6 +96,10 @@ export class AppPeersManager {
} }
} }
if(_limitSymbols !== undefined) {
title = limitSymbols(title, _limitSymbols, _limitSymbols);
}
return plainText ? title : RichTextProcessor.wrapEmojiText(title); return plainText ? title : RichTextProcessor.wrapEmojiText(title);
} }

2
src/lib/appManagers/appReactionsManager.ts

@ -8,7 +8,7 @@ import { MOUNT_CLASS_TO } from "../../config/debug";
import assumeType from "../../helpers/assumeType"; import assumeType from "../../helpers/assumeType";
import callbackify from "../../helpers/callbackify"; import callbackify from "../../helpers/callbackify";
import callbackifyAll from "../../helpers/callbackifyAll"; import callbackifyAll from "../../helpers/callbackifyAll";
import { copy } from "../../helpers/object"; import copy from "../../helpers/object/copy";
import { AvailableReaction, Message, MessagePeerReaction, MessagesAvailableReactions, Update, Updates } from "../../layer"; import { AvailableReaction, Message, MessagePeerReaction, MessagesAvailableReactions, Update, Updates } from "../../layer";
import apiManager from "../mtproto/mtprotoworker"; import apiManager from "../mtproto/mtprotoworker";
import { ReferenceContext } from "../mtproto/referenceDatabase"; import { ReferenceContext } from "../mtproto/referenceDatabase";

29
src/lib/calls/callInstance.ts

@ -34,7 +34,8 @@ export default class CallInstance extends CallInstanceBase<{
state: (state: CALL_STATE) => void, state: (state: CALL_STATE) => void,
id: (id: CallId, prevId: CallId) => void, id: (id: CallId, prevId: CallId) => void,
muted: (muted: boolean) => void, muted: (muted: boolean) => void,
mediaState: (mediaState: CallMediaState) => void mediaState: (mediaState: CallMediaState) => void,
acceptCallOverride: () => Promise<boolean>,
}> { }> {
public dh: Partial<DiffieHellmanInfo.a & DiffieHellmanInfo.b>; public dh: Partial<DiffieHellmanInfo.a & DiffieHellmanInfo.b>;
public id: CallId; public id: CallId;
@ -55,6 +56,7 @@ export default class CallInstance extends CallInstanceBase<{
public release: () => Promise<void>; public release: () => Promise<void>;
public _connectionState: CALL_STATE; public _connectionState: CALL_STATE;
public createdAt: number;
public connectedAt: number; public connectedAt: number;
public discardReason: string; public discardReason: string;
@ -78,6 +80,9 @@ export default class CallInstance extends CallInstanceBase<{
private wasStartingScreen: boolean; private wasStartingScreen: boolean;
private wasStartingVideo: boolean; private wasStartingVideo: boolean;
public wasTryingToJoin: boolean;
public streamManager: StreamManager;
constructor(options: { constructor(options: {
isOutgoing: boolean, isOutgoing: boolean,
@ -97,6 +102,7 @@ export default class CallInstance extends CallInstanceBase<{
safeAssign(this, options); safeAssign(this, options);
this.createdAt = Date.now();
this.offerReceived = false; this.offerReceived = false;
this.offerSent = false; this.offerSent = false;
this.decryptQueue = []; this.decryptQueue = [];
@ -110,7 +116,7 @@ export default class CallInstance extends CallInstanceBase<{
} }
}); });
const streamManager = new StreamManager(GROUP_CALL_AMPLITUDE_ANALYSE_INTERVAL_MS); const streamManager = this.streamManager = new StreamManager(GROUP_CALL_AMPLITUDE_ANALYSE_INTERVAL_MS);
streamManager.direction = 'sendrecv'; streamManager.direction = 'sendrecv';
streamManager.types.push('screencast'); streamManager.types.push('screencast');
if(!this.isOutgoing) { if(!this.isOutgoing) {
@ -164,6 +170,14 @@ export default class CallInstance extends CallInstanceBase<{
} }
} }
get sortIndex() {
const connectionState = this.connectionState;
const state = CALL_STATE.CLOSED - connectionState + 1;
let index = state * 10000000000000;
index += 2147483647000 - (connectionState === CALL_STATE.PENDING && this.isOutgoing ? 0 : this.createdAt);
return index;
}
public getVideoElement(type: CallMediaState['type']) { public getVideoElement(type: CallMediaState['type']) {
if(type === 'input') return this.elements.get('main'); if(type === 'input') return this.elements.get('main');
else { else {
@ -283,10 +297,6 @@ export default class CallInstance extends CallInstanceBase<{
return connectionState === CALL_STATE.CLOSING || connectionState === CALL_STATE.CLOSED; return connectionState === CALL_STATE.CLOSING || connectionState === CALL_STATE.CLOSED;
} }
public get streamManager(): StreamManager {
return this.connectionInstance?.streamManager;
}
public get description(): localConferenceDescription { public get description(): localConferenceDescription {
return this.connectionInstance?.description; return this.connectionInstance?.description;
} }
@ -318,6 +328,11 @@ export default class CallInstance extends CallInstanceBase<{
} }
public async acceptCall() { public async acceptCall() {
const canAccept = (await Promise.all(this.dispatchResultableEvent('acceptCallOverride')))[0] ?? true;
if(this.isClosing || !canAccept) {
return;
}
// this.clearHangUpTimeout(); // this.clearHangUpTimeout();
this.overrideConnectionState(CALL_STATE.EXCHANGING_KEYS); this.overrideConnectionState(CALL_STATE.EXCHANGING_KEYS);
@ -619,7 +634,7 @@ export default class CallInstance extends CallInstanceBase<{
} }
public async hangUp(discardReason?: PhoneCallDiscardReason['_'], discardedByOtherParty?: boolean) { public async hangUp(discardReason?: PhoneCallDiscardReason['_'], discardedByOtherParty?: boolean) {
if(this.connectionState === CALL_STATE.CLOSED) { if(this.isClosing) {
return; return;
} }

8
src/lib/calls/callInstanceBase.ts

@ -11,6 +11,7 @@ import getAudioConstraints from "./helpers/getAudioConstraints";
import getScreenConstraints from "./helpers/getScreenConstraints"; import getScreenConstraints from "./helpers/getScreenConstraints";
import getStreamCached from "./helpers/getStreamCached"; import getStreamCached from "./helpers/getStreamCached";
import getVideoConstraints from "./helpers/getVideoConstraints"; import getVideoConstraints from "./helpers/getVideoConstraints";
import stopTrack from "./helpers/stopTrack";
import LocalConferenceDescription from "./localConferenceDescription"; import LocalConferenceDescription from "./localConferenceDescription";
import StreamManager, { StreamItem } from "./streamManager"; import StreamManager, { StreamItem } from "./streamManager";
@ -190,6 +191,9 @@ export default abstract class CallInstanceBase<E extends EventListenerListeners>
if(!isVideo) { if(!isVideo) {
player.appendChild(element); player.appendChild(element);
} else {
element.setAttribute('playsinline', 'true');
element.muted = true;
} }
// audio.play(); // audio.play();
@ -229,6 +233,10 @@ export default abstract class CallInstanceBase<E extends EventListenerListeners>
if(description) { if(description) {
streamManager.appendToConference(description); streamManager.appendToConference(description);
} }
} else { // if call is declined earlier than stream appears
stream.getTracks().forEach(track => {
stopTrack(track);
});
} }
} }
} }

2
src/lib/calls/groupCallInstance.ts

@ -183,8 +183,6 @@ export default class GroupCallInstance extends CallInstanceBase<{
const clone = element.cloneNode() as typeof element; const clone = element.cloneNode() as typeof element;
clone.srcObject = element.srcObject; clone.srcObject = element.srcObject;
clone.setAttribute('playsinline', 'true');
clone.muted = true;
return {video: clone, source}; return {video: clone, source};
} }

2
src/lib/richtextprocessor.ts

@ -18,7 +18,7 @@ import { encodeEntities } from '../helpers/string';
import { IS_SAFARI } from '../environment/userAgent'; import { IS_SAFARI } from '../environment/userAgent';
import { MOUNT_CLASS_TO } from '../config/debug'; import { MOUNT_CLASS_TO } from '../config/debug';
import IS_EMOJI_SUPPORTED from '../environment/emojiSupport'; import IS_EMOJI_SUPPORTED from '../environment/emojiSupport';
import { copy } from '../helpers/object'; import copy from '../helpers/object/copy';
const EmojiHelper = { const EmojiHelper = {
emojiMap: (code: string) => { return code; }, emojiMap: (code: string) => { return code; },

5
src/lib/rootScope.ts

@ -17,7 +17,7 @@ import type { PushNotificationObject } from "./serviceWorker/push";
import type { ConnectionStatusChange } from "./mtproto/connectionStatus"; import type { ConnectionStatusChange } from "./mtproto/connectionStatus";
import type { GroupCallId } from "./appManagers/appGroupCallsManager"; import type { GroupCallId } from "./appManagers/appGroupCallsManager";
import type GroupCallInstance from "./calls/groupCallInstance"; import type GroupCallInstance from "./calls/groupCallInstance";
// import type CallInstance from "./calls/callInstance"; import type CallInstance from "./calls/callInstance";
import type { StreamAmplitude } from "./calls/streamManager"; import type { StreamAmplitude } from "./calls/streamManager";
import type Chat from "../components/chat/chat"; import type Chat from "../components/chat/chat";
import { NULL_PEER_ID, UserAuth } from "./mtproto/mtproto_config"; import { NULL_PEER_ID, UserAuth } from "./mtproto/mtproto_config";
@ -158,7 +158,8 @@ export type BroadcastEvents = {
'group_call_participant': {groupCallId: GroupCallId, participant: GroupCallParticipant}, 'group_call_participant': {groupCallId: GroupCallId, participant: GroupCallParticipant},
// 'group_call_video_track_added': {instance: GroupCallInstance} // 'group_call_video_track_added': {instance: GroupCallInstance}
'call_instance': {hasCurrent: boolean, instance: any/* CallInstance */}, 'call_instance': {hasCurrent: boolean, instance: CallInstance},
'call_accepting': CallInstance, // это костыль. используется при параллельном вызове, чтобы заменить звонок в topbarCall
'quick_reaction': string, 'quick_reaction': string,

16
src/scss/partials/_avatar.scss

@ -232,3 +232,19 @@ avatar-element {
left: 0; left: 0;
} }
} }
.avatar-full {
position: absolute;
width: 100%;
height: 100%;
border-radius: inherit;
display: inline-flex;
align-items: center;
justify-content: center;
.avatar-photo {
width: 100% !important;
height: 100% !important;
object-fit: cover;
}
}

3
src/scss/partials/_chatBubble.scss

@ -1383,12 +1383,13 @@ $bubble-beside-button-width: 38px;
} }
&-subtitle { &-subtitle {
font-size: .875rem; font-size: var(--messages-secondary-text-size);
color: var(--secondary-text-color); color: var(--secondary-text-color);
display: flex; display: flex;
align-items: center; align-items: center;
margin-top: .0625rem; margin-top: .0625rem;
margin-left: -.1875rem; margin-left: -.1875rem;
line-height: var(--messages-secondary-line-height);
&.is-reason:before { &.is-reason:before {
margin-right: .0625rem; margin-right: .0625rem;

26
src/scss/partials/_chatTopbar.scss

@ -239,6 +239,7 @@ body.is-right-column-shown {
height: 3.5rem; height: 3.5rem;
max-height: 3.5rem; max-height: 3.5rem;
flex: 1 1 auto; flex: 1 1 auto;
max-width: 100%;
} }
} }
@ -360,19 +361,33 @@ body.is-right-column-shown {
white-space: nowrap; white-space: nowrap;
} }
&:not(.is-call) {
@media only screen and (max-width: 480px) { @media only screen and (max-width: 480px) {
.topbar-call-left, .topbar-call-left,
.topbar-call-right { .topbar-call-right {
width: auto; width: auto;
} }
.group-call-description, .group-call-description {
.call-description {
display: none; display: none;
} }
} }
} }
&.is-call {
@media only screen and (max-width: 480px) {
.topbar-call-left,
.topbar-call-right {
width: 6.25rem;
}
.call-description:not(.has-duration) {
display: none;
}
}
}
}
&-left, &-left,
&-right { &-right {
flex: 0 0 auto; flex: 0 0 auto;
@ -399,6 +414,13 @@ body.is-right-column-shown {
&-center { &-center {
@include sidebar-transform(true); @include sidebar-transform(true);
@include text-overflow(); @include text-overflow();
@include respond-to(medium-screens) {
// ! it flicks over the left side :(
// body.is-right-column-shown & {
padding: 0 calc(var(--right-column-width) / 2);
// }
}
} }
&-right { &-right {

7
src/scss/partials/_fonts.scss

@ -37,6 +37,13 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.tgico-phone_filled {
&:before {
content: $tgico-endcall_filled;
transform: rotate(-135deg);
}
}
.tgico-check { .tgico-check {
&:before { &:before {
content: $tgico-check; content: $tgico-check;

46
src/scss/partials/popups/_call.scss

@ -24,10 +24,15 @@
color: #fff; color: #fff;
align-items: center; align-items: center;
&.is-full-screen { &.is-full-screen,
html.is-mobile & {
border-radius: 0; border-radius: 0;
} }
&.is-full-screen:not(.show-controls) {
cursor: none;
}
&.show-controls, &.show-controls,
&.no-video { &.no-video {
.call-title, .call-title,
@ -41,10 +46,25 @@
} }
} }
&.show-controls {
.call-video {
opacity: .8;
}
.call-video-blur {
opacity: .56;
}
}
.popup-header { .popup-header {
.btn-icon { .btn-icon {
color: #fff; color: #fff;
} }
.call-emojis {
transform: scale(1.3125);
margin-right: 1rem;
}
} }
&-avatar { &-avatar {
@ -55,11 +75,10 @@
left: 0; left: 0;
z-index: -1; z-index: -1;
opacity: .7; opacity: .7;
border-radius: inherit;
.avatar-photo { .avatar-full {
width: 100%; font-size: 6rem;
height: 100%;
object-fit: cover;
} }
} }
@ -182,6 +201,7 @@
object-fit: contain; object-fit: contain;
position: absolute; position: absolute;
border-radius: inherit; border-radius: inherit;
opacity: 1;
&-container { &-container {
position: absolute; position: absolute;
@ -212,6 +232,13 @@
opacity: .7; opacity: .7;
border-radius: inherit; border-radius: inherit;
} }
&,
&-blur {
@include animation-level(2) {
transition: opacity var(--transition-standard-in);
}
}
} }
// html.emoji-supported & { // html.emoji-supported & {
@ -234,6 +261,7 @@
z-index: 2; z-index: 2;
width: 100%; width: 100%;
align-items: center; align-items: center;
padding: 0 1rem;
} }
&-party-state { &-party-state {
@ -252,7 +280,6 @@
opacity: 0; opacity: 0;
transform: scale(0) translateY(0); transform: scale(0) translateY(0);
max-width: 100%; max-width: 100%;
padding: 0 1rem;
@include animation-level(2) { @include animation-level(2) {
transition: opacity var(--transition-standard-in), transform var(--transition-standard-in); transition: opacity var(--transition-standard-in), transform var(--transition-standard-in);
@ -262,7 +289,7 @@
width: 1.875rem !important; width: 1.875rem !important;
height: 1.875rem !important; height: 1.875rem !important;
margin-right: .25rem; margin-right: .25rem;
margin-left: -.625rem; margin-left: -.25rem;
flex: 0 0 auto; flex: 0 0 auto;
} }
@ -275,12 +302,13 @@
// opacity: 0 !important; // opacity: 0 !important;
// } // }
} }
}
&-text { &-party-state-text,
&-title {
max-width: 100%; max-width: 100%;
@include text-overflow(); @include text-overflow();
} }
}
&.two-button-rows { &.two-button-rows {
.call-buttons { .call-buttons {

Loading…
Cancel
Save