Browse Source

Background color

master
Eduard Kuzmenko 4 years ago
parent
commit
57790f3d2e
  1. 26
      src/components/chat/chat.ts
  2. 336
      src/components/colorPicker.ts
  3. 38
      src/components/sidebarLeft/tabs/background.ts
  4. 142
      src/components/sidebarLeft/tabs/backgroundColor.ts
  5. 27
      src/components/sidebarLeft/tabs/privacyAndSecurity.ts
  6. 12
      src/components/sliderTab.ts
  7. 2
      src/config/app.ts
  8. 85
      src/helpers/color.ts
  9. 13
      src/helpers/highlightningColor.ts
  10. 5
      src/lang.ts
  11. 10
      src/lib/appManagers/appImManager.ts
  12. 35
      src/scss/partials/_colorPicker.scss
  13. 8
      src/scss/partials/_leftSidebar.scss
  14. 1
      src/scss/style.scss

26
src/components/chat/chat.ts

@ -83,10 +83,22 @@ export default class Chat extends EventListenerBase<{
} }
public setBackground(url: string): Promise<void> { public setBackground(url: string): Promise<void> {
const item = document.createElement('div');
item.classList.add('chat-background-item');
const theme = rootScope.settings.themes.find(t => t.name === rootScope.settings.theme); const theme = rootScope.settings.themes.find(t => t.name === rootScope.settings.theme);
let item: HTMLElement;
if(theme.background.type === 'color' && document.documentElement.style.cursor === 'grabbing') {
const _item = this.backgroundEl.lastElementChild as HTMLElement;
if(_item && _item.dataset.type === theme.background.type) {
item = _item;
}
}
if(!item) {
item = document.createElement('div');
item.classList.add('chat-background-item');
item.dataset.type = theme.background.type;
}
if(theme.background.type === 'color') { if(theme.background.type === 'color') {
item.style.backgroundColor = theme.background.color; item.style.backgroundColor = theme.background.color;
item.style.backgroundImage = 'none'; item.style.backgroundImage = 'none';
@ -94,7 +106,13 @@ export default class Chat extends EventListenerBase<{
return new Promise<void>((resolve) => { return new Promise<void>((resolve) => {
const cb = () => { const cb = () => {
const prev = this.backgroundEl.children[this.backgroundEl.childElementCount - 1] as HTMLElement; const prev = this.backgroundEl.lastElementChild as HTMLElement;
if(prev === item) {
resolve();
return;
}
this.backgroundEl.append(item); this.backgroundEl.append(item);
// * одного недостаточно, при обновлении страницы все равно фон появляется неплавно // * одного недостаточно, при обновлении страницы все равно фон появляется неплавно

336
src/components/colorPicker.ts

@ -0,0 +1,336 @@
import { ColorHsla, hexaToHsla, hslaToRgba, rgbaToHexa as rgbaToHexa, rgbaToHsla } from "../helpers/color";
import { clamp } from "../helpers/number";
import InputField, { InputState } from "./inputField";
type EventPosition = {x: number, y: number, isTouch?: boolean};
export type ColorPickerColor = {
hsl: string;
rgb: string;
hex: string;
hsla: string;
rgba: string;
hexa: string;
rgbaArray: import("f:/tweb/src/helpers/color").ColorRgba;
};
export default class ColorPicker {
private static BASE_CLASS = 'color-picker';
public container: HTMLElement;
private boxRect: DOMRect;
//private boxDraggerRect: DOMRect;
private hueRect: DOMRect;
//private hueDraggerRect: DOMRect;
private hue = 0;
private saturation = 100;
private lightness = 50;
private alpha = 1;
private elements: {
box: SVGSVGElement,
boxDragger: SVGSVGElement,
sliders: HTMLElement,
hue: SVGSVGElement,
hueDragger: SVGSVGElement,
saturation: SVGLinearGradientElement,
} = {} as any;
private hexInputField: InputField;
private rgbInputField: InputField;
public onChange: (color: ReturnType<ColorPicker['getCurrentColor']>) => void;
constructor() {
this.container = document.createElement('div');
this.container.classList.add(ColorPicker.BASE_CLASS);
const html = `
<svg class="${ColorPicker.BASE_CLASS + '-box'}" viewBox="0 0 380 198">
<defs>
<linearGradient id="color-picker-saturation" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stop-color="#fff"></stop>
<stop offset="100%" stop-color="hsl(0,100%,50%)"></stop>
</linearGradient>
<linearGradient id="color-picker-brightness" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="rgba(0,0,0,0)"></stop>
<stop offset="100%" stop-color="#000"></stop>
</linearGradient>
<pattern id="color-picker-pattern" width="100%" height="100%">
<rect x="0" y="0" width="100%" height="100%" fill="url(#color-picker-saturation)"></rect>
<rect x="0" y="0" width="100%" height="100%" fill="url(#color-picker-brightness)"></rect>
</pattern>
</defs>
<rect rx="10" ry="10" x="0" y="0" width="380" height="198" fill="url(#color-picker-pattern)"></rect>
<svg class="${ColorPicker.BASE_CLASS + '-dragger'} ${ColorPicker.BASE_CLASS + '-box-dragger'}" x="0" y="0">
<circle r="11" fill="inherit" stroke="#fff" stroke-width="2"></circle>
</svg>
</svg>
<div class="${ColorPicker.BASE_CLASS + '-sliders'}">
<svg class="${ColorPicker.BASE_CLASS + '-color-slider'}" viewBox="0 0 380 24">
<defs>
<linearGradient id="hue" x1="100%" y1="0%" x2="0%" y2="0%">
<stop offset="0%" stop-color="#f00"></stop>
<stop offset="16.666%" stop-color="#f0f"></stop>
<stop offset="33.333%" stop-color="#00f"></stop>
<stop offset="50%" stop-color="#0ff"></stop>
<stop offset="66.666%" stop-color="#0f0"></stop>
<stop offset="83.333%" stop-color="#ff0"></stop>
<stop offset="100%" stop-color="#f00"></stop>
</linearGradient>
</defs>
<rect rx="4" ry="4" x="0" y="9" width="380" height="8" fill="url(#hue)"></rect>
<svg class="${ColorPicker.BASE_CLASS + '-dragger'} ${ColorPicker.BASE_CLASS + '-color-slider-dragger'}" x="0" y="13">
<circle r="11" fill="inherit" stroke="#fff" stroke-width="2"></circle>
</svg>
</svg>
</div>
`;
this.container.innerHTML = html;
this.elements.box = this.container.firstElementChild as any;
this.elements.boxDragger = this.elements.box.lastElementChild as any;
this.elements.saturation = this.elements.box.firstElementChild.firstElementChild as any;
this.elements.sliders = this.elements.box.nextElementSibling as any;
this.elements.hue = this.elements.sliders.firstElementChild as any;
this.elements.hueDragger = this.elements.hue.lastElementChild as any;
this.hexInputField = new InputField({plainText: true, label: 'Appearance.Color.Hex'});
this.rgbInputField = new InputField({plainText: true, label: 'Appearance.Color.RGB'});
const inputs = document.createElement('div');
inputs.className = ColorPicker.BASE_CLASS + '-inputs';
inputs.append(this.hexInputField.container, this.rgbInputField.container);
this.container.append(inputs);
this.hexInputField.input.addEventListener('input', () => {
let value = this.hexInputField.value.replace(/#/g, '').slice(0, 6);
const match = value.match(/([a-fA-F\d]+)/);
const valid = match && match[0].length === value.length && [/* 3, 4, */6].includes(value.length);
this.hexInputField.setState(valid ? InputState.Neutral : InputState.Error);
value = '#' + value;
this.hexInputField.setValueSilently(value);
if(valid) {
this.setColor(value, false, true);
}
});
// patched https://stackoverflow.com/a/34029238/6758968
const rgbRegExp = /^(?:rgb)?\(?([01]?\d\d?|2[0-4]\d|25[0-5])(?:\W+)([01]?\d\d?|2[0-4]\d|25[0-5])\W+(?:([01]?\d\d?|2[0-4]\d|25[0-5])\)?)$/;
this.rgbInputField.input.addEventListener('input', () => {
const match = this.rgbInputField.value.match(rgbRegExp);
this.rgbInputField.setState(match ? InputState.Neutral : InputState.Error);
if(match) {
this.setColor(rgbaToHsla(+match[1], +match[2], +match[3]), true, false);
}
});
this.attachBoxListeners();
this.attachHueListeners();
}
private attachGrabListeners(element: SVGSVGElement, onStart: (position: EventPosition) => void, onMove: (position: EventPosition) => void, onEnd: (position: EventPosition) => void) {
// * Mouse
const onMouseMove = (event: MouseEvent) => {
onMove({x: event.pageX, y: event.pageY});
};
const onMouseDown = (event: MouseEvent) => {
if(event.button !== 0) {
element.addEventListener('mousedown', onMouseDown, {once: true});
return;
}
this.onGrabStart();
onStart({x: event.pageX, y: event.pageY});
onMouseMove(event);
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', () => {
document.removeEventListener('mousemove', onMouseMove);
element.addEventListener('mousedown', onMouseDown, {once: true});
this.onGrabEnd();
onEnd && onEnd({x: event.pageX, y: event.pageY});
}, {once: true});
};
element.addEventListener('mousedown', onMouseDown, {once: true});
// * Touch
const onTouchMove = (event: TouchEvent) => {
event.preventDefault();
onMove({x: event.touches[0].clientX, y: event.touches[0].clientY, isTouch: true});
};
const onTouchStart = (event: TouchEvent) => {
this.onGrabStart();
onStart({x: event.touches[0].clientX, y: event.touches[0].clientY, isTouch: true});
onTouchMove(event);
document.addEventListener('touchmove', onTouchMove, {passive: false});
document.addEventListener('touchend', (event) => {
document.removeEventListener('touchmove', onTouchMove);
element.addEventListener('touchstart', onTouchStart, {passive: true, once: true});
this.onGrabEnd();
onEnd && onEnd({x: event.touches[0].clientX, y: event.touches[0].clientY, isTouch: true});
}, {passive: true, once: true});
};
element.addEventListener('touchstart', onTouchStart, {passive: true, once: true});
}
private onGrabStart = () => {
document.documentElement.style.cursor = this.elements.boxDragger.style.cursor = 'grabbing';
};
private onGrabEnd = () => {
document.documentElement.style.cursor = this.elements.boxDragger.style.cursor = '';
};
private attachBoxListeners() {
this.attachGrabListeners(this.elements.box, () => {
this.boxRect = this.elements.box.getBoundingClientRect();
//this.boxDraggerRect = this.elements.boxDragger.getBoundingClientRect();
}, (pos) => {
this.saturationHandler(pos.x, pos.y);
}, null);
}
private attachHueListeners() {
this.attachGrabListeners(this.elements.hue, () => {
this.hueRect = this.elements.hue.getBoundingClientRect();
//this.hueDraggerRect = this.elements.hueDragger.getBoundingClientRect();
}, (pos) => {
this.hueHandler(pos.x);
}, null);
}
public setColor(color: ColorHsla | string, updateHexInput = true, updateRgbInput = true) {
if(color === undefined) { // * set to red
color = {
h: 0,
s: 100,
l: 50,
a: 1
};
} else if(typeof(color) === 'string') {
if(color[0] === '#') {
color = hexaToHsla(color);
} else {
const rgb = color.match(/[.?\d]+/g);
color = rgbaToHsla(+rgb[0], +rgb[1], +rgb[2], rgb[3] === undefined ? 1 : +rgb[3]);
}
}
// Set box
this.boxRect = this.elements.box.getBoundingClientRect();
const boxX = this.boxRect.width / 100 * color.s;
const percentY = 100 - (color.l / (100 - color.s / 2)) * 100;
const boxY = this.boxRect.height / 100 * percentY;
this.saturationHandler(this.boxRect.left + boxX, this.boxRect.top + boxY, false);
// Set hue
this.hueRect = this.elements.hue.getBoundingClientRect();
const percentHue = color.h / 360;
const hueX = this.hueRect.left + this.hueRect.width * percentHue;
this.hueHandler(hueX, false);
// Set values
this.hue = color.h;
this.saturation = color.s;
this.lightness = color.l;
this.alpha = color.a;
this.updatePicker(updateHexInput, updateRgbInput);
};
public getCurrentColor(): ColorPickerColor {
const rgbaArray = hslaToRgba(this.hue, this.saturation, this.lightness, this.alpha);
const hexa = rgbaToHexa(rgbaArray);
const hex = hexa.slice(0, -2);
return {
hsl: `hsl(${this.hue}, ${this.saturation}%, ${this.lightness}%)`,
rgb: `rgb(${rgbaArray[0]}, ${rgbaArray[1]}, ${rgbaArray[2]})`,
hex: hex,
hsla: `hsla(${this.hue}, ${this.saturation}%, ${this.lightness}%, ${this.alpha})`,
rgba: `rgba(${rgbaArray[0]}, ${rgbaArray[1]}, ${rgbaArray[2]}, ${rgbaArray[3]})`,
hexa: hexa,
rgbaArray: rgbaArray
};
}
public updatePicker(updateHexInput = true, updateRgbInput = true) {
const color = this.getCurrentColor();
this.elements.boxDragger.setAttributeNS(null, 'fill', color.hex);
if(updateHexInput) {
this.hexInputField.setValueSilently(color.hex);
this.hexInputField.setState(InputState.Neutral);
}
if(updateRgbInput) {
this.rgbInputField.setValueSilently(color.rgbaArray.slice(0, -1).join(', '));
this.rgbInputField.setState(InputState.Neutral);
}
if(this.onChange) {
this.onChange(color);
}
}
private hueHandler(pageX: number, update = true) {
const eventX = clamp(pageX - this.hueRect.left, 0, this.hueRect.width);
const percents = eventX / this.hueRect.width;
this.hue = Math.round(360 * percents);
const hsla = `hsla(${this.hue}, 100%, 50%, ${this.alpha})`;
this.elements.hueDragger.setAttributeNS(null, 'x', (percents * 100) + '%');
this.elements.hueDragger.setAttributeNS(null, 'fill', hsla);
this.elements.saturation.lastElementChild.setAttributeNS(null, 'stop-color', hsla);
if(update) {
this.updatePicker();
}
}
private saturationHandler(pageX: number, pageY: number, update = true) {
const maxX = this.boxRect.width;
const maxY = this.boxRect.height;
const eventX = clamp(pageX - this.boxRect.left, 0, maxX);
const eventY = clamp(pageY - this.boxRect.top, 0, maxY);
const posX = eventX / maxX * 100;
const posY = eventY / maxY * 100;
const boxDragger = this.elements.boxDragger;
boxDragger.setAttributeNS(null, 'x', posX + '%');
boxDragger.setAttributeNS(null, 'y', posY + '%');
const saturation = clamp(posX, 0, 100);
const lightnessX = 100 - saturation / 2;
const lightnessY = 100 - clamp(posY, 0, 100);
const lightness = clamp(lightnessY / 100 * lightnessX, 0, 100);
this.saturation = saturation;
this.lightness = lightness;
if(update) {
this.updatePicker();
}
};
}

38
src/components/sidebarLeft/tabs/background.ts

@ -8,31 +8,49 @@ import { generateSection } from "..";
import { averageColor } from "../../../helpers/averageColor"; import { averageColor } from "../../../helpers/averageColor";
import blur from "../../../helpers/blur"; import blur from "../../../helpers/blur";
import { deferredPromise } from "../../../helpers/cancellablePromise"; import { deferredPromise } from "../../../helpers/cancellablePromise";
import { highlightningColor } from "../../../helpers/color";
import { attachClickEvent } from "../../../helpers/dom"; import { attachClickEvent } from "../../../helpers/dom";
import findUpClassName from "../../../helpers/dom/findUpClassName"; import findUpClassName from "../../../helpers/dom/findUpClassName";
import highlightningColor from "../../../helpers/highlightningColor";
import { copy } from "../../../helpers/object";
import { AccountWallPapers, WallPaper } from "../../../layer"; import { AccountWallPapers, WallPaper } from "../../../layer";
import appDocsManager, { MyDocument } from "../../../lib/appManagers/appDocsManager"; import appDocsManager, { MyDocument } from "../../../lib/appManagers/appDocsManager";
import appDownloadManager from "../../../lib/appManagers/appDownloadManager"; import appDownloadManager from "../../../lib/appManagers/appDownloadManager";
import appImManager from "../../../lib/appManagers/appImManager"; import appImManager from "../../../lib/appManagers/appImManager";
import appStateManager from "../../../lib/appManagers/appStateManager"; import appStateManager, { STATE_INIT } from "../../../lib/appManagers/appStateManager";
import apiManager from "../../../lib/mtproto/mtprotoworker"; import apiManager from "../../../lib/mtproto/mtprotoworker";
import rootScope from "../../../lib/rootScope"; import rootScope from "../../../lib/rootScope";
import Button from "../../button";
import CheckboxField from "../../checkboxField"; import CheckboxField from "../../checkboxField";
import ProgressivePreloader from "../../preloader"; import ProgressivePreloader from "../../preloader";
import { SliderSuperTab } from "../../slider"; import { SliderSuperTab } from "../../slider";
import { wrapPhoto } from "../../wrappers"; import { wrapPhoto } from "../../wrappers";
import AppBackgroundColorTab from "./backgroundColor";
export default class AppBackgroundTab extends SliderSuperTab { export default class AppBackgroundTab extends SliderSuperTab {
init() { init() {
this.container.classList.add('background-container'); this.container.classList.add('background-container', 'background-image-container');
this.setTitle('ChatBackground'); this.setTitle('ChatBackground');
{ {
const container = generateSection(this.scrollable); const container = generateSection(this.scrollable);
//const uploadButton = Button('btn-primary btn-transparent', {icon: 'cameraadd', text: 'ChatBackground.UploadWallpaper', disabled: true}); //const uploadButton = Button('btn-primary btn-transparent', {icon: 'cameraadd', text: 'ChatBackground.UploadWallpaper', disabled: true});
//const colorButton = Button('btn-primary btn-transparent', {icon: 'colorize', text: 'ChatBackground.SetColor', disabled: true}); const colorButton = Button('btn-primary btn-transparent', {icon: 'colorize', text: 'SetColor'});
const resetButton = Button('btn-primary btn-transparent', {icon: 'favourites', text: 'Appearance.Reset'});
attachClickEvent(colorButton, () => {
new AppBackgroundColorTab(this.slider).open();
}, {listenerSetter: this.listenerSetter});
attachClickEvent(resetButton, () => {
const defaultTheme = STATE_INIT.settings.themes.find(t => t.name === theme.name);
if(defaultTheme) {
++tempId;
theme.background = copy(defaultTheme.background);
appStateManager.pushToState('settings', rootScope.settings);
appImManager.applyCurrentTheme(undefined, undefined, true);
}
}, {listenerSetter: this.listenerSetter});
const theme = rootScope.settings.themes.find(t => t.name === rootScope.settings.theme); const theme = rootScope.settings.themes.find(t => t.name === rootScope.settings.theme);
const blurCheckboxField = new CheckboxField({ const blurCheckboxField = new CheckboxField({
@ -41,7 +59,8 @@ export default class AppBackgroundTab extends SliderSuperTab {
checked: theme.background.blur, checked: theme.background.blur,
withRipple: true withRipple: true
}); });
blurCheckboxField.input.addEventListener('change', () => {
this.listenerSetter.add(blurCheckboxField.input, 'change', () => {
const active = grid.querySelector('.active') as HTMLElement; const active = grid.querySelector('.active') as HTMLElement;
if(!active) return; if(!active) return;
@ -54,7 +73,7 @@ export default class AppBackgroundTab extends SliderSuperTab {
}, 100); }, 100);
}); });
container.append(/* uploadButton, colorButton, */blurCheckboxField.label); container.append(/* uploadButton, */colorButton, resetButton, blurCheckboxField.label);
} }
const grid = document.createElement('div'); const grid = document.createElement('div');
@ -92,7 +111,7 @@ export default class AppBackgroundTab extends SliderSuperTab {
return; return;
} }
const hsla = highlightningColor(pixel); const hsla = highlightningColor(Array.from(pixel) as any);
//console.log(doc, hsla, performance.now() - perf); //console.log(doc, hsla, performance.now() - perf);
background.slug = slug; background.slug = slug;
@ -208,15 +227,16 @@ export default class AppBackgroundTab extends SliderSuperTab {
attachClickEvent(target, (e) => { attachClickEvent(target, (e) => {
if(preloader.preloader.parentElement) { if(preloader.preloader.parentElement) {
preloader.onClick(e); preloader.onClick(e);
preloader.detach();
} else { } else {
load(); load();
} }
}); }, {listenerSetter: this.listenerSetter});
load(); load();
//console.log(doc); //console.log(doc);
}); }, {listenerSetter: this.listenerSetter});
//console.log(accountWallpapers); //console.log(accountWallpapers);
}); });

142
src/components/sidebarLeft/tabs/backgroundColor.ts

@ -0,0 +1,142 @@
import { SettingSection } from "..";
import { hexaToRgba } from "../../../helpers/color";
import { attachClickEvent } from "../../../helpers/dom";
import findUpClassName from "../../../helpers/dom/findUpClassName";
import highlightningColor from "../../../helpers/highlightningColor";
import { throttle } from "../../../helpers/schedulers";
import appImManager from "../../../lib/appManagers/appImManager";
import appStateManager from "../../../lib/appManagers/appStateManager";
import rootScope from "../../../lib/rootScope";
import ColorPicker, { ColorPickerColor } from "../../colorPicker";
import { SliderSuperTab } from "../../slider";
export default class AppBackgroundColorTab extends SliderSuperTab {
private colorPicker: ColorPicker;
private grid: HTMLElement;
private applyColor: (hex: string, updateColorPicker?: boolean) => void;
init() {
this.container.classList.add('background-container', 'background-color-container');
this.setTitle('SetColor');
const section = new SettingSection({});
this.colorPicker = new ColorPicker();
section.content.append(this.colorPicker.container);
this.scrollable.append(section.container);
const grid = this.grid = document.createElement('div');
grid.classList.add('grid');
const colors = [
'#E6EBEE',
'#B2CEE1',
'#008DD0',
'#C6E7CB',
'#C4E1A6',
'#60B16E',
'#CCD0AF',
'#A6A997',
'#7A7072',
'#FDD7AF',
'#FDB76E',
'#DD8851'
];
colors.forEach(color => {
const item = document.createElement('div');
item.classList.add('grid-item');
item.dataset.color = color.toLowerCase();
// * need for transform scale
const media = document.createElement('div');
media.classList.add('grid-item-media');
media.style.backgroundColor = color;
item.append(media);
grid.append(item);
});
attachClickEvent(grid, (e) => {
const target = findUpClassName(e.target, 'grid-item');
if(!target || target.classList.contains('active')) {
return;
}
const color = target.dataset.color;
if(!color) {
return;
}
this.applyColor(color);
}, {listenerSetter: this.listenerSetter});
this.scrollable.append(grid);
this.applyColor = throttle(this._applyColor, 16, true);
}
private setActive() {
const active = this.grid.querySelector('.active');
const background = rootScope.settings.themes.find(t => t.name === rootScope.settings.theme).background;
const target = background.type === 'color' ? this.grid.querySelector(`.grid-item[data-color="${background.color}"]`) : null;
if(active === target) {
return;
}
if(active) {
active.classList.remove('active');
}
if(target) {
target.classList.add('active');
}
}
private _applyColor = (hex: string, updateColorPicker = true) => {
if(updateColorPicker) {
this.colorPicker.setColor(hex);
} else {
const rgba = hexaToRgba(hex);
const background = rootScope.settings.themes.find(t => t.name === rootScope.settings.theme).background;
const hsla = highlightningColor(rgba);
background.color = hex.toLowerCase();
background.type = 'color';
background.highlightningColor = hsla;
appStateManager.pushToState('settings', rootScope.settings);
appImManager.applyCurrentTheme(undefined, undefined, true);
this.setActive();
}
};
private onColorChange = (color: ColorPickerColor) => {
this.applyColor(color.hex, false);
};
onOpen() {
setTimeout(() => {
const background = rootScope.settings.themes.find(t => t.name === rootScope.settings.theme).background;
// * set active if type is color
if(background.type === 'color') {
this.colorPicker.onChange = this.onColorChange;
}
this.colorPicker.setColor(background.color || '#cccccc');
if(background.type !== 'color') {
this.colorPicker.onChange = this.onColorChange;
}
}, 0);
}
onCloseAfterTimeout() {
this.colorPicker.onChange = undefined;
this.colorPicker = undefined;
return super.onCloseAfterTimeout();
}
}

27
src/components/sidebarLeft/tabs/privacyAndSecurity.ts

@ -5,7 +5,7 @@
*/ */
import { SliderSuperTab } from "../../slider"; import { SliderSuperTab } from "../../slider";
import { generateSection, SettingSection } from ".."; import { SettingSection } from "..";
import Row from "../../row"; import Row from "../../row";
import { AccountPassword, Authorization, InputPrivacyKey } from "../../../layer"; import { AccountPassword, Authorization, InputPrivacyKey } from "../../../layer";
import appPrivacyManager, { PrivacyType } from "../../../lib/appManagers/appPrivacyManager"; import appPrivacyManager, { PrivacyType } from "../../../lib/appManagers/appPrivacyManager";
@ -26,6 +26,7 @@ import appUsersManager from "../../../lib/appManagers/appUsersManager";
import rootScope from "../../../lib/rootScope"; import rootScope from "../../../lib/rootScope";
import { convertKeyToInputKey } from "../../../helpers/string"; import { convertKeyToInputKey } from "../../../helpers/string";
import { i18n, LangPackKey, _i18n } from "../../../lib/langPack"; import { i18n, LangPackKey, _i18n } from "../../../lib/langPack";
import { replaceContent } from "../../../helpers/dom";
export default class AppPrivacyAndSecurityTab extends SliderSuperTab { export default class AppPrivacyAndSecurityTab extends SliderSuperTab {
private activeSessionsRow: Row; private activeSessionsRow: Row;
@ -35,8 +36,6 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTab {
this.container.classList.add('privacy-container'); this.container.classList.add('privacy-container');
this.setTitle('PrivacySettings'); this.setTitle('PrivacySettings');
const section = generateSection.bind(null, this.scrollable);
const SUBTITLE: LangPackKey = 'Loading'; const SUBTITLE: LangPackKey = 'Loading';
{ {
@ -98,14 +97,11 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTab {
section.content.append(blockedUsersRow.container, twoFactorRow.container, activeSessionsRow.container); section.content.append(blockedUsersRow.container, twoFactorRow.container, activeSessionsRow.container);
this.scrollable.append(section.container); this.scrollable.append(section.container);
let blockedCount: number;
const setBlockedCount = (count: number) => { const setBlockedCount = (count: number) => {
blockedCount = count;
if(count) { if(count) {
_i18n(blockedUsersRow.subtitle, 'PrivacySettingsController.UserCount', [count]); replaceContent(blockedUsersRow.subtitle, i18n('PrivacySettingsController.UserCount', [count]));
} else { } else {
_i18n(blockedUsersRow.subtitle, 'BlockedEmpty'); replaceContent(blockedUsersRow.subtitle, i18n('BlockedEmpty', [count]));
} }
}; };
@ -130,7 +126,7 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTab {
passwordManager.getState().then(state => { passwordManager.getState().then(state => {
passwordState = state; passwordState = state;
_i18n(twoFactorRow.subtitle, state.pFlags.has_password ? 'PrivacyAndSecurity.Item.On' : 'PrivacyAndSecurity.Item.Off'); replaceContent(twoFactorRow.subtitle, i18n(state.pFlags.has_password ? 'PrivacyAndSecurity.Item.On' : 'PrivacyAndSecurity.Item.Off'));
twoFactorRow.freezed = false; twoFactorRow.freezed = false;
//console.log('password state', state); //console.log('password state', state);
@ -140,9 +136,9 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTab {
} }
{ {
const container = section('PrivacyTitle'); const section = new SettingSection({name: 'PrivacyTitle'});
container.classList.add('privacy-navigation-container'); section.content.classList.add('privacy-navigation-container');
const rowsByKeys: Partial<{ const rowsByKeys: Partial<{
[key in InputPrivacyKey['_']]: Row [key in InputPrivacyKey['_']]: Row
@ -152,7 +148,7 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTab {
titleLangKey: 'PrivacyPhoneTitle', titleLangKey: 'PrivacyPhoneTitle',
subtitleLangKey: SUBTITLE, subtitleLangKey: SUBTITLE,
clickable: () => { clickable: () => {
new AppPrivacyPhoneNumberTab(this.slider).open() new AppPrivacyPhoneNumberTab(this.slider).open();
} }
}); });
@ -160,7 +156,7 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTab {
titleLangKey: 'LastSeenTitle', titleLangKey: 'LastSeenTitle',
subtitleLangKey: SUBTITLE, subtitleLangKey: SUBTITLE,
clickable: () => { clickable: () => {
new AppPrivacyLastSeenTab(this.slider).open() new AppPrivacyLastSeenTab(this.slider).open();
} }
}); });
@ -217,6 +213,9 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTab {
}); });
}; };
section.content.append(numberVisibilityRow.container, lastSeenTimeRow.container, photoVisibilityRow.container, callRow.container, linkAccountRow.container, groupChatsAddRow.container);
this.scrollable.append(section.container);
for(const key in rowsByKeys) { for(const key in rowsByKeys) {
updatePrivacyRow(key as keyof typeof rowsByKeys); updatePrivacyRow(key as keyof typeof rowsByKeys);
} }
@ -224,8 +223,6 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTab {
rootScope.on('privacy_update', (update) => { rootScope.on('privacy_update', (update) => {
updatePrivacyRow(convertKeyToInputKey(update.key._) as any); updatePrivacyRow(convertKeyToInputKey(update.key._) as any);
}); });
container.append(numberVisibilityRow.container, lastSeenTimeRow.container, photoVisibilityRow.container, callRow.container, linkAccountRow.container, groupChatsAddRow.container);
} }
} }

12
src/components/sliderTab.ts

@ -75,15 +75,15 @@ export default class SliderSuperTab implements SliderTab {
public async open(...args: any[]) { public async open(...args: any[]) {
if(this.init) { if(this.init) {
const result = this.init(); try {
this.init = null; const result = this.init();
this.init = null;
if(result instanceof Promise) { if(result instanceof Promise) {
try {
await result; await result;
} catch(err) {
console.error('open tab error', err);
} }
} catch(err) {
console.error('open tab error', err);
} }
} }

2
src/config/app.ts

@ -13,7 +13,7 @@ const App = {
id: 1025907, id: 1025907,
hash: '452b0359b988148995f22ff0f4229750', hash: '452b0359b988148995f22ff0f4229750',
version: '0.4.3', version: '0.4.3',
langPackVersion: '0.1.5', langPackVersion: '0.1.6',
langPack: 'macos', langPack: 'macos',
langPackCode: 'en', langPackCode: 'en',
domains: [] as string[], domains: [] as string[],

85
src/helpers/color.ts

@ -4,10 +4,22 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE * https://github.com/morethanwords/tweb/blob/master/LICENSE
*/ */
export function rgbToHsl(r: number, g: number, b: number) { export type ColorHsla = {
h: number,
s: number,
l: number,
a: number
};
export type ColorRgba = [number, number, number, number];
/**
* @returns h [0, 360], s [0, 100], l [0, 100], a [0, 1]
*/
export function rgbaToHsla(r: number, g: number, b: number, a: number = 1): ColorHsla {
r /= 255, g /= 255, b /= 255; r /= 255, g /= 255, b /= 255;
let max = Math.max(r, g, b), const max = Math.max(r, g, b),
min = Math.min(r, g, b); min = Math.min(r, g, b);
let h, s, l = (max + min) / 2; let h, s, l = (max + min) / 2;
if(max === min) { if(max === min) {
@ -29,18 +41,19 @@ export function rgbToHsl(r: number, g: number, b: number) {
h /= 6; h /= 6;
} }
return ({ return {
h: h, h: h * 360,
s: s, s: s * 100,
l: l, l: l * 100,
}); a
};
} }
// * https://stackoverflow.com/a/9493060/6758968 // * https://stackoverflow.com/a/9493060/6758968
/** /**
* Converts an HSL color value to RGB. Conversion formula * Converts an HSL color value to RGB. Conversion formula
* adapted from http://en.wikipedia.org/wiki/HSL_color_space. * adapted from http://en.wikipedia.org/wiki/HSL_color_space.
* Assumes h, s, and l are contained in the set [0, 1] and * Assumes h in [0, 360], s, and l are contained in the set [0, 1], a in [0, 1] and
* returns r, g, and b in the set [0, 255]. * returns r, g, and b in the set [0, 255].
* *
* @param {number} h The hue * @param {number} h The hue
@ -48,7 +61,8 @@ export function rgbToHsl(r: number, g: number, b: number) {
* @param {number} l The lightness * @param {number} l The lightness
* @return {Array} The RGB representation * @return {Array} The RGB representation
*/ */
export function hslToRgba(h: number, s: number, l: number, a: number) { export function hslaToRgba(h: number, s: number, l: number, a: number): ColorRgba {
h /= 360, s /= 100, l /= 100;
let r: number, g: number, b: number; let r: number, g: number, b: number;
if(s === 0) { if(s === 0) {
@ -78,31 +92,50 @@ export function hslaStringToRgba(hsla: string) {
const alpha = +splitted.pop(); const alpha = +splitted.pop();
const arr = splitted.map((val) => { const arr = splitted.map((val) => {
if(val.endsWith('%')) { if(val.endsWith('%')) {
return +val.slice(0, -1) / 100; return +val.slice(0, -1);
} }
return +val / 360; return +val;
}); });
return hslToRgba(arr[0], arr[1], arr[2], alpha); return hslaToRgba(arr[0], arr[1], arr[2], alpha);
}
export function hexaToRgba(hexa: string) {
const arr: ColorRgba = [] as any;
const offset = 1;
if(hexa.length === (3 + offset)) {
for(let i = offset; i < hexa.length; ++i) {
arr.push(parseInt(hexa[i] + hexa[i], 16));
}
} else if(hexa.length === (4 + offset)) {
for(let i = offset; i < (hexa.length - 1); ++i) {
arr.push(parseInt(hexa[i] + hexa[i], 16));
}
arr.push(parseInt(hexa[hexa.length - 1], 16));
} else {
for(let i = offset; i < hexa.length; i += 2) {
arr.push(parseInt(hexa.slice(i, i + 2), 16));
}
}
return arr;
} }
export function hslaStringToRgbaString(hsla: string) { export function hexaToHsla(hexa: string) {
return '#' + hslaStringToRgba(hsla).map(v => ('0' + v.toString(16)).slice(-2)).join(''); const rgba = hexaToRgba(hexa);
return rgbaToHsla(rgba[0], rgba[1], rgba[2], rgba[3]);
} }
export function hslaStringToRgbString(hsla: string) { export function rgbaToHexa(rgba: ColorRgba) {
return hslaStringToRgbaString(hsla).slice(0, -2); return '#' + rgba.map(v => ('0' + v.toString(16)).slice(-2)).join('');
} }
// * https://github.com/TelegramMessenger/Telegram-iOS/blob/3d062fff78cc6b287c74e6171f855a3500c0156d/submodules/TelegramPresentationData/Sources/PresentationData.swift#L453 export function hslaStringToHexa(hsla: string) {
export function highlightningColor(pixel: Uint8ClampedArray) { return rgbaToHexa(hslaStringToRgba(hsla));
let {h, s, l} = rgbToHsl(pixel[0], pixel[1], pixel[2]); }
if(s > 0.0) {
s = Math.min(1.0, s + 0.05 + 0.1 * (1.0 - s));
}
l = Math.max(0.0, l * 0.65);
const hsla = `hsla(${h * 360}, ${s * 100}%, ${l * 100}%, .4)`; export function hslaStringToHex(hsla: string) {
return hsla; return hslaStringToHexa(hsla).slice(0, -2);
} }

13
src/helpers/highlightningColor.ts

@ -0,0 +1,13 @@
import { rgbaToHsla } from "./color";
// * https://github.com/TelegramMessenger/Telegram-iOS/blob/3d062fff78cc6b287c74e6171f855a3500c0156d/submodules/TelegramPresentationData/Sources/PresentationData.swift#L453
export default function highlightningColor(rgba: [number, number, number, number?]) {
let {h, s, l} = rgbaToHsla(rgba[0], rgba[1], rgba[2]);
if(s > 0) {
s = Math.min(100, s + 5 + 0.1 * (100 - s));
}
l = Math.max(0, l * .65);
const hsla = `hsla(${h}, ${s}%, ${l}%, .4)`;
return hsla;
}

5
src/lang.ts

@ -1,6 +1,8 @@
const lang = { const lang = {
"Animations": "Animations", "Animations": "Animations",
"AttachAlbum": "Album", "AttachAlbum": "Album",
"Appearance.Color.Hex": "HEX",
"Appearance.Color.RGB": "RGB",
"BlockModal.Search.Placeholder": "Block user...", "BlockModal.Search.Placeholder": "Block user...",
"DarkMode": "Dark Mode", "DarkMode": "Dark Mode",
"FilterIncludeExcludeInfo": "Choose chats and types of chats that will\nappear and never appear in this folder.", "FilterIncludeExcludeInfo": "Choose chats and types of chats that will\nappear and never appear in this folder.",
@ -43,7 +45,6 @@ const lang = {
"General.SendShortcut.NewLine.Enter": "New line by Enter", "General.SendShortcut.NewLine.Enter": "New line by Enter",
"General.AutoplayMedia": "Auto-Play Media", "General.AutoplayMedia": "Auto-Play Media",
"ChatBackground.UploadWallpaper": "Upload Wallpaper", "ChatBackground.UploadWallpaper": "Upload Wallpaper",
"ChatBackground.SetColor": "Set a Color",
"ChatBackground.Blur": "Blur Wallpaper Image", "ChatBackground.Blur": "Blur Wallpaper Image",
"Notifications.Sound": "Notification Sound", "Notifications.Sound": "Notification Sound",
"Notifications.MessagePreview": "Message preview", "Notifications.MessagePreview": "Message preview",
@ -401,12 +402,14 @@ const lang = {
}, },
"HidAccount": "The account was hidden by the user", "HidAccount": "The account was hidden by the user",
"TelegramFeatures": "Telegram Features", "TelegramFeatures": "Telegram Features",
"SetColor": "Set a color",
// * macos // * macos
"AccountSettings.Filters": "Chat Folders", "AccountSettings.Filters": "Chat Folders",
"AccountSettings.Notifications": "Notifications and Sounds", "AccountSettings.Notifications": "Notifications and Sounds",
"AccountSettings.PrivacyAndSecurity": "Privacy and Security", "AccountSettings.PrivacyAndSecurity": "Privacy and Security",
"AccountSettings.Language": "Language", "AccountSettings.Language": "Language",
"Appearance.Reset": "Reset to Defaults",
"Bio.Description": "Any details such as age, occupation or city.\nExample: 23 y.o. designer from San Francisco", "Bio.Description": "Any details such as age, occupation or city.\nExample: 23 y.o. designer from San Francisco",
"Channel.UsernameAboutChannel": "People can share this link with others and can find your channel using Telegram search.", "Channel.UsernameAboutChannel": "People can share this link with others and can find your channel using Telegram search.",
"Channel.UsernameAboutGroup": "People can share this link with others and find your group using Telegram search.", "Channel.UsernameAboutGroup": "People can share this link with others and find your group using Telegram search.",

10
src/lib/appManagers/appImManager.ts

@ -45,7 +45,7 @@ import appNotificationsManager from './appNotificationsManager';
import AppPrivateSearchTab from '../../components/sidebarRight/tabs/search'; import AppPrivateSearchTab from '../../components/sidebarRight/tabs/search';
import { i18n, LangPackKey } from '../langPack'; import { i18n, LangPackKey } from '../langPack';
import { SendMessageAction } from '../../layer'; import { SendMessageAction } from '../../layer';
import { hslaStringToRgbString } from '../../helpers/color'; import { hslaStringToHex } from '../../helpers/color';
import { copy, getObjectKeysAndSort } from '../../helpers/object'; import { copy, getObjectKeysAndSort } from '../../helpers/object';
import { getFilesFromEvent } from '../../helpers/files'; import { getFilesFromEvent } from '../../helpers/files';
import PeerTitle from '../../components/peerTitle'; import PeerTitle from '../../components/peerTitle';
@ -224,7 +224,7 @@ export class AppImManager {
public setCurrentBackground(broadcastEvent = false) { public setCurrentBackground(broadcastEvent = false) {
const theme = rootScope.settings.themes.find(t => t.name === rootScope.settings.theme); const theme = rootScope.settings.themes.find(t => t.name === rootScope.settings.theme);
if(theme.background.slug) { if(theme.background.type === 'image' || (theme.background.type === 'default' && theme.background.slug)) {
const defaultTheme = AppStateManager.STATE_INIT.settings.themes.find(t => t.name === theme.name); const defaultTheme = AppStateManager.STATE_INIT.settings.themes.find(t => t.name === theme.name);
const isDefaultBackground = theme.background.blur === defaultTheme.background.blur && const isDefaultBackground = theme.background.blur === defaultTheme.background.blur &&
theme.background.slug === defaultTheme.background.slug; theme.background.slug === defaultTheme.background.slug;
@ -312,7 +312,7 @@ export class AppImManager {
let themeColor = '#ffffff'; let themeColor = '#ffffff';
if(hsla) { if(hsla) {
themeColor = hslaStringToRgbString(hsla); themeColor = hslaStringToHex(hsla);
} }
if(this.themeColorElem === undefined) { if(this.themeColorElem === undefined) {
@ -324,7 +324,7 @@ export class AppImManager {
} }
} }
public applyCurrentTheme(slug?: string, backgroundUrl?: string) { public applyCurrentTheme(slug?: string, backgroundUrl?: string, broadcastEvent?: boolean) {
this.applyHighlightningColor(); this.applyHighlightningColor();
document.documentElement.classList.toggle('night', rootScope.settings.theme === 'night'); document.documentElement.classList.toggle('night', rootScope.settings.theme === 'night');
@ -333,7 +333,7 @@ export class AppImManager {
this.backgroundPromises[slug] = Promise.resolve(backgroundUrl); this.backgroundPromises[slug] = Promise.resolve(backgroundUrl);
} }
return this.setCurrentBackground(!!slug); return this.setCurrentBackground(broadcastEvent === undefined ? !!slug : broadcastEvent);
} }
private setSettings = () => { private setSettings = () => {

35
src/scss/partials/_colorPicker.scss

@ -0,0 +1,35 @@
.color-picker {
width: 380px;
max-width: 100%;
margin: 1.1875rem auto 1rem;
user-select: none;
&-box {
width: 100%;
height: 198px;
}
&-box, &-color-slider, &-dragger {
overflow: visible !important;
}
&-sliders {
margin: 1rem 0 1.125rem;
}
&-dragger {
cursor: grab;
}
&-inputs {
display: flex;
.input-field {
flex: 1 1 auto;
&:not(:first-child) {
margin-left: 1.25rem;
}
}
}
}

8
src/scss/partials/_leftSidebar.scss

@ -1108,7 +1108,7 @@
.background-container { .background-container {
.grid { .grid {
padding: 0 .5rem; padding: 0 .5rem .5rem;
&-item { &-item {
&:after { &:after {
@ -1141,3 +1141,9 @@
} }
} }
} }
.background-image-container {
.sidebar-left-section {
padding-bottom: .5rem;
}
}

1
src/scss/style.scss

@ -256,6 +256,7 @@ html.night {
@import "partials/poll"; @import "partials/poll";
@import "partials/transition"; @import "partials/transition";
@import "partials/row"; @import "partials/row";
@import "partials/colorPicker";
@import "partials/popups/popup"; @import "partials/popups/popup";
@import "partials/popups/editAvatar"; @import "partials/popups/editAvatar";

Loading…
Cancel
Save