This commit is contained in:
Eduard Kuzmenko 2023-03-01 14:20:49 +04:00
parent 820885dbd9
commit 628fbfec26
51 changed files with 701 additions and 281 deletions

View File

@ -4,6 +4,7 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import type {LiteModeKey} from '../helpers/liteMode';
import {CustomEmojiElement, CustomEmojiRendererElement} from '../lib/richTextProcessor/wrapRichText';
import rootScope from '../lib/rootScope';
import {IS_SAFARI} from '../environment/userAgent';
@ -25,6 +26,7 @@ export interface AnimationItem {
el: HTMLElement,
group: AnimationItemGroup,
animation: AnimationItemWrapper,
liteModeKey?: LiteModeKey,
controlled?: boolean | Middleware
};
@ -34,6 +36,7 @@ export interface AnimationItemWrapper {
pause: () => any;
play: () => any;
autoplay: boolean;
loop: boolean | number;
// onVisibilityChange?: (visible: boolean) => boolean;
};
@ -176,12 +179,14 @@ export class AnimationIntersector {
}
}
public addAnimation(
public addAnimation(options: {
animation: AnimationItem['animation'],
group: AnimationItemGroup = '',
group?: AnimationItemGroup,
observeElement?: HTMLElement,
controlled?: AnimationItem['controlled']
) {
controlled?: AnimationItem['controlled'],
liteModeKey?: LiteModeKey
}) {
let {animation, group = '', observeElement, controlled, liteModeKey} = options;
if(group === 'none' || this.byPlayer.has(animation)) {
return;
}
@ -202,7 +207,8 @@ export class AnimationIntersector {
el: observeElement,
animation: animation,
group,
controlled
controlled,
liteModeKey
};
if(controlled && typeof(controlled) !== 'boolean') {

View File

@ -58,6 +58,7 @@ import clamp from '../helpers/number/clamp';
import debounce from '../helpers/schedulers/debounce';
import isBetween from '../helpers/number/isBetween';
import findUpAsChild from '../helpers/dom/findUpAsChild';
import liteMode from '../helpers/liteMode';
const ZOOM_STEP = 0.5;
const ZOOM_INITIAL_VALUE = 1;
@ -866,7 +867,7 @@ export default class AppMediaViewerBase<
const wasActive = fromRight !== 0;
const delay = rootScope.settings.animationsEnabled ? (wasActive ? 350 : 200) : 0;
const delay = liteMode.isAvailable('animations') ? (wasActive ? 350 : 200) : 0;
// let delay = wasActive ? 350 : 10000;
/* if(wasActive) {

View File

@ -37,6 +37,7 @@ import hasRights from '../lib/appManagers/utils/chats/hasRights';
import getDialogIndex from '../lib/appManagers/utils/dialogs/getDialogIndex';
import {generateDelimiter} from './generateDelimiter';
import SettingSection from './settingSection';
import liteMode from '../helpers/liteMode';
type SelectSearchPeerType = 'contacts' | 'dialogs' | 'channelParticipants';
@ -678,7 +679,7 @@ export default class AppSelectPeers {
this.onChange && this.onChange(this.selected.size);
};
if(rootScope.settings.animationsEnabled) {
if(liteMode.isAvailable('animations')) {
div.addEventListener('animationend', onAnimationEnd, {once: true});
} else {
onAnimationEnd();

View File

@ -12,6 +12,7 @@ import appNavigationController, {NavigationItem} from '../appNavigationControlle
import SetTransition from '../singleTransition';
import AutocompleteHelperController from './autocompleteHelperController';
import safeAssign from '../../helpers/object/safeAssign';
import liteMode from '../../helpers/liteMode';
export default class AutocompleteHelper extends EventListenerBase<{
hidden: () => void,
@ -158,7 +159,7 @@ export default class AutocompleteHelper extends EventListenerBase<{
element: this.container,
className: 'is-visible',
forwards: !hide,
duration: rootScope.settings.animationsEnabled && !skipAnimation ? 300 : 0,
duration: liteMode.isAvailable('animations') && !skipAnimation ? 300 : 0,
onTransitionEnd: () => {
this.hidden && this.dispatchEvent('hidden');
},

View File

@ -132,6 +132,7 @@ import wrapPeerTitle from '../wrappers/peerTitle';
import {PopupPeerCheckboxOptions} from '../popups/peer';
import toggleDisability from '../../helpers/dom/toggleDisability';
import {copyTextToClipboard} from '../../helpers/clipboard';
import liteMode from '../../helpers/liteMode';
export const USER_REACTIONS_INLINE = false;
const USE_MEDIA_TAILS = false;
@ -1043,7 +1044,7 @@ export default class ChatBubbles {
this.listenerSetter.add(rootScope)('history_append', async({storageKey, message}) => {
if(storageKey !== this.chat.messagesStorageKey || this.chat.type === 'scheduled') return;
if(rootScope.settings.animationsEnabled) {
if(liteMode.isAvailable('chat_background')) {
this.updateGradient = true;
}
@ -4458,6 +4459,7 @@ export default class ChatBubbles {
group: this.chat.animationGroup,
// play: !!message.pending || !multipleRender,
play: true,
liteModeKey: 'stickers_chat',
loop: true,
emoji: isEmoji ? messageMessage : undefined,
withThumb: true,
@ -5529,7 +5531,8 @@ export default class ChatBubbles {
play: true,
loop: true,
withThumb: true,
loadPromises
loadPromises,
liteModeKey: 'stickers_chat'
});
attachClickEvent(stickerDiv, (e) => {
@ -6227,7 +6230,7 @@ export default class ChatBubbles {
const waitPromise = isAdditionRender ? processPromise(resultPromise) : promise;
if(isFirstMessageRender && rootScope.settings.animationsEnabled/* && false */) {
if(isFirstMessageRender && liteMode.isAvailable('animations')/* && false */) {
let times = isAdditionRender ? 2 : 1;
this.messagesQueueOnRenderAdditional = () => {
this.log('messagesQueueOnRenderAdditional');

View File

@ -37,6 +37,7 @@ import indexOfAndSplice from '../../helpers/array/indexOfAndSplice';
import {Message, WallPaper} from '../../layer';
import animationIntersector, {AnimationItemGroup} from '../animationIntersector';
import {getColorsFromWallPaper} from '../../helpers/color';
import liteMode from '../../helpers/liteMode';
export type ChatType = 'chat' | 'pinned' | 'discussion' | 'scheduled';
@ -211,7 +212,7 @@ export default class Chat extends EventListenerBase<{
gradientCanvas = this.gradientCanvas = canvas;
gradientCanvas.classList.add('chat-background-item-canvas', 'chat-background-item-color-canvas');
if(rootScope.settings.animationsEnabled) {
if(liteMode.isAvailable('animations')) {
gradientRenderer.scrollAnimate(true);
}
// } else {

View File

@ -307,7 +307,7 @@ export default class ChatBackgroundGradientRenderer {
this.update();
}
public update() {
private update() {
if(this._colors.length < 2) {
const color = this._colors[0];
this._ctx.fillStyle = `rgb(${color.r}, ${color.g}, ${color.b})`;

View File

@ -21,6 +21,7 @@ import RLottiePlayer from '../../lib/rlottie/rlottiePlayer';
import {fastRaf} from '../../helpers/schedulers';
import noop from '../../helpers/noop';
import {Middleware} from '../../helpers/middleware';
import liteMode from '../../helpers/liteMode';
const CLASS_NAME = 'reaction';
const TAG_NAME = CLASS_NAME + '-element';
@ -186,6 +187,10 @@ export default class ReactionElement extends HTMLElement {
}
public fireAroundAnimation() {
if(!liteMode.isAvailable('effects_reactions')) {
return;
}
const reaction = this.reactionCount.reaction;
if(reaction._ !== 'reactionEmoji') return;
callbackify(this.managers.appReactionsManager.getReaction(reaction.emoticon), (availableReaction) => {

View File

@ -145,7 +145,7 @@ export default class ReactionsElement extends HTMLElement {
const totalReactions = counts.reduce((acc, c) => acc + c.count, 0);
const canRenderAvatars = reactions && (!!reactions.pFlags.can_see_list || this.message.peerId.isUser()) && totalReactions < REACTION_DISPLAY_BLOCK_COUNTER_AT;
this.sorted = counts.map((reactionCount, idx) => {
let reactionElement = this.sorted.find((reactionElement) => reactionsEqual(reactionElement.reactionCount.reaction, reactionCount.reaction));
let reactionElement: ReactionElement = this.sorted.find((reactionElement) => reactionsEqual(reactionElement.reactionCount.reaction, reactionCount.reaction));
if(!reactionElement) {
const middlewareHelper = this.middleware.create();
reactionElement = new ReactionElement();

View File

@ -11,6 +11,7 @@ import callbackify from '../../helpers/callbackify';
import {attachClickEvent} from '../../helpers/dom/clickEvent';
import findUpClassName from '../../helpers/dom/findUpClassName';
import getVisibleRect from '../../helpers/dom/getVisibleRect';
import liteMode from '../../helpers/liteMode';
import {getMiddleware} from '../../helpers/middleware';
import noop from '../../helpers/noop';
import {fastRaf} from '../../helpers/schedulers';
@ -136,7 +137,7 @@ export class ChatReactionsMenu {
};
private canUseAnimations() {
return rootScope.settings.animationsEnabled && !IS_MOBILE;
return liteMode.isAvailable('animations') && liteMode.isAvailable('stickers_chat') && !IS_MOBILE;
}
private renderReaction(reaction: AvailableReaction) {
@ -184,6 +185,7 @@ export class ChatReactionsMenu {
wrapSticker({
doc: reaction.static_icon,
div: appearWrapper,
liteModeKey: false,
...options
});
} else {
@ -192,6 +194,7 @@ export class ChatReactionsMenu {
doc: reaction.appear_animation,
div: appearWrapper,
play: true,
liteModeKey: false,
...options
}).then(({render}) => render).then((player) => {
assumeType<RLottiePlayer>(player);
@ -217,6 +220,7 @@ export class ChatReactionsMenu {
const selectLoadPromise = wrapSticker({
doc: reaction.select_animation,
div: selectWrapper,
liteModeKey: false,
...options
}).then(({render}) => render).then((player) => {
assumeType<RLottiePlayer>(player);

View File

@ -7,6 +7,7 @@
import indexOfAndSplice from '../../helpers/array/indexOfAndSplice';
import callbackify from '../../helpers/callbackify';
import ListenerSetter from '../../helpers/listenerSetter';
import liteMode from '../../helpers/liteMode';
import {getMiddleware} from '../../helpers/middleware';
import {modifyAckedPromise} from '../../helpers/modifyAckedResult';
import {ChatFull} from '../../layer';
@ -148,7 +149,7 @@ export default class ChatSendAs {
this.updateButtons(peerIds);
};
if(rootScope.settings.animationsEnabled) {
if(liteMode.isAvailable('animations')) {
setTimeout(executeButtonsUpdate, 250);
} else {
executeButtonsUpdate();

View File

@ -0,0 +1,157 @@
/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import cancelEvent from '../helpers/dom/cancelEvent';
import {attachClickEvent} from '../helpers/dom/clickEvent';
import findUpAsChild from '../helpers/dom/findUpAsChild';
import ListenerSetter from '../helpers/listenerSetter';
import safeAssign from '../helpers/object/safeAssign';
import I18n, {LangPackKey} from '../lib/langPack';
import CheckboxField from './checkboxField';
import Row from './row';
import {toast} from './toast';
export type CheckboxFieldsField = {
text: LangPackKey,
description?: LangPackKey,
restrictionText?: LangPackKey,
checkboxField?: CheckboxField,
checked?: boolean,
nested?: CheckboxFieldsField[],
nestedTo?: CheckboxFieldsField,
nestedCounter?: HTMLElement,
setNestedCounter?: (count: number) => void,
toggleWith?: {checked?: CheckboxFieldsField[], unchecked?: CheckboxFieldsField[]},
name?: string,
row?: Row
};
export default class CheckboxFields<K extends CheckboxFieldsField = CheckboxFieldsField> {
public fields: Array<K>;
protected listenerSetter: ListenerSetter;
protected asRestrictions: boolean;
constructor(options: {
fields: Array<K>,
listenerSetter: ListenerSetter,
asRestrictions?: boolean
}) {
safeAssign(this, options);
}
public createField(info: CheckboxFieldsField, isNested?: boolean) {
if(info.nestedTo && !isNested) {
return;
}
const row = info.row = new Row({
titleLangKey: isNested ? undefined : info.text,
checkboxField: info.checkboxField = new CheckboxField({
text: isNested ? info.text : undefined,
checked: info.nested ? false : info.checked,
toggle: !isNested,
listenerSetter: this.listenerSetter,
restriction: this.asRestrictions && !isNested,
name: info.name
}),
listenerSetter: this.listenerSetter,
subtitleLangKey: info.description,
clickable: info.nested ? (e) => {
if(findUpAsChild(e.target as HTMLElement, row.checkboxField.label)) {
return;
}
cancelEvent(e);
row.container.classList.toggle('accordion-toggler-expanded');
accordion.classList.toggle('is-expanded');
} : undefined
});
if(info.restrictionText) {
info.checkboxField.input.disabled = true;
attachClickEvent(info.checkboxField.label, (e) => {
toast(I18n.format(info.restrictionText, true));
}, {listenerSetter: this.listenerSetter});
}
const nodes: HTMLElement[] = [row.container];
let accordion: HTMLElement, nestedCounter: HTMLElement;
if(info.nested) {
const container = accordion = document.createElement('div');
container.classList.add('accordion');
container.style.setProperty('--max-height', info.nested.length * 48 + 'px');
const _info = info;
info.nested.forEach((info) => {
info.nestedTo ??= _info;
container.append(...this.createField(info, true).nodes);
});
nodes.push(container);
const span = document.createElement('span');
span.classList.add('tgico-down', 'accordion-icon');
nestedCounter = info.nestedCounter = document.createElement('b');
this.setNestedCounter(info);
row.title.append(' ', nestedCounter, ' ', span);
row.container.classList.add('accordion-toggler');
row.titleRow.classList.add('with-delimiter');
row.checkboxField.setValueSilently(this.getNestedCheckedLength(info) === info.nested.length);
info.toggleWith ??= {checked: info.nested, unchecked: info.nested};
}
if(info.toggleWith || info.nestedTo) {
const processToggleWith = info.toggleWith ? (info: CheckboxFieldsField) => {
const {toggleWith, nested} = info;
const value = info.checkboxField.checked;
const arr = value ? toggleWith.checked : toggleWith.unchecked;
if(!arr) {
return;
}
const other = this.fields.filter((i) => arr.includes(i));
other.forEach((info) => {
info.checkboxField.setValueSilently(value);
if(info.nestedTo && !nested) {
this.setNestedCounter(info.nestedTo);
}
if(info.toggleWith) {
processToggleWith(info);
}
});
if(info.nested) {
this.setNestedCounter(info);
}
} : undefined;
const processNestedTo = info.nestedTo ? () => {
const length = this.getNestedCheckedLength(info.nestedTo);
info.nestedTo.checkboxField.setValueSilently(length === info.nestedTo.nested.length);
this.setNestedCounter(info.nestedTo, length);
} : undefined;
this.listenerSetter.add(info.checkboxField.input)('change', () => {
processToggleWith?.(info);
processNestedTo?.();
});
}
return {row, nodes};
}
protected getNestedCheckedLength(info: CheckboxFieldsField) {
return info.nested.reduce((acc, v) => acc + +v.checkboxField.checked, 0);
}
public setNestedCounter(info: CheckboxFieldsField, count = this.getNestedCheckedLength(info)) {
info.nestedCounter.textContent = `${count}/${info.nested.length}`;
}
}

View File

@ -6,6 +6,7 @@
import {IS_MOBILE} from '../environment/userAgent';
import {animate} from '../helpers/animation';
import liteMode from '../helpers/liteMode';
import {Middleware} from '../helpers/middleware';
import clamp from '../helpers/number/clamp';
import animationIntersector, {AnimationItemGroup, AnimationItemWrapper} from './animationIntersector';
@ -31,6 +32,8 @@ export default class DotRenderer implements AnimationItemWrapper {
private dpr: number;
public loop: boolean = true;
constructor(
private width: number,
private height: number,
@ -51,7 +54,7 @@ export default class DotRenderer implements AnimationItemWrapper {
private prepare() {
let count = Math.round(this.width * this.height / (35 * (IS_MOBILE ? 2 : 1)));
count *= this.multiply || 1;
count = Math.min(IS_MOBILE ? 1000 : 2200, count);
count = Math.min(!liteMode.isAvailable('chat_spoilers') ? 400 : IS_MOBILE ? 1000 : 2200, count);
count = Math.round(count);
const dots: DotRendererDot[] = this.dots = new Array(count);
@ -168,7 +171,12 @@ export default class DotRenderer implements AnimationItemWrapper {
const dotRenderer = new DotRenderer(width, height, multiply);
dotRenderer.renderFirstFrame();
animationIntersector.addAnimation(dotRenderer, animationGroup, dotRenderer.canvas, middleware);
animationIntersector.addAnimation({
animation: dotRenderer,
group: animationGroup,
observeElement: dotRenderer.canvas,
controlled: middleware
});
return dotRenderer;
}

View File

@ -39,6 +39,7 @@ import PopupStickers from '../../popups/stickers';
import {hideToast, toastNew} from '../../toast';
import safeAssign from '../../../helpers/object/safeAssign';
import type {AppStickersManager} from '../../../lib/appManagers/appStickersManager';
import liteMode from '../../../helpers/liteMode';
const loadedURLs: Set<string> = new Set();
export function appendEmoji(emoji: string, container?: HTMLElement, prepend = false, unify = false) {
@ -81,14 +82,14 @@ export function appendEmoji(emoji: string, container?: HTMLElement, prepend = fa
const placeholder = document.createElement('span');
placeholder.classList.add('emoji-placeholder');
if(rootScope.settings.animationsEnabled) {
if(liteMode.isAvailable('animations')) {
image.style.opacity = '0';
placeholder.style.opacity = '1';
}
image.addEventListener('load', () => {
fastRaf(() => {
if(rootScope.settings.animationsEnabled) {
if(liteMode.isAvailable('animations')) {
image.style.opacity = '';
placeholder.style.opacity = '';
}

View File

@ -13,6 +13,7 @@ import findUpAsChild from '../helpers/dom/findUpAsChild';
import whichChild from '../helpers/dom/whichChild';
import ListenerSetter from '../helpers/listenerSetter';
import {attachClickEvent} from '../helpers/dom/clickEvent';
import liteMode from '../helpers/liteMode';
export function horizontalMenu(
tabs: HTMLElement,
@ -66,7 +67,7 @@ export function horizontalMenu(
});
}
if(!rootScope.settings.animationsEnabled) {
if(!liteMode.isAvailable('animations')) {
animate = false;
}

View File

@ -26,6 +26,7 @@ import setInnerHTML from '../helpers/dom/setInnerHTML';
import {AppManagers} from '../lib/appManagers/managers';
import wrapEmojiText from '../lib/richTextProcessor/wrapEmojiText';
import wrapRichText from '../lib/richTextProcessor/wrapRichText';
import liteMode from '../helpers/liteMode';
let lineTotalLength = 0;
const tailLength = 9;
@ -528,7 +529,7 @@ export default class PollElement extends HTMLElement {
}
performResults(results: PollResults, chosenIndexes: number[], animate = true) {
if(!rootScope.settings.animationsEnabled) {
if(!liteMode.isAvailable('animations')) {
animate = false;
}

View File

@ -4,6 +4,7 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import liteMode from '../../helpers/liteMode';
import {doubleRaf} from '../../helpers/schedulers';
import appImManager from '../../lib/appManagers/appImManager';
import {LangPackKey, _i18n, i18n} from '../../lib/langPack';
@ -114,7 +115,7 @@ class P extends PopupPeer {
hint.classList.add('active');
};
if(rootScope.settings.animationsEnabled) {
if(liteMode.isAvailable('animations')) {
doubleRaf().then(setHintActive);
} else {
setHintActive();

View File

@ -47,6 +47,7 @@ import VIDEO_MIME_TYPES_SUPPORTED from '../../environment/videoMimeTypesSupport'
import rootScope from '../../lib/rootScope';
import shake from '../../helpers/dom/shake';
import AUDIO_MIME_TYPES_SUPPORTED from '../../environment/audioMimeTypeSupport';
import liteMode from '../../helpers/liteMode';
type SendFileParams = SendFileDetails & {
file?: File,
@ -559,7 +560,7 @@ export default class PopupNewMedia extends PopupElement {
langPackKey: key
});
if(rootScope.settings.animationsEnabled) {
if(liteMode.isAvailable('animations')) {
shake(this.body);
}
}

View File

@ -21,6 +21,7 @@ import getPeerInitials from './wrappers/getPeerInitials';
import {wrapTopicIcon} from './wrappers/messageActionTextNewUnsafe';
import makeError from '../helpers/makeError';
import noop from '../helpers/noop';
import liteMode from '../helpers/liteMode';
export async function putAvatar(
div: HTMLElement,
@ -46,7 +47,7 @@ export async function putAvatar(
div.dataset.color = '';
};
} else {
const animate = rootScope.settings.animationsEnabled;
const animate = liteMode.isAvailable('animations');
if(animate) {
img.classList.add('fade-in');
}

View File

@ -10,6 +10,7 @@ import IS_TOUCH_SUPPORTED from '../environment/touchSupport';
import rootScope from '../lib/rootScope';
import findUpAsChild from '../helpers/dom/findUpAsChild';
import {fastRaf} from '../helpers/schedulers';
import liteMode from '../helpers/liteMode';
let rippleClickId = 0;
export default function ripple(
@ -167,7 +168,7 @@ export default function ripple(
};
attachListenerTo.addEventListener('touchstart', (e) => {
if(!rootScope.settings.animationsEnabled) {
if(!liteMode.isAvailable('animations')) {
return;
}
@ -196,7 +197,7 @@ export default function ripple(
return;
}
if(!rootScope.settings.animationsEnabled) {
if(!liteMode.isAvailable('animations')) {
return;
}
// console.log('ripple mousedown', e, e.target, findUpClassName(e.target as HTMLElement, 'c-ripple') === r);

View File

@ -285,10 +285,15 @@ export default class Row {
return media;
}
public toggleDisability(disable = !this.container.classList.contains('is-disabled')) {
this.container.classList.toggle('is-disabled', disable);
return () => this.toggleDisability(!disable);
}
public disableWithPromise(promise: Promise<any>) {
this.container.classList.add('is-disabled');
const toggle = this.toggleDisability(true);
promise.finally(() => {
this.container.classList.remove('is-disabled');
toggle();
});
}

View File

@ -154,30 +154,6 @@ export default class AppDataAndStorageTab extends SliderSuperTabEventable {
this.scrollable.append(section.container);
}
{
const section = new SettingSection({name: 'AutoplayMedia'});
section.content.append(new Row({
checkboxField: new CheckboxField({
text: 'AutoplayGIF',
name: 'gifs',
stateKey: 'settings.autoPlay.gifs',
listenerSetter: this.listenerSetter
}),
listenerSetter: this.listenerSetter
}).container, new Row({
checkboxField: new CheckboxField({
text: 'AutoplayVideo',
name: 'videos',
stateKey: 'settings.autoPlay.videos',
listenerSetter: this.listenerSetter
}),
listenerSetter: this.listenerSetter
}).container);
this.scrollable.append(section.container);
}
}
private setAutoDownloadSubtitle(row: Row, settings: AutoDownloadPeerTypeSettings, sizeMax?: number) {

View File

@ -12,7 +12,7 @@ import rootScope from '../../../lib/rootScope';
import {IS_APPLE, IS_SAFARI} from '../../../environment/userAgent';
import Row, {CreateRowFromCheckboxField} from '../../row';
import AppBackgroundTab from './background';
import {LangPackKey, _i18n} from '../../../lib/langPack';
import I18n, {i18n, LangPackKey, _i18n} from '../../../lib/langPack';
import {attachClickEvent} from '../../../helpers/dom/clickEvent';
import assumeType from '../../../helpers/assumeType';
import {BaseTheme, MessagesAllStickers, StickerSet} from '../../../layer';
@ -33,6 +33,8 @@ import {Theme} from '../../../layer';
import findUpClassName from '../../../helpers/dom/findUpClassName';
import RLottiePlayer from '../../../lib/rlottie/rlottiePlayer';
import themeController from '../../../helpers/themeController';
import liteMode from '../../../helpers/liteMode';
import AppPowerSavingTab from './powerSaving';
export class RangeSettingSelector {
public container: HTMLDivElement;
@ -123,17 +125,30 @@ export default class AppGeneralSettingsTab extends SliderSuperTabEventable {
this.slider.createTab(AppBackgroundTab).open(initArgs);
});
const animationsCheckboxField = new CheckboxField({
text: 'EnableAnimations',
name: 'animations',
stateKey: 'settings.animationsEnabled',
const getLiteModeStatus = (): LangPackKey => rootScope.settings.liteMode.all ? 'Checkbox.Enabled' : 'Checkbox.Disabled';
const i = new I18n.IntlElement();
const onUpdate = () => {
i.compareAndUpdate({key: getLiteModeStatus()});
};
onUpdate();
const liteModeRow = new Row({
icon: 'animations',
titleLangKey: 'LiteMode.EnableText',
titleRightSecondary: i.element,
clickable: () => {
this.slider.createTab(AppPowerSavingTab).open();
},
listenerSetter: this.listenerSetter
});
this.listenerSetter.add(rootScope)('settings_updated', onUpdate);
container.append(
range.container,
chatBackgroundButton,
CreateRowFromCheckboxField(animationsCheckboxField).container
liteModeRow.container
);
}
@ -186,7 +201,7 @@ export default class AppGeneralSettingsTab extends SliderSuperTabEventable {
lastOnFrameNo?.(-1);
if(item.player && rootScope.settings.animationsEnabled) {
if(item.player && liteMode.isAvailable('animations')) {
if(IS_SAFARI) {
if(item.player.paused) {
item.player.restart();

View File

@ -0,0 +1,131 @@
/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import {State} from '../../../config/state';
import flatten from '../../../helpers/array/flatten';
import {attachClickEvent} from '../../../helpers/dom/clickEvent';
import {LiteModeKey} from '../../../helpers/liteMode';
import pause from '../../../helpers/schedulers/pause';
import rootScope from '../../../lib/rootScope';
import CheckboxFields, {CheckboxFieldsField} from '../../checkboxFields';
import SettingSection from '../../settingSection';
import SliderSuperTab from '../../sliderTab';
import {toastNew} from '../../toast';
type PowerSavingCheckboxFieldsField = CheckboxFieldsField & {
key: LiteModeKey
};
export default class AppPowerSavingTab extends SliderSuperTab {
public init() {
this.container.classList.add('power-saving-container');
this.setTitle('LiteMode.Title');
const form = document.createElement('form');
let infoSection: SettingSection;
{
const section = infoSection = new SettingSection({
caption: 'LiteMode.Info'
});
form.append(section.container);
}
const keys: Array<LiteModeKey | [LiteModeKey, LiteModeKey[]]> = [
'all',
'video',
'gif',
['stickers', ['stickers_panel', 'stickers_chat']],
['emoji', ['emoji_panel', 'emoji_messages']],
['effects', ['effects_reactions', 'effects_premiumstickers', 'effects_emoji']],
['chat', ['chat_background', 'chat_spoilers']],
'animations'
];
let fields: PowerSavingCheckboxFieldsField[], checkboxFields: CheckboxFields<PowerSavingCheckboxFieldsField>;
{
const section = new SettingSection({});
const wrap = (key: typeof keys[0]): PowerSavingCheckboxFieldsField[] => {
const isArray = Array.isArray(key);
const mainKey = isArray ? key[0] : key;
const nested = isArray ? flatten(key[1].map(wrap)) : undefined;
const value = rootScope.settings.liteMode[mainKey];
return [{
key: mainKey,
text: mainKey === 'all' ? 'LiteMode.EnableText' : `LiteMode.Key.${mainKey}.Title`,
checked: mainKey === 'all' ? value : !value,
nested: nested,
name: 'power-saving-' + mainKey
}, ...(nested || [])];
};
fields = flatten(keys.map(wrap));
checkboxFields = new CheckboxFields({
fields: fields,
listenerSetter: this.listenerSetter
});
fields.forEach((field, idx) => {
const created = checkboxFields.createField(field);
if(!created) {
return;
}
const {row, nodes} = created;
(idx === 0 ? infoSection : section).content.append(...nodes);
});
attachClickEvent(section.content, () => {
if(rootScope.settings.liteMode.all) {
toastNew({langPackKey: 'LiteMode.DisableAlert'});
}
}, {listenerSetter: this.listenerSetter});
form.append(section.container);
}
const onAllChange = (disable: boolean) => {
fields.forEach((field) => {
if(field.key === 'all') {
return;
}
if(field.nested) {
checkboxFields.setNestedCounter(field, disable ? 0 : undefined);
}
field.checkboxField.input.classList.toggle('is-fake-disabled', disable);
field.row.toggleDisability(disable);
});
};
this.listenerSetter.add(form)('change', async() => {
const liteMode: State['settings']['liteMode'] = {} as any;
fields.forEach((field) => {
const checked = field.checkboxField.checked;
liteMode[field.key] = field.key === 'all' ? checked : !checked;
});
const wasAll = rootScope.settings.liteMode.all;
if(wasAll !== liteMode.all) {
onAllChange(!wasAll);
if(liteMode.all) {
await pause(200);
}
}
await this.managers.appStateManager.setByKey('settings.liteMode', rootScope.settings.liteMode = liteMode);
});
onAllChange(rootScope.settings.liteMode.all);
this.scrollable.append(form);
}
}

View File

@ -28,7 +28,6 @@ import {AccountAuthorizations, Authorization} from '../../../layer';
import PopupElement from '../../popups';
import {attachClickEvent} from '../../../helpers/dom/clickEvent';
import SettingSection from '../../settingSection';
// import AppMediaViewer from "../../appMediaViewerNew";
export default class AppSettingsTab extends SliderSuperTab {
private buttons: {

View File

@ -5,8 +5,6 @@
*/
import type {ChatRights} from '../../../lib/appManagers/appChatsManager';
import flatten from '../../../helpers/array/flatten';
import cancelEvent from '../../../helpers/dom/cancelEvent';
import {attachClickEvent} from '../../../helpers/dom/clickEvent';
import findUpTag from '../../../helpers/dom/findUpTag';
import replaceContent from '../../../helpers/dom/replaceContent';
@ -19,32 +17,22 @@ import combineParticipantBannedRights from '../../../lib/appManagers/utils/chats
import hasRights from '../../../lib/appManagers/utils/chats/hasRights';
import getPeerActiveUsernames from '../../../lib/appManagers/utils/peers/getPeerActiveUsernames';
import getPeerId from '../../../lib/appManagers/utils/peers/getPeerId';
import I18n, {i18n, join, LangPackKey} from '../../../lib/langPack';
import {i18n, join, LangPackKey} from '../../../lib/langPack';
import rootScope from '../../../lib/rootScope';
import CheckboxField from '../../checkboxField';
import PopupPickUser from '../../popups/pickUser';
import Row from '../../row';
import SettingSection from '../../settingSection';
import {SliderSuperTabEventable} from '../../sliderTab';
import {toast} from '../../toast';
import AppUserPermissionsTab from './userPermissions';
import findUpAsChild from '../../../helpers/dom/findUpAsChild';
import CheckboxFields, {CheckboxFieldsField} from '../../checkboxFields';
type T = {
type PermissionsCheckboxFieldsField = CheckboxFieldsField & {
flags: ChatRights[],
text: LangPackKey,
exceptionText: LangPackKey,
checkboxField?: CheckboxField,
nested?: T[],
nestedTo?: T,
nestedCounter?: HTMLElement,
setNestedCounter?: (count: number) => void,
toggleWith?: {checked?: ChatRights[], unchecked?: ChatRights[]}
exceptionText: LangPackKey
};
export class ChatPermissions {
public v: Array<T>;
export class ChatPermissions extends CheckboxFields<PermissionsCheckboxFieldsField> {
protected chat: Chat.chat | Chat.channel;
protected rights: ChatBannedRights.chatBannedRights;
protected defaultBannedRights: ChatBannedRights.chatBannedRights;
@ -56,11 +44,22 @@ export class ChatPermissions {
appendTo: HTMLElement,
participant?: ChannelParticipant.channelParticipantBanned
}, private managers: AppManagers) {
super({
listenerSetter: options.listenerSetter,
fields: [],
asRestrictions: true
});
this.construct();
}
public async construct() {
const mediaNested: T[] = [
const options = this.options;
const chat = this.chat = await this.managers.appChatsManager.getChat(options.chatId) as Chat.chat | Chat.channel;
const defaultBannedRights = this.defaultBannedRights = chat.default_banned_rights;
const rights = this.rights = options.participant ? combineParticipantBannedRights(chat as Chat.channel, options.participant.banned_rights) : defaultBannedRights;
const mediaNested: PermissionsCheckboxFieldsField[] = [
{flags: ['send_photos'], text: 'UserRestrictionsSendPhotos', exceptionText: 'UserRestrictionsNoSendPhotos'},
{flags: ['send_videos'], text: 'UserRestrictionsSendVideos', exceptionText: 'UserRestrictionsNoSendVideos'},
{flags: ['send_stickers', 'send_gifs'], text: 'UserRestrictionsSendStickers', exceptionText: 'UserRestrictionsNoSendStickers'},
@ -68,150 +67,57 @@ export class ChatPermissions {
{flags: ['send_docs'], text: 'UserRestrictionsSendFiles', exceptionText: 'UserRestrictionsNoSendDocs'},
{flags: ['send_voices'], text: 'UserRestrictionsSendVoices', exceptionText: 'UserRestrictionsNoSendVoice'},
{flags: ['send_roundvideos'], text: 'UserRestrictionsSendRound', exceptionText: 'UserRestrictionsNoSendRound'},
{flags: ['embed_links'], text: 'UserRestrictionsEmbedLinks', exceptionText: 'UserRestrictionsNoEmbedLinks', toggleWith: {checked: ['send_plain']}},
{flags: ['embed_links'], text: 'UserRestrictionsEmbedLinks', exceptionText: 'UserRestrictionsNoEmbedLinks'},
{flags: ['send_polls'], text: 'UserRestrictionsSendPolls', exceptionText: 'UserRestrictionsNoSendPolls'}
];
const mediaToggleWith = flatten(mediaNested.map(({flags}) => flags));
const media: T = {flags: ['send_media'], text: 'UserRestrictionsSendMedia', exceptionText: 'UserRestrictionsNoSendMedia', nested: mediaNested, toggleWith: {unchecked: mediaToggleWith, checked: mediaToggleWith}};
this.v = [
{flags: ['send_plain'], text: 'UserRestrictionsSend', exceptionText: 'UserRestrictionsNoSend', toggleWith: {unchecked: ['embed_links']}},
media,
const mediaToggleWith = mediaNested;
const v: PermissionsCheckboxFieldsField[] = [
{flags: ['send_plain'], text: 'UserRestrictionsSend', exceptionText: 'UserRestrictionsNoSend'},
{flags: ['send_media'], text: 'UserRestrictionsSendMedia', exceptionText: 'UserRestrictionsNoSendMedia', nested: mediaNested},
{flags: ['invite_users'], text: 'UserRestrictionsInviteUsers', exceptionText: 'UserRestrictionsNoInviteUsers'},
{flags: ['pin_messages'], text: 'UserRestrictionsPinMessages', exceptionText: 'UserRestrictionsNoPinMessages'},
{flags: ['change_info'], text: 'UserRestrictionsChangeInfo', exceptionText: 'UserRestrictionsNoChangeInfo'}
];
mediaNested.forEach((info) => info.nestedTo = media);
const options = this.options;
const chat = this.chat = await this.managers.appChatsManager.getChat(options.chatId) as Chat.chat | Chat.channel;
const defaultBannedRights = this.defaultBannedRights = chat.default_banned_rights;
const rights = this.rights = options.participant ? combineParticipantBannedRights(chat as Chat.channel, options.participant.banned_rights) : defaultBannedRights;
for(const info of this.v) {
const {nodes} = this.createRow(info);
options.appendTo.append(...nodes);
}
this.v.push(...mediaNested);
}
protected createRow(info: T, isNested?: boolean) {
const {defaultBannedRights, chat, rights, restrictionText} = this;
const mainFlag = info.flags[0];
const row = new Row({
titleLangKey: isNested ? undefined : info.text,
checkboxField: info.checkboxField = new CheckboxField({
text: isNested ? info.text : undefined,
checked: info.nested ? false : hasRights(chat, mainFlag, rights),
toggle: !isNested,
listenerSetter: this.options.listenerSetter,
restriction: !isNested
}),
listenerSetter: this.options.listenerSetter,
clickable: info.nested ? (e) => {
if(findUpAsChild(e.target as HTMLElement, row.checkboxField.label)) {
return;
}
cancelEvent(e);
row.container.classList.toggle('accordion-toggler-expanded');
accordion.classList.toggle('is-expanded');
} : undefined
const map: {[action in ChatRights]?: PermissionsCheckboxFieldsField} = {};
v.push(...mediaNested);
v.forEach((info) => {
const mainFlag = info.flags[0];
map[mainFlag] = info;
info.checked = hasRights(chat, mainFlag, rights)
});
if((
this.options.participant &&
defaultBannedRights.pFlags[mainFlag as keyof typeof defaultBannedRights['pFlags']]
) || (
getPeerActiveUsernames(chat as Chat.channel)[0] &&
(
info.flags.includes('pin_messages') ||
info.flags.includes('change_info')
)
)
) {
info.checkboxField.input.disabled = true;
mediaNested.forEach((info) => info.nestedTo = map.send_media);
map.send_media.toggleWith = {unchecked: mediaToggleWith, checked: mediaToggleWith};
map.embed_links.toggleWith = {checked: [map.send_plain]};
map.send_plain.toggleWith = {unchecked: [map.embed_links]};
attachClickEvent(info.checkboxField.label, (e) => {
toast(I18n.format(restrictionText, true));
}, {listenerSetter: this.options.listenerSetter});
this.fields = v;
for(const info of this.fields) {
if((
this.options.participant &&
defaultBannedRights.pFlags[info.flags[0] as keyof typeof defaultBannedRights['pFlags']]
) || (
getPeerActiveUsernames(chat as Chat.channel)[0] &&
(
info.flags.includes('pin_messages') ||
info.flags.includes('change_info')
)
)
) {
info.restrictionText = this.restrictionText;
}
if(info.nestedTo) {
continue;
}
const {nodes} = this.createField(info);
options.appendTo.append(...nodes);
}
if(info.toggleWith || info.nestedTo) {
const processToggleWith = info.toggleWith ? (info: T) => {
const {toggleWith, nested} = info;
const value = info.checkboxField.checked;
const arr = value ? toggleWith.checked : toggleWith.unchecked;
if(!arr) {
return;
}
const other = this.v.filter((i) => arr.includes(i.flags[0]));
other.forEach((info) => {
info.checkboxField.setValueSilently(value);
if(info.nestedTo && !nested) {
this.setNestedCounter(info.nestedTo);
}
if(info.toggleWith) {
processToggleWith(info);
}
});
if(info.nested) {
this.setNestedCounter(info);
}
} : undefined;
const processNestedTo = info.nestedTo ? () => {
const length = this.getNestedCheckedLength(info.nestedTo);
info.nestedTo.checkboxField.setValueSilently(length === info.nestedTo.nested.length);
this.setNestedCounter(info.nestedTo, length);
} : undefined;
this.options.listenerSetter.add(info.checkboxField.input)('change', () => {
processToggleWith?.(info);
processNestedTo?.();
});
}
const nodes: HTMLElement[] = [row.container];
let accordion: HTMLElement, nestedCounter: HTMLElement;
if(info.nested) {
const container = accordion = document.createElement('div');
container.classList.add('accordion');
container.style.setProperty('--max-height', info.nested.length * 48 + 'px');
info.nested.forEach((info) => {
container.append(...this.createRow(info, true).nodes);
});
nodes.push(container);
const span = document.createElement('span');
span.classList.add('tgico-down', 'accordion-icon');
nestedCounter = info.nestedCounter = document.createElement('b');
this.setNestedCounter(info);
row.title.append(' ', nestedCounter, ' ', span);
row.container.classList.add('accordion-toggler');
row.titleRow.classList.add('with-delimiter');
row.checkboxField.setValueSilently(this.getNestedCheckedLength(info) === info.nested.length);
}
return {row, nodes};
}
protected getNestedCheckedLength(info: T) {
return info.nested.reduce((acc, v) => acc + +v.checkboxField.checked, 0);
}
protected setNestedCounter(info: T, count = this.getNestedCheckedLength(info)) {
info.nestedCounter.textContent = `${count}/${info.nested.length}`;
}
public takeOut() {
@ -224,7 +130,7 @@ export class ChatPermissions {
const IGNORE_FLAGS: Set<ChatRights> = new Set([
'send_media'
]);
for(const info of this.v) {
for(const info of this.fields) {
const banned = !info.checkboxField.checked;
if(!banned) {
continue;
@ -341,7 +247,7 @@ export default class AppGroupPermissionsTab extends SliderSuperTabEventable {
// const combinedRights = appChatsManager.combineParticipantBannedRights(this.chatId, bannedRights);
const cantWhat: LangPackKey[] = []/* , canWhat: LangPackKey[] = [] */;
chatPermissions.v.forEach((info) => {
chatPermissions.fields.forEach((info) => {
const mainFlag = info.flags[0];
// @ts-ignore
if(bannedRights.pFlags[mainFlag] && !defaultBannedRights.pFlags[mainFlag]) {

View File

@ -24,6 +24,7 @@ import PeerProfile from '../../peerProfile';
import {Message} from '../../../layer';
import getMessageThreadId from '../../../lib/appManagers/utils/messages/getMessageThreadId';
import AppEditTopicTab from './editTopic';
import liteMode from '../../../helpers/liteMode';
type SharedMediaHistoryStorage = Partial<{
[type in SearchSuperType]: {mid: number, peerId: PeerId}[]
@ -226,7 +227,7 @@ export default class AppSharedMediaTab extends SliderSuperTab {
}],
scrollable: this.scrollable,
onChangeTab: (mediaTab) => {
const timeout = mediaTab.type === 'members' && rootScope.settings.animationsEnabled ? 250 : 0;
const timeout = mediaTab.type === 'members' && liteMode.isAvailable('animations') ? 250 : 0;
setTimeout(() => {
btnAddMembers.classList.toggle('is-hidden', mediaTab.type !== 'members');
}, timeout);

View File

@ -4,6 +4,7 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import liteMode from '../helpers/liteMode';
import rootScope from '../lib/rootScope';
type SetTransitionOptions = {
@ -36,7 +37,7 @@ const SetTransition = (options: SetTransitionOptions) => {
// return;
// }
if(useRafs && rootScope.settings.animationsEnabled && duration) {
if(useRafs && liteMode.isAvailable('animations') && duration) {
element.dataset.raf = '' + window.requestAnimationFrame(() => {
delete element.dataset.raf;
SetTransition({
@ -64,7 +65,7 @@ const SetTransition = (options: SetTransitionOptions) => {
};
onTransitionStart?.();
if(!rootScope.settings.animationsEnabled || !duration) {
if(!liteMode.isAvailable('animations') || !duration) {
element.classList.remove('animating', 'backwards');
afterTimeout();
return;

View File

@ -10,6 +10,7 @@ import {dispatchHeavyAnimationEvent} from '../hooks/useHeavyAnimationCheck';
import whichChild from '../helpers/dom/whichChild';
import cancelEvent from '../helpers/dom/cancelEvent';
import ListenerSetter from '../helpers/listenerSetter';
import liteMode from '../helpers/liteMode';
function makeTransitionFunction(options: TransitionFunction) {
return options;
@ -216,7 +217,7 @@ const TransitionSlider = (options: TransitionSliderOptions) => {
const to = content.children[id] as HTMLElement;
if(!rootScope.settings.animationsEnabled || (prevId === -1 && !animateFirst)) {
if(!liteMode.isAvailable('animations') || (prevId === -1 && !animateFirst)) {
animate = false;
}

View File

@ -11,6 +11,7 @@ import {formatFullSentTime} from '../../helpers/date';
import {simulateClickEvent, attachClickEvent} from '../../helpers/dom/clickEvent';
import findUpClassName from '../../helpers/dom/findUpClassName';
import formatBytes from '../../helpers/formatBytes';
import liteMode from '../../helpers/liteMode';
import {MediaSizeType} from '../../helpers/mediaSizes';
import noop from '../../helpers/noop';
import {Message, MessageMedia, WebPage} from '../../layer';
@ -289,7 +290,7 @@ export default async function wrapDocument({message, withTime, fontWeight, voice
setTimeout(async() => { // wait for preloader animation end
const url = (await getCacheContext()).url;
window.open(url);
}, rootScope.settings.animationsEnabled ? 250 : 0);
}, liteMode.isAvailable('animations') ? 250 : 0);
});
}
} else if(MEDIA_MIME_TYPES_SUPPORTED.has(doc.mime_type) && doc.thumbs?.length) {

View File

@ -24,6 +24,7 @@ import createVideo from '../../helpers/dom/createVideo';
import noop from '../../helpers/noop';
import {THUMB_TYPE_FULL} from '../../lib/mtproto/mtproto_config';
import {Middleware} from '../../helpers/middleware';
import liteMode from '../../helpers/liteMode';
export default async function wrapPhoto({photo, message, container, boxWidth, boxHeight, withTail, isOut, lazyLoadQueue, middleware, size, withoutPreloader, loadPromises, autoDownloadSize, noBlur, noThumb, noFadeIn, blurAfter, managers = rootScope.managers, processUrl}: {
photo: MyPhoto | MyDocument | WebDocument,
@ -191,7 +192,7 @@ export default async function wrapPhoto({photo, message, container, boxWidth, bo
// console.log('wrapPhoto downloaded:', photo, photo.downloaded, container);
const needFadeIn = (thumbImage || !cacheContext.downloaded) && rootScope.settings.animationsEnabled && !noFadeIn;
const needFadeIn = (thumbImage || !cacheContext.downloaded) && liteMode.isAvailable('animations') && !noFadeIn;
let preloader: ProgressivePreloader;
const uploadingFileName = (message as Message.message)?.uploadingFileName;

View File

@ -45,6 +45,7 @@ import PopupStickers from '../popups/stickers';
import {hideToast, toastNew} from '../toast';
import wrapStickerAnimation from './stickerAnimation';
import framesCache from '../../helpers/framesCache';
import liteMode, {LiteModeKey} from '../../helpers/liteMode';
// https://github.com/telegramdesktop/tdesktop/blob/master/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp#L40
export const STICKER_EFFECT_MULTIPLIER = 1 + 0.245 * 2;
@ -64,7 +65,7 @@ const onAnimationEnd = (element: HTMLElement, onAnimationEnd: () => void, timeou
const _timeout = setTimeout(onEnd, timeout);
};
export default async function wrapSticker({doc, div, middleware, loadStickerMiddleware, lazyLoadQueue, exportLoad, group, play, onlyThumb, emoji, width, height, withThumb, loop, loadPromises, needFadeIn, needUpscale, skipRatio, static: asStatic, managers = rootScope.managers, fullThumb, isOut, noPremium, withLock, relativeEffect, loopEffect, isCustomEmoji, syncedVideo}: {
export default async function wrapSticker({doc, div, middleware, loadStickerMiddleware, lazyLoadQueue, exportLoad, group, play, onlyThumb, emoji, width, height, withThumb, loop, loadPromises, needFadeIn, needUpscale, skipRatio, static: asStatic, managers = rootScope.managers, fullThumb, isOut, noPremium, withLock, relativeEffect, loopEffect, isCustomEmoji, syncedVideo, liteModeKey, isEffect}: {
doc: MyDocument,
div: HTMLElement | HTMLElement[],
middleware?: Middleware,
@ -92,10 +93,14 @@ export default async function wrapSticker({doc, div, middleware, loadStickerMidd
relativeEffect?: boolean,
loopEffect?: boolean,
isCustomEmoji?: boolean,
syncedVideo?: boolean
syncedVideo?: boolean,
liteModeKey?: LiteModeKey | false,
isEffect?: boolean
}) {
div = Array.isArray(div) ? div : [div];
liteModeKey ??= 'stickers_panel';
if(isCustomEmoji) {
emoji = doc.stickerEmojiRaw;
}
@ -118,15 +123,25 @@ export default async function wrapSticker({doc, div, middleware, loadStickerMidd
lottieLoader.loadLottieWorkers();
}
loop = !!(!emoji || isCustomEmoji) && loop;
div.forEach((div) => {
div.dataset.docId = '' + doc.id;
if(emoji) {
div.dataset.stickerEmoji = emoji;
}
div.dataset.stickerPlay = '' + +play;
div.dataset.stickerLoop = '' + +loop;
div.classList.add('media-sticker-wrapper');
});
if(play && !liteMode.isAvailable(liteModeKey) && !isCustomEmoji && !isEffect) {
play = false;
loop = false;
}
/* if(stickerType === 3) {
const videoRes = wrapVideo({
doc,
@ -277,7 +292,7 @@ export default async function wrapSticker({doc, div, middleware, loadStickerMidd
const path = document.createElementNS(ns, 'path');
path.setAttributeNS(null, 'd', d);
if(rootScope.settings.animationsEnabled && !isCustomEmoji) path.setAttributeNS(null, 'fill', 'url(#g)');
if(liteMode.isAvailable('animations') && !isCustomEmoji) path.setAttributeNS(null, 'fill', 'url(#g)');
svg.append(path);
div.forEach((div, idx) => div.append(idx > 0 ? svg.cloneNode(true) : svg));
haveThumbCached = true;
@ -383,7 +398,7 @@ export default async function wrapSticker({doc, div, middleware, loadStickerMidd
const animation = await lottieLoader.loadAnimationWorker({
container: (div as HTMLElement[])[0],
loop: !!(!emoji || isCustomEmoji) && loop,
loop,
autoplay: play,
animationData: blob,
width,
@ -394,7 +409,8 @@ export default async function wrapSticker({doc, div, middleware, loadStickerMidd
toneIndex,
sync: isCustomEmoji,
middleware: loadStickerMiddleware ?? middleware,
group
group,
liteModeKey: liteModeKey || undefined
});
// const deferred = deferredPromise<void>();
@ -407,7 +423,7 @@ export default async function wrapSticker({doc, div, middleware, loadStickerMidd
const onFirstFrame = (container: HTMLElement, canvas: HTMLCanvasElement) => {
const element = container.firstElementChild !== canvas && container.firstElementChild as HTMLElement;
if(needFadeIn !== false) {
needFadeIn = (needFadeIn || !element || element.tagName === 'svg') && rootScope.settings.animationsEnabled;
needFadeIn = (needFadeIn || !element || element.tagName === 'svg') && liteMode.isAvailable('animations');
}
const cb = () => {
@ -505,7 +521,7 @@ export default async function wrapSticker({doc, div, middleware, loadStickerMidd
const thumbImage = (div as HTMLElement[]).map((div, idx) => (div.firstElementChild as HTMLElement) !== media[idx] && div.firstElementChild) as HTMLElement[];
if(needFadeIn !== false) {
needFadeIn = (needFadeIn || !downloaded || (asStatic ? thumbImage[0] : (!thumbImage[0] || thumbImage[0].tagName === 'svg'))) && rootScope.settings.animationsEnabled;
needFadeIn = (needFadeIn || !downloaded || (asStatic ? thumbImage[0] : (!thumbImage[0] || thumbImage[0].tagName === 'svg'))) && liteMode.isAvailable('animations');
}
if(needFadeIn) {
@ -569,7 +585,13 @@ export default async function wrapSticker({doc, div, middleware, loadStickerMidd
}
if(isAnimated) {
animationIntersector.addAnimation(media as HTMLVideoElement, group, undefined, middleware);
animationIntersector.addAnimation({
animation: media as HTMLVideoElement,
observeElement: div,
group,
controlled: middleware,
liteModeKey: liteModeKey || undefined
});
}
if(loaded.push(media) === mediaLength) {
@ -666,8 +688,13 @@ function attachStickerEffectHandler({container, doc, managers, middleware, isOut
let playing = false;
attachClickEvent(container, async(e) => {
const isAvailable = liteMode.isAvailable('effects_premiumstickers') || relativeEffect;
cancelEvent(e);
if(playing) {
if(!e.isTrusted && !isAvailable) {
return;
}
if(playing || !isAvailable) {
const a = document.createElement('a');
a.onclick = () => {
hideToast();
@ -749,7 +776,7 @@ export async function onEmojiStickerClick({event, container, managers, peerId, m
animation.restart();
}
if(!peerId.isUser()) {
if(!peerId.isUser() || !liteMode.isAvailable('effects_emoji')) {
return;
}

View File

@ -76,7 +76,8 @@ export default function wrapStickerAnimation({
group: 'none',
skipRatio,
managers,
fullThumb
fullThumb,
isEffect: true
}).then(({render}) => render).then((_animation) => {
assumeType<RLottiePlayer>(_animation);
if(!middleware()) {

View File

@ -69,7 +69,10 @@ export default async function wrapStickerSetThumb({set, lazyLoadQueue, container
container.append(media);
if(set.pFlags.videos) {
animationIntersector.addAnimation(media as HTMLVideoElement, group);
animationIntersector.addAnimation({
animation: media as HTMLVideoElement,
group
});
}
});
});

View File

@ -14,6 +14,7 @@ import createVideo from '../../helpers/dom/createVideo';
import isInDOM from '../../helpers/dom/isInDOM';
import renderImageFromUrl from '../../helpers/dom/renderImageFromUrl';
import getStrippedThumbIfNeeded from '../../helpers/getStrippedThumbIfNeeded';
import liteMode from '../../helpers/liteMode';
import makeError from '../../helpers/makeError';
import mediaSizes, {ScreenSize} from '../../helpers/mediaSizes';
import {Middleware} from '../../helpers/middleware';
@ -96,7 +97,7 @@ export default async function wrapVideo({doc, container, message, boxWidth, boxH
doc.size <= MAX_VIDEO_AUTOPLAY_SIZE &&
!isAlbumItem
)
) && (doc.type === 'gif' ? rootScope.settings.autoPlay.gifs : rootScope.settings.autoPlay.videos)
) && (doc.type === 'gif' ? liteMode.isAvailable('gif') : liteMode.isAvailable('video'))
);
let spanTime: HTMLElement, spanPlay: HTMLElement;
@ -537,7 +538,10 @@ export default async function wrapVideo({doc, container, message, boxWidth, boxH
onMediaLoad(video).then(() => {
if(group) {
animationIntersector.addAnimation(video, group);
animationIntersector.addAnimation({
animation: video,
group
});
}
if(preloader && !uploadFileName) {

View File

@ -21,7 +21,7 @@ const App = {
version: process.env.VERSION,
versionFull: process.env.VERSION_FULL,
build: +process.env.BUILD,
langPackVersion: '0.9.3',
langPackVersion: '0.9.7',
langPack: 'webk',
langPackCode: 'en',
domains: MAIN_DOMAINS,

View File

@ -4,15 +4,16 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import {AppMediaPlaybackController} from '../components/appMediaPlaybackController';
import type {LiteModeKey} from '../helpers/liteMode';
import type {AppMediaPlaybackController} from '../components/appMediaPlaybackController';
import type {TopPeerType, MyTopPeer} from '../lib/appManagers/appUsersManager';
import type {AutoDownloadSettings, BaseTheme, NotifyPeer, PeerNotifySettings, Theme, ThemeSettings, WallPaper} from '../layer';
import type DialogsStorage from '../lib/storages/dialogs';
import type FiltersStorage from '../lib/storages/filters';
import type {AuthState, Modify} from '../types';
import {IS_MOBILE} from '../environment/userAgent';
import getTimeFormat from '../helpers/getTimeFormat';
import {nextRandomUint} from '../helpers/random';
import {AutoDownloadSettings, BaseTheme, NotifyPeer, PeerNotifySettings, Theme, ThemeSettings, WallPaper} from '../layer';
import {TopPeerType, MyTopPeer} from '../lib/appManagers/appUsersManager';
import DialogsStorage from '../lib/storages/dialogs';
import FiltersStorage from '../lib/storages/filters';
import {AuthState, Modify} from '../types';
import App from './app';
const STATE_VERSION = App.version;
@ -74,7 +75,7 @@ export type State = {
messagesTextSize: number,
distanceUnit: 'kilometers' | 'miles',
sendShortcut: 'enter' | 'ctrlEnter',
animationsEnabled: boolean,
animationsEnabled?: boolean, // ! DEPRECATED
autoDownload: {
contacts?: boolean, // ! DEPRECATED
private?: boolean, // ! DEPRECATED
@ -85,7 +86,7 @@ export type State = {
file: AutoDownloadPeerTypeSettings
},
autoDownloadNew: AutoDownloadSettings,
autoPlay: {
autoPlay?: { // ! DEPRECATED
gifs: boolean,
videos: boolean
},
@ -104,7 +105,8 @@ export type State = {
sound: boolean
},
nightTheme?: boolean, // ! DEPRECATED
timeFormat: 'h12' | 'h23'
timeFormat: 'h12' | 'h23',
liteMode: {[key in LiteModeKey]: boolean}
},
playbackParams: ReturnType<AppMediaPlaybackController['getPlaybackParams']>,
keepSigned: boolean,
@ -233,7 +235,6 @@ export const STATE_INIT: State = {
messagesTextSize: 16,
distanceUnit: 'kilometers',
sendShortcut: 'enter',
animationsEnabled: true,
autoDownload: {
photo: {
contacts: true,
@ -265,10 +266,6 @@ export const STATE_INIT: State = {
video_size_max: 15728640,
video_upload_maxbitrate: 100
},
autoPlay: {
gifs: true,
videos: true
},
stickers: {
suggest: true,
loop: true
@ -285,7 +282,26 @@ export const STATE_INIT: State = {
notifications: {
sound: false
},
timeFormat: getTimeFormat()
timeFormat: getTimeFormat(),
liteMode: {
all: false,
animations: false,
chat: false,
chat_background: false,
chat_spoilers: false,
effects: false,
effects_premiumstickers: false,
effects_reactions: false,
effects_emoji: false,
emoji: false,
emoji_messages: false,
emoji_panel: false,
gif: false,
stickers: false,
stickers_chat: false,
stickers_panel: false,
video: false
}
},
playbackParams: {
volume: 1,

View File

@ -12,6 +12,7 @@ import roundRect from './canvas/roundRect';
import Shimmer from './canvas/shimmer';
import customProperties from './dom/customProperties';
import easeInOutSine from './easing/easeInOutSine';
import liteMode from './liteMode';
import mediaSizes from './mediaSizes';
export default class DialogsPlaceholder {
@ -91,7 +92,7 @@ export default class DialogsPlaceholder {
this.availableLength = availableLength;
this.detachTime = Date.now();
if(!rootScope.settings.animationsEnabled) {
if(!liteMode.isAvailable('animations')) {
this.remove();
}
}
@ -132,7 +133,7 @@ export default class DialogsPlaceholder {
if(!detachTime) {
return;
} else if(!rootScope.settings.animationsEnabled) {
} else if(!liteMode.isAvailable('animations')) {
this.remove();
return;
}
@ -219,7 +220,7 @@ export default class DialogsPlaceholder {
}
// ! should've removed the loop if animations are disabled
if(rootScope.settings.animationsEnabled) {
if(liteMode.isAvailable('animations')) {
this.renderFrame();
}

View File

@ -1,7 +1,8 @@
import rootScope from '../../lib/rootScope';
import liteMode from '../liteMode';
export default function shake(element: HTMLElement) {
if(!rootScope.settings.animationsEnabled) {
if(!liteMode.isAvailable('animations')) {
return;
}

View File

@ -8,6 +8,7 @@ import {ScrollableBase} from '../../components/scrollable';
import SwipeHandler from '../../components/swipeHandler';
import IS_TOUCH_SUPPORTED from '../../environment/touchSupport';
import rootScope from '../../lib/rootScope';
import liteMode from '../liteMode';
import {Middleware} from '../middleware';
import clamp from '../number/clamp';
import safeAssign from '../object/safeAssign';
@ -156,7 +157,7 @@ export default class Sortable {
attachClickEvent(document.body, cancelEvent, {capture: true, once: true});
}
if(rootScope.settings.animationsEnabled) {
if(liteMode.isAvailable('animations')) {
await pause(250);
}

View File

@ -13,6 +13,7 @@ import safeAssign from './object/safeAssign';
import appNavigationController, {NavigationItem} from '../components/appNavigationController';
import findUpClassName from './dom/findUpClassName';
import rootScope from '../lib/rootScope';
import liteMode from './liteMode';
const KEEP_OPEN = false;
const TOGGLE_TIMEOUT = 200;
@ -171,7 +172,7 @@ export default class DropdownHover extends EventListenerBase<{
return;
}
const delay = IS_TOUCH_SUPPORTED || !rootScope.settings.animationsEnabled ? 0 : ANIMATION_DURATION;
const delay = IS_TOUCH_SUPPORTED || !liteMode.isAvailable('animations') ? 0 : ANIMATION_DURATION;
if((this.element.style.display && enable === undefined) || enable) {
const res = this.dispatchResultableEvent('open');
await Promise.all(res);

View File

@ -11,6 +11,7 @@ import {fastRafPromise} from './schedulers';
import {animateSingle, cancelAnimationByKey} from './animation';
import rootScope from '../lib/rootScope';
import isInDOM from './dom/isInDOM';
import liteMode from './liteMode';
const MIN_JS_DURATION = 250;
const MAX_JS_DURATION = 600;
@ -58,7 +59,7 @@ export default function fastSmoothScroll(options: ScrollOptions) {
options.axis ??= 'y';
// return;
if(!rootScope.settings.animationsEnabled || options.forceDuration === 0) {
if(!liteMode.isAvailable('animations') || options.forceDuration === 0) {
options.forceDirection = FocusDirection.Static;
}

24
src/helpers/liteMode.ts Normal file
View File

@ -0,0 +1,24 @@
/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import {MOUNT_CLASS_TO} from '../config/debug';
import rootScope from '../lib/rootScope';
export type LiteModeKey = 'all' | 'gif' | 'video' |
'emoji' | 'emoji_panel' | 'emoji_messages' |
'effects' | 'effects_reactions' | 'effects_premiumstickers' | 'effects_emoji' |
'stickers' | 'stickers_panel' | 'stickers_chat' |
'chat' | 'chat_background' | 'chat_spoilers' | 'animations';
export class LiteMode {
public isAvailable(key: LiteModeKey) {
return !rootScope.settings.liteMode.all && !rootScope.settings.liteMode[key];
}
}
const liteMode = new LiteMode();
MOUNT_CLASS_TO && (MOUNT_CLASS_TO.liteMode = liteMode);
export default liteMode;

View File

@ -115,6 +115,17 @@ const lang = {
'Link.Available': 'Link is available',
'Link.Taken': 'Link is already taken',
'Link.Invalid': 'Link is invalid',
'LiteMode.Key.chat.Title': 'Chat Animations',
'LiteMode.Key.chat_background.Title': 'Background rotation',
'LiteMode.Key.chat_spoilers.Title': 'Animated spoiler effect',
'LiteMode.Key.stickers_panel.Title': 'Autoplay in panel',
'LiteMode.Key.stickers_chat.Title': 'Autoplay in chat',
'LiteMode.Key.emoji_panel.Title': 'Autoplay in panel',
'LiteMode.Key.emoji_messages.Title': 'Autoplay in messages',
'LiteMode.Key.effects.Title': 'Interactive Effects',
'LiteMode.Key.effects_reactions.Title': 'Reaction effect',
'LiteMode.Key.effects_premiumstickers.Title': 'Premium stickers effect',
'LiteMode.Key.effects_emoji.Title': 'Emoji effect',
'StickersTab.SearchPlaceholder': 'Search Stickers',
'ForwardedFrom': 'Forwarded from %s',
'Popup.Avatar.Title': 'Drag to Reposition',
@ -1178,6 +1189,20 @@ const lang = {
'other_value': 'last seen %d hours ago'
},
'Login.Register.LastName.Placeholder': 'Last Name',
'LiteMode.Title': 'Power Saving',
'LiteMode.Key.emoji.Title': 'Emoji Animations',
'LiteMode.Key.gif.Title': 'Autoplay GIFs',
'LiteMode.Key.video.Title': 'Autoplay Videos',
'LiteMode.Key.stickers.Title': 'Sticker Animations',
'LiteMode.Key.animations.Title': 'Interface Animations',
// 'LiteMode.Key.emoji.Info': 'Loop animated emoji in messages, reactions and statuses.',
// 'LiteMode.Key.gif.Info': 'Autoplay and loop GIFs in chats and in the keyboard.',
// 'LiteMode.Key.video.Info': 'Autoplay and loop videos and video messages in chats.',
// 'LiteMode.Key.stickers.Info': 'Loop animated stickers, in chats and in the keyboard.',
// 'LiteMode.Key.animations.Info': 'Other animations that make Telegram look amazing.',
'LiteMode.Info': 'Reduce all power-intensive animations and improve performance.',
'LiteMode.EnableText': 'Power Saving Mode',
'LiteMode.DisableAlert': 'Disable Power Saving Mode',
'Message.Context.Select': 'Select',
'Message.Context.Pin': 'Pin',
'Message.Context.Unpin': 'Unpin',

View File

@ -105,6 +105,8 @@ import PopupForward from '../../components/popups/forward';
import AppBackgroundTab from '../../components/sidebarLeft/tabs/background';
import partition from '../../helpers/array/partition';
import indexOfAndSplice from '../../helpers/array/indexOfAndSplice';
import liteMode, {LiteModeKey} from '../../helpers/liteMode';
import RLottiePlayer from '../rlottie/rlottiePlayer';
export type ChatSavedPosition = {
mids: number[],
@ -458,6 +460,29 @@ export class AppImManager extends EventListenerBase<{
});
};
document.addEventListener('mousemove', (e) => {
const mediaStickerWrapper = findUpClassName(e.target, 'media-sticker-wrapper');
if(!mediaStickerWrapper ||
mediaStickerWrapper.classList.contains('custom-emoji') ||
findUpClassName(e.target, 'emoji-big')) {
return;
}
const animations = animationIntersector.getAnimations(mediaStickerWrapper);
animations?.forEach((animationItem) => {
const {liteModeKey, animation} = animationItem;
if(!liteModeKey || !animation?.paused || liteMode.isAvailable(liteModeKey)) {
return;
}
if(animation instanceof RLottiePlayer) {
animation.playOrRestart();
} else {
animation.play();
}
});
});
rootScope.addEventListener('sticker_updated', ({type, faved}) => {
if(type === 'faved') {
toastNew({
@ -1006,7 +1031,7 @@ export class AppImManager extends EventListenerBase<{
private toggleChatGradientAnimation(activatingChat: Chat) {
this.chats.forEach((chat) => {
if(chat.gradientRenderer) {
chat.gradientRenderer.scrollAnimate(rootScope.settings.animationsEnabled && chat === activatingChat);
chat.gradientRenderer.scrollAnimate(liteMode.isAvailable('animations') && chat === activatingChat);
}
});
}
@ -1639,18 +1664,21 @@ export class AppImManager extends EventListenerBase<{
});
}
document.body.classList.toggle('animation-level-0', !rootScope.settings.animationsEnabled);
document.body.classList.toggle('animation-level-0', !liteMode.isAvailable('animations'));
document.body.classList.toggle('animation-level-1', false);
document.body.classList.toggle('animation-level-2', rootScope.settings.animationsEnabled);
document.body.classList.toggle('animation-level-2', liteMode.isAvailable('animations'));
this.chatsSelectTabDebounced = debounce(() => {
const topbar = this.chat.topbar;
topbar.pinnedMessage?.setCorrectIndex(0); // * буду молиться богам, чтобы это ничего не сломало, но это исправляет получение пиннеда после анимации
this.managers.apiFileManager.setQueueId(this.chat.bubbles.lazyLoadQueue.queueId);
}, rootScope.settings.animationsEnabled ? 250 : 0, false, true);
}, liteMode.isAvailable('animations') ? 250 : 0, false, true);
if(lottieLoader.setLoop(rootScope.settings.stickers.loop)) {
const c: LiteModeKey[] = ['stickers_chat', 'stickers_panel'];
const changedLoop = lottieLoader.setLoop(rootScope.settings.stickers.loop);
const changedAutoplay = !!c.filter((key) => lottieLoader.setAutoplay(liteMode.isAvailable(key), key)).length;
if(changedLoop || changedAutoplay) {
animationIntersector.checkAnimations2(false);
}
@ -1679,7 +1707,7 @@ export class AppImManager extends EventListenerBase<{
this.chatsSelectTabDebounced();
// ! нужно переделать на animation, так как при лаге анимация будет длиться не 250мс
if(rootScope.settings.animationsEnabled && animate !== false) {
if(liteMode.isAvailable('animations') && animate !== false) {
dispatchHeavyAnimationEvent(pause(250 + 150), 250 + 150);
}
@ -1927,11 +1955,11 @@ export class AppImManager extends EventListenerBase<{
this.log('selectTab', id, prevTabId);
let animationPromise: Promise<any> = rootScope.settings.animationsEnabled ? doubleRaf() : Promise.resolve();
let animationPromise: Promise<any> = liteMode.isAvailable('animations') ? doubleRaf() : Promise.resolve();
if(
prevTabId !== undefined &&
prevTabId !== id &&
rootScope.settings.animationsEnabled &&
liteMode.isAvailable('animations') &&
animate !== false/* &&
mediaSizes.activeScreen !== ScreenSize.large */
) {

View File

@ -87,7 +87,11 @@ export class CustomEmojiElement extends HTMLElement {
// }
if(this.player) {
animationIntersector.addAnimation(this, this.renderer.animationGroup, undefined, true);
animationIntersector.addAnimation({
animation: this,
group: this.renderer.animationGroup,
controlled: true
});
}
// this.connectedCallback = undefined;
@ -185,7 +189,7 @@ export class CustomEmojiElement extends HTMLElement {
this.paused = false;
if(this.player instanceof HTMLVideoElement) {
this.player.currentTime = this.renderer.lastPausedVideo?.currentTime || this.player.currentTime;
this.player.currentTime = this.renderer.lastPausedVideo?.currentTime ?? this.player.currentTime;
this.player.play().catch(noop);
}
@ -206,6 +210,10 @@ export class CustomEmojiElement extends HTMLElement {
public get autoplay() {
return true;
}
public get loop() {
return true;
}
}
type CustomEmojiElements = Set<CustomEmojiElement>;
@ -648,7 +656,11 @@ export class CustomEmojiRendererElement extends HTMLElement {
}
if(element.isConnected) {
animationIntersector.addAnimation(element, element.renderer.animationGroup, undefined, true);
animationIntersector.addAnimation({
animation: element,
group: element.renderer.animationGroup,
controlled: true
});
}
});

View File

@ -4,6 +4,7 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import type {LiteModeKey} from '../../helpers/liteMode';
import animationIntersector, {AnimationItemGroup} from '../../components/animationIntersector';
import {MOUNT_CLASS_TO} from '../../config/debug';
import pause from '../../helpers/schedulers/pause';
@ -45,6 +46,20 @@ export class LottieLoader {
return null;
}
public setAutoplay(play: boolean, liteModeKey: LiteModeKey) {
let changed = false;
for(const i in this.players) {
const player = this.players[i];
if(player.liteModeKey === liteModeKey) {
changed = true;
player.autoplay = play ? !!+player.el[0].dataset.stickerPlay : false;
player.loop = play ? !!+player.el[0].dataset.stickerLoop : false;
}
}
return changed;
}
public setLoop(loop: boolean) {
let changed = false;
for(const i in this.players) {
@ -196,7 +211,12 @@ export class LottieLoader {
const player = this.initPlayer(containers, params);
animationIntersector.addAnimation(player, group, undefined, middleware);
animationIntersector.addAnimation({
animation: player,
group,
controlled: middleware,
liteModeKey: params.liteModeKey
});
return player;
}

View File

@ -4,6 +4,7 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import liteMode from '../../helpers/liteMode';
import noop from '../../helpers/noop';
import safeAssign from '../../helpers/object/safeAssign';
import rootScope from '../rootScope';
@ -181,7 +182,7 @@ export default class RLottieIcon {
const part = item.getPart(index);
item.player.playPart({
from: rootScope.settings.animationsEnabled && !this.skipAnimation ? part.startFrame : part.endFrame,
from: liteMode.isAvailable('animations') && !this.skipAnimation ? part.startFrame : part.endFrame,
to: part.endFrame,
callback
});

View File

@ -6,6 +6,7 @@
import type {AnimationItemGroup, AnimationItemWrapper} from '../../components/animationIntersector';
import type {Middleware} from '../../helpers/middleware';
import type {LiteModeKey} from '../../helpers/liteMode';
import CAN_USE_TRANSFERABLES from '../../environment/canUseTransferables';
import IS_APPLE_MX from '../../environment/appleMx';
import {IS_ANDROID, IS_APPLE_MOBILE, IS_APPLE, IS_SAFARI} from '../../environment/userAgent';
@ -35,7 +36,8 @@ export type RLottieOptions = {
name?: string,
skipFirstFrameRendering?: boolean,
toneIndex?: number,
sync?: boolean
sync?: boolean,
liteModeKey?: LiteModeKey
};
export type RLottieColor = [number, number, number];
@ -92,6 +94,7 @@ export default class RLottiePlayer extends EventListenerBase<{
public loop: number | boolean = true;
public _loop: RLottiePlayer['loop']; // ! will be used to store original value for settings.stickers.loop
public group: AnimationItemGroup = '';
public liteModeKey: LiteModeKey;
private frInterval: number;
private frThen: number;
@ -153,6 +156,7 @@ export default class RLottiePlayer extends EventListenerBase<{
this.skipFirstFrameRendering = options.skipFirstFrameRendering;
this.toneIndex = options.toneIndex;
this.raw = this.color !== undefined;
this.liteModeKey = options.liteModeKey;
if(this.name) {
this.cacheName = RLottiePlayer.CACHE.generateName(this.name, this.width, this.height, this.color, this.toneIndex);
@ -284,6 +288,18 @@ export default class RLottiePlayer extends EventListenerBase<{
this.play();
}
public playOrRestart() {
if(!this.paused) {
return;
}
if(this.curFrame === this.maxFrame) {
this.restart();
} else {
this.play();
}
}
public setSpeed(speed: number) {
if(this.speed === speed) {
return;

View File

@ -66,9 +66,9 @@
left: -15%;
background-color: var(--primary-color);
transform: scale(1);
transform: scale(0);
border-radius: 50%;
transition: transform .2s 0s ease-in-out;
transition: transform .2s .05s ease-in-out;
@include animation-level(0) {
transition: none !important;
@ -87,10 +87,10 @@
stroke: #fff;
stroke-width: 3.75;
stroke-linecap: round;
stroke-dasharray: 24.19, 24.19;
stroke-dasharray: 0, 24.19;
stroke-dashoffset: 0;
transition: stroke-dasharray .1s .15s ease-in-out, visibility 0s .15s;
visibility: visible; // fix blinking on parent's transform
transition: stroke-dasharray .1s ease-in-out, visibility 0s .1s;
visibility: hidden; // fix blinking on parent's transform
@include animation-level(0) {
transition: none !important;
@ -287,18 +287,18 @@
}
.checkbox-field .checkbox-field-input {
&:not(:checked) + .checkbox-box {
&:checked:not(.is-fake-disabled) + .checkbox-box {
.checkbox-box-check {
use {
stroke-dasharray: 0, 24.19;
visibility: hidden;
transition: stroke-dasharray .1s ease-in-out, visibility 0s .1s;
stroke-dasharray: 24.19, 24.19;
visibility: visible;
transition: stroke-dasharray .1s .15s ease-in-out, visibility 0s .15s;
}
}
.checkbox-box-background {
transition: transform .2s .05s ease-in-out;
transform: scale(0);
transition: transform .2s 0s ease-in-out;
transform: scale(1);
}
}
@ -402,7 +402,7 @@
}
}
[type="checkbox"]:checked + .checkbox-toggle {
[type="checkbox"]:checked:not(.is-fake-disabled) + .checkbox-toggle {
background-color: var(--primary-color);
&:before {

View File

@ -1204,6 +1204,12 @@
}
}
.power-saving-container {
.row.is-disabled {
}
}
.empty-placeholder {
// left: 50%;
// transform: translate(-50%, -50%);