Browse Source

Improve navigation controller

Change gray hovers on blue buttons
master
Eduard Kuzmenko 3 years ago
parent
commit
1da72f8c23
  1. 82
      src/components/appNavigationController.ts
  2. 2
      src/components/chat/topbar.ts
  3. 25
      src/components/misc.ts
  4. 3
      src/components/popups/datePicker.ts
  5. 6
      src/components/popups/index.ts
  6. 4
      src/components/ripple.ts
  7. 9
      src/components/sidebarLeft/index.ts
  8. 6
      src/components/sidebarLeft/tabs/editFolder.ts
  9. 2
      src/components/slider.ts
  10. 4
      src/helpers/dom.ts
  11. 2
      src/helpers/schedulers.ts
  12. 2
      src/index.hbs
  13. 4
      src/lib/appManagers/appImManager.ts
  14. 16
      src/scss/mixins/_hover.scss
  15. 28
      src/scss/partials/_button.scss
  16. 2
      src/scss/partials/_chat.scss
  17. 2
      src/scss/partials/_leftSidebar.scss
  18. 2
      src/scss/partials/_ripple.scss
  19. 14
      src/scss/partials/_slider.scss
  20. 2
      src/scss/partials/popups/_datePicker.scss
  21. 15
      src/scss/partials/popups/_popup.scss
  22. 16
      src/scss/style.scss

82
src/components/appNavigationController.ts

@ -1,6 +1,8 @@
import { MOUNT_CLASS_TO } from "../config/debug"; import { MOUNT_CLASS_TO } from "../config/debug";
import { isSafari, isAppleMobile } from "../helpers/userAgent"; import { isMobileSafari } from "../helpers/userAgent";
import { cancelEvent } from "../helpers/dom"; import { cancelEvent } from "../helpers/dom";
import { logger } from "../lib/logger";
import { doubleRaf } from "../helpers/schedulers";
export type NavigationItem = { export type NavigationItem = {
type: 'left' | 'right' | 'im' | 'chat' | 'popup' | 'media' | 'menu' | 'esg', type: 'left' | 'right' | 'im' | 'chat' | 'popup' | 'media' | 'menu' | 'esg',
@ -12,10 +14,14 @@ export type NavigationItem = {
export class AppNavigationController { export class AppNavigationController {
private navigations: Array<NavigationItem> = []; private navigations: Array<NavigationItem> = [];
private id = Date.now(); private id = Date.now();
private manual = false;
private log = logger('NC');
private debug = true;
constructor() { constructor() {
let isPossibleSwipe = false;
window.addEventListener('popstate', (e) => { window.addEventListener('popstate', (e) => {
console.log('popstate', e); this.debug && this.log('popstate', e, isPossibleSwipe);
const id: number = e.state; const id: number = e.state;
if(id !== this.id) { if(id !== this.id) {
@ -29,12 +35,8 @@ export class AppNavigationController {
return; return;
} }
const good = item.onPop(isSafari && isAppleMobile ? false : undefined); this.manual = !isPossibleSwipe;
console.log('[NC]: popstate, navigation:', item, this.navigations); this.handleItem(item);
if(good === false) {
this.pushItem(item);
}
//this.pushState(); // * prevent adding forward arrow //this.pushState(); // * prevent adding forward arrow
}); });
@ -47,16 +49,62 @@ export class AppNavigationController {
} }
}, {capture: true}); }, {capture: true});
if(isMobileSafari) {
/* window.addEventListener('touchstart', (e) => {
this.debug && this.log('touchstart');
}, {passive: true}); */
window.addEventListener('touchend', (e) => {
this.debug && this.log('touchend');
if(e.touches.length > 1) return;
isPossibleSwipe = true;
doubleRaf().then(() => {
isPossibleSwipe = false;
});
}, {passive: true});
}
this.pushState(); // * push init state this.pushState(); // * push init state
} }
public back() { private handleItem(item: NavigationItem) {
const good = item.onPop(!this.manual ? false : undefined);
this.debug && this.log('popstate, navigation:', item, this.navigations);
if(good === false) {
this.pushItem(item);
}
this.manual = false;
}
public back(type?: NavigationItem['type']) {
if(type) {
let item: NavigationItem;
let i = this.navigations.length - 1;
for(; i >= 0; --i) {
const _item = this.navigations[i];
if(_item.type === type) {
item = _item;
break;
}
}
if(item) {
this.manual = true;
if(i !== (this.navigations.length - 1)) {
this.navigations.splice(i, 1);
this.handleItem(item);
return;
}
}
}
history.back(); history.back();
} }
public pushItem(item: NavigationItem) { public pushItem(item: NavigationItem) {
this.navigations.push(item); this.navigations.push(item);
console.log('[NC]: pushstate', item, this.navigations); this.debug && this.log('pushstate', item, this.navigations);
if(!item.noHistory) { if(!item.noHistory) {
this.pushState(); this.pushState();
@ -64,6 +112,7 @@ export class AppNavigationController {
} }
private pushState() { private pushState() {
this.manual = false;
history.pushState(this.id, ''); history.pushState(this.id, '');
} }
@ -74,6 +123,19 @@ export class AppNavigationController {
public removeItem(item: NavigationItem) { public removeItem(item: NavigationItem) {
this.navigations.findAndSplice(i => i === item); this.navigations.findAndSplice(i => i === item);
} }
public removeByType(type: NavigationItem['type'], single = false) {
for(let i = this.navigations.length - 1; i >= 0; --i) {
const item = this.navigations[i];
if(item.type === type) {
this.navigations.splice(i, 1);
if(single) {
break;
}
}
}
}
} }
const appNavigationController = new AppNavigationController(); const appNavigationController = new AppNavigationController();

2
src/components/chat/topbar.ts

@ -144,7 +144,7 @@ export default class ChatTopbar {
cancelEvent(e); cancelEvent(e);
/* this.chat.appImManager.setPeer(0); /* this.chat.appImManager.setPeer(0);
blurActiveElement(); */ blurActiveElement(); */
appNavigationController.back(); appNavigationController.back('chat');
}, {listenerSetter: this.listenerSetter}); }, {listenerSetter: this.listenerSetter});
} }

25
src/components/misc.ts

@ -4,7 +4,8 @@ import { cancelEvent, CLICK_EVENT_NAME } from "../helpers/dom";
import ListenerSetter from "../helpers/listenerSetter"; import ListenerSetter from "../helpers/listenerSetter";
import mediaSizes from "../helpers/mediaSizes"; import mediaSizes from "../helpers/mediaSizes";
import { isTouchSupported } from "../helpers/touchSupport"; import { isTouchSupported } from "../helpers/touchSupport";
import { isApple } from "../helpers/userAgent"; import { isApple, isMobileSafari } from "../helpers/userAgent";
import appNavigationController from "./appNavigationController";
export const loadedURLs: {[url: string]: boolean} = {}; export const loadedURLs: {[url: string]: boolean} = {};
const set = (elem: HTMLElement | HTMLImageElement | SVGImageElement | HTMLVideoElement, url: string) => { const set = (elem: HTMLElement | HTMLImageElement | SVGImageElement | HTMLVideoElement, url: string) => {
@ -136,12 +137,13 @@ const onClick = (e: MouseEvent | TouchEvent) => {
closeBtnMenu(); closeBtnMenu();
}; };
const onKeyDown = (e: KeyboardEvent) => { // ! no need in this due to the same handler in appNavigationController
/* const onKeyDown = (e: KeyboardEvent) => {
if(e.key === 'Escape') { if(e.key === 'Escape') {
closeBtnMenu(); closeBtnMenu();
cancelEvent(e); cancelEvent(e);
} }
}; }; */
export const closeBtnMenu = () => { export const closeBtnMenu = () => {
if(openedMenu) { if(openedMenu) {
@ -159,11 +161,15 @@ export const closeBtnMenu = () => {
if(!isTouchSupported) { if(!isTouchSupported) {
window.removeEventListener('mousemove', onMouseMove); window.removeEventListener('mousemove', onMouseMove);
window.removeEventListener('keydown', onKeyDown, {capture: true}); //window.removeEventListener('keydown', onKeyDown, {capture: true});
window.removeEventListener('contextmenu', onClick); window.removeEventListener('contextmenu', onClick);
} }
document.removeEventListener(CLICK_EVENT_NAME, onClick); document.removeEventListener(CLICK_EVENT_NAME, onClick);
if(!isMobileSafari) {
appNavigationController.removeByType('menu');
}
}; };
window.addEventListener('resize', () => { window.addEventListener('resize', () => {
@ -182,6 +188,15 @@ window.addEventListener('resize', () => {
let openedMenu: HTMLElement = null, openedMenuOnClose: () => void = null, menuOverlay: HTMLElement = null; let openedMenu: HTMLElement = null, openedMenuOnClose: () => void = null, menuOverlay: HTMLElement = null;
export function openBtnMenu(menuElement: HTMLElement, onClose?: () => void) { export function openBtnMenu(menuElement: HTMLElement, onClose?: () => void) {
closeBtnMenu(); closeBtnMenu();
if(!isMobileSafari) {
appNavigationController.pushItem({
type: 'menu',
onPop: (canAnimate) => {
closeBtnMenu();
}
});
}
openedMenu = menuElement; openedMenu = menuElement;
openedMenu.classList.add('active'); openedMenu.classList.add('active');
@ -206,7 +221,7 @@ export function openBtnMenu(menuElement: HTMLElement, onClose?: () => void) {
if(!isTouchSupported) { if(!isTouchSupported) {
window.addEventListener('mousemove', onMouseMove); window.addEventListener('mousemove', onMouseMove);
window.addEventListener('keydown', onKeyDown, {capture: true}); //window.addEventListener('keydown', onKeyDown, {capture: true});
window.addEventListener('contextmenu', onClick, {once: true}); window.addEventListener('contextmenu', onClick, {once: true});
} }

3
src/components/popups/datePicker.ts

@ -145,6 +145,9 @@ export default class PopupDatePicker extends PopupElement {
}, {once: true}); }, {once: true});
this.body.append(this.timeDiv); this.body.append(this.timeDiv);
this.prevBtn.classList.add('primary');
this.nextBtn.classList.add('primary');
} }
const popupCenterer = document.createElement('div'); const popupCenterer = document.createElement('div');

6
src/components/popups/index.ts

@ -75,9 +75,9 @@ export default class PopupElement {
const buttonsElements = buttons.map(b => { const buttonsElements = buttons.map(b => {
const button = document.createElement('button'); const button = document.createElement('button');
button.className = 'btn' + (b.isDanger ? ' danger' : ''); button.className = 'btn' + (b.isDanger ? ' danger' : ' primary');
button.innerHTML = b.text; button.innerHTML = b.text;
//ripple(button); ripple(button);
if(b.callback) { if(b.callback) {
button.addEventListener('click', () => { button.addEventListener('click', () => {
@ -118,7 +118,7 @@ export default class PopupElement {
} }
public hide = () => { public hide = () => {
appNavigationController.back(); appNavigationController.back('popup');
}; };
private destroy = () => { private destroy = () => {

4
src/components/ripple.ts

@ -168,6 +168,10 @@ export function ripple(elem: HTMLElement, callback: (id: number) => Promise<bool
}, {passive: true}); }, {passive: true});
} else { } else {
elem.addEventListener('mousedown', (e) => { elem.addEventListener('mousedown', (e) => {
if(![0, 2].includes(e.button)) { // only left and right buttons
return;
}
if(!rootScope.settings.animationsEnabled) { if(!rootScope.settings.animationsEnabled) {
return; return;
} }

9
src/components/sidebarLeft/index.ts

@ -417,9 +417,10 @@ export class AppSidebarLeft extends SidebarSlider {
transition(0); transition(0);
const activeClassName = 'is-visible';
const onFocus = () => { const onFocus = () => {
this.toolsBtn.classList.remove('active'); this.toolsBtn.classList.remove(activeClassName);
this.backBtn.classList.add('active'); this.backBtn.classList.add(activeClassName);
this.newBtnMenu.classList.add('is-hidden'); this.newBtnMenu.classList.add('is-hidden');
this.toolsBtn.parentElement.firstElementChild.classList.toggle('state-back', true); this.toolsBtn.parentElement.firstElementChild.classList.toggle('state-back', true);
@ -430,8 +431,8 @@ export class AppSidebarLeft extends SidebarSlider {
onFocus(); onFocus();
this.backBtn.addEventListener('click', (e) => { this.backBtn.addEventListener('click', (e) => {
this.toolsBtn.classList.add('active'); this.toolsBtn.classList.add(activeClassName);
this.backBtn.classList.remove('active'); this.backBtn.classList.remove(activeClassName);
this.toolsBtn.parentElement.firstElementChild.classList.toggle('state-back', false); this.toolsBtn.parentElement.firstElementChild.classList.toggle('state-back', false);
transition(0); transition(0);

6
src/components/sidebarLeft/tabs/editFolder.ts

@ -90,7 +90,7 @@ export default class AppEditFolderTab extends SliderSuperTab {
categories.classList.add('folder-categories'); categories.classList.add('folder-categories');
buttons.forEach(o => { buttons.forEach(o => {
const button = Button('folder-category-button btn-primary btn-transparent', { const button = Button('folder-category-button btn btn-primary btn-transparent', {
icon: o.icon, icon: o.icon,
text: o.text, text: o.text,
noRipple: o.withRipple ? undefined : true, noRipple: o.withRipple ? undefined : true,
@ -110,7 +110,7 @@ export default class AppEditFolderTab extends SliderSuperTab {
}; };
this.include_peers = generateList('folder-list-included', 'Included chats', [{ this.include_peers = generateList('folder-list-included', 'Included chats', [{
icon: 'add blue', icon: 'add primary',
text: 'Add Chats', text: 'Add Chats',
withRipple: true withRipple: true
}, { }, {
@ -136,7 +136,7 @@ export default class AppEditFolderTab extends SliderSuperTab {
}], this.flags); }], this.flags);
this.exclude_peers = generateList('folder-list-excluded', 'Excluded chats', [{ this.exclude_peers = generateList('folder-list-excluded', 'Excluded chats', [{
icon: 'minus blue', icon: 'minus primary',
text: 'Remove Chats', text: 'Remove Chats',
withRipple: true withRipple: true
}, { }, {

2
src/components/slider.ts

@ -114,7 +114,7 @@ export default class SidebarSlider {
} }
private onCloseBtnClick = () => { private onCloseBtnClick = () => {
appNavigationController.back(); appNavigationController.back(this.navigationType);
// this.closeTab(); // this.closeTab();
}; };

4
src/helpers/dom.ts

@ -5,7 +5,7 @@ import { isTouchSupported } from "./touchSupport";
import { isApple } from "./userAgent"; import { isApple } from "./userAgent";
import rootScope from "../lib/rootScope"; import rootScope from "../lib/rootScope";
import { MOUNT_CLASS_TO } from "../config/debug"; import { MOUNT_CLASS_TO } from "../config/debug";
import { superRaf } from "./schedulers"; import { doubleRaf } from "./schedulers";
/* export function isInDOM(element: Element, parentNode?: HTMLElement): boolean { /* export function isInDOM(element: Element, parentNode?: HTMLElement): boolean {
if(!element) { if(!element) {
@ -780,7 +780,7 @@ export function isSelectionEmpty(selection = window.getSelection()) {
export function disableTransition(elements: HTMLElement[]) { export function disableTransition(elements: HTMLElement[]) {
elements.forEach(el => el.classList.add('no-transition')); elements.forEach(el => el.classList.add('no-transition'));
superRaf().then(() => { doubleRaf().then(() => {
elements.forEach(el => el.classList.remove('no-transition')); elements.forEach(el => el.classList.remove('no-transition'));
}); });
} }

2
src/helpers/schedulers.ts

@ -124,7 +124,7 @@ export function fastRaf(callback: NoneToVoidFunction) {
} }
} }
export function superRaf() { export function doubleRaf() {
return new Promise((resolve) => { return new Promise((resolve) => {
window.requestAnimationFrame(() => { window.requestAnimationFrame(() => {
window.requestAnimationFrame(resolve); window.requestAnimationFrame(resolve);

2
src/index.hbs

@ -99,7 +99,7 @@
<div class="sidebar-header"> <div class="sidebar-header">
<div class="sidebar-header__btn-container"> <div class="sidebar-header__btn-container">
<div class="animated-menu-icon"></div> <div class="animated-menu-icon"></div>
<div class="btn-icon btn-menu-toggle rp sidebar-tools-button active"> <div class="btn-icon btn-menu-toggle rp sidebar-tools-button is-visible">
<div class="btn-menu bottom-right"> <div class="btn-menu bottom-right">
<div class="btn-menu-item menu-newGroup tgico-newgroup rp">New Group</div> <div class="btn-menu-item menu-newGroup tgico-newgroup rp">New Group</div>
<div class="btn-menu-item menu-contacts tgico-user rp">Contacts</div> <div class="btn-menu-item menu-contacts tgico-user rp">Contacts</div>

4
src/lib/appManagers/appImManager.ts

@ -26,7 +26,7 @@ import { isTouchSupported } from '../../helpers/touchSupport';
import appPollsManager from './appPollsManager'; import appPollsManager from './appPollsManager';
import SetTransition from '../../components/singleTransition'; import SetTransition from '../../components/singleTransition';
import ChatDragAndDrop from '../../components/chat/dragAndDrop'; import ChatDragAndDrop from '../../components/chat/dragAndDrop';
import { debounce, pause, superRaf } from '../../helpers/schedulers'; import { debounce, pause, doubleRaf } from '../../helpers/schedulers';
import lottieLoader from '../lottieLoader'; import lottieLoader from '../lottieLoader';
import useHeavyAnimationCheck, { dispatchHeavyAnimationEvent } from '../../hooks/useHeavyAnimationCheck'; import useHeavyAnimationCheck, { dispatchHeavyAnimationEvent } from '../../hooks/useHeavyAnimationCheck';
import appDraftsManager from './appDraftsManager'; import appDraftsManager from './appDraftsManager';
@ -545,7 +545,7 @@ export class AppImManager {
this.log('selectTab', id, prevTabId); this.log('selectTab', id, prevTabId);
let animationPromise: Promise<any> = superRaf(); let animationPromise: Promise<any> = doubleRaf();
if(prevTabId !== -1 && prevTabId !== id && rootScope.settings.animationsEnabled && animate !== false) { if(prevTabId !== -1 && prevTabId !== id && rootScope.settings.animationsEnabled && animate !== false) {
const transitionTime = (mediaSizes.isMobile ? 250 : 200) + 100; // * cause transition time could be > 250ms const transitionTime = (mediaSizes.isMobile ? 250 : 200) + 100; // * cause transition time could be > 250ms
animationPromise = pause(transitionTime); animationPromise = pause(transitionTime);

16
src/scss/mixins/_hover.scss

@ -2,7 +2,7 @@
@if $color { @if $color {
@if $color == gray { @if $color == gray {
$color: var(--color-gray-hover); $color: var(--color-gray-hover);
} @else if $color == blue { } @else if $color == blue or $color == primary {
$color: var(--color-blue-hover); $color: var(--color-blue-hover);
} @else if $color == red { } @else if $color == red {
$color: var(--color-red-hover); $color: var(--color-red-hover);
@ -33,4 +33,16 @@
@include hover($color, background-color, $use-transition) { @include hover($color, background-color, $use-transition) {
@content; @content;
} }
} }
@mixin btn-hoverable {
@include hover-background-effect();
&.primary, &.blue, &.active {
@include hover-background-effect(primary);
}
&.danger {
@include hover-background-effect(red);
}
}

28
src/scss/partials/_button.scss

@ -3,29 +3,22 @@
font-size: 1.5rem; font-size: 1.5rem;
line-height: 1; line-height: 1;
border-radius: 50% !important; border-radius: 50% !important;
transition: background-color .15s ease-in-out, opacity .15s ease-in-out;
color: $color-gray; color: $color-gray;
cursor: pointer;
background-color: transparent; background-color: transparent;
border: none; border: none;
padding: .5rem; padding: .5rem;
position: relative; position: relative;
overflow: hidden; overflow: hidden;
transition: color .15s ease-in-out, opacity .15s ease-in-out, background-color .15s ease-in-out;
/* kostil */ /* kostil */
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
body.animation-level-0 & {
transition: none;
}
&.active { &.active {
color: $color-blue; color: $color-blue;
} }
@include hover-background-effect();
&:disabled { &:disabled {
color: #cacaca; color: #cacaca;
@ -164,6 +157,10 @@
@include hover-background-effect(); @include hover-background-effect();
&.danger {
@include hover-background-effect(red);
}
&:before { &:before {
color: $color-gray; color: $color-gray;
font-size: 1.5rem; font-size: 1.5rem;
@ -323,4 +320,17 @@
&:before { &:before {
color: inherit !important; color: inherit !important;
} }
} }
.btn, .btn-icon {
background: none;
outline: none;
border: none;
cursor: pointer;
body.animation-level-0 & {
transition: none;
}
@include btn-hoverable();
}

2
src/scss/partials/_chat.scss

@ -749,7 +749,7 @@ $chat-helper-size: 39px;
.attach-file { .attach-file {
&.menu-open { &.menu-open {
color: $color-blue; color: $color-blue;
background-color: transparent; background-color: hover-color($color-blue) !important;
} }
.btn-menu { .btn-menu {

2
src/scss/partials/_leftSidebar.scss

@ -223,7 +223,7 @@
transition: none; transition: none;
} }
&.active { &.is-visible {
//margin-top: 1px; //margin-top: 1px;
opacity: 1; opacity: 1;
visibility: visible; visibility: visible;

2
src/scss/partials/_ripple.scss

@ -49,7 +49,7 @@
border-radius: 50%; border-radius: 50%;
animation: ripple-effect .7s forwards; animation: ripple-effect .7s forwards;
//animation-timing-function: ease-out; //animation-timing-function: ease-out;
transition: .35s opacity; transition: .35s opacity, .35s background-color;
//overflow: hidden; //overflow: hidden;
.btn-menu &, .c-ripple.is-square & { .btn-menu &, .c-ripple.is-square & {

14
src/scss/partials/_slider.scss

@ -28,18 +28,16 @@
border-top-right-radius: 6px; border-top-right-radius: 6px;
transition: none !important; transition: none !important;
@include hover() { @include hover-background-effect();
background-color: transparent;
&:hover { &.active {
background-color: var(--color-gray-hover); .c-ripple__circle {
background-color: var(--color-blue-hover);
} }
@include hover-background-effect(primary);
} }
/* html.no-touch body:not(.animation-level-0) & {
transition: background-color .15s ease-in-out;
} */
> span { > span {
position: relative; position: relative;
display: inline-flex; display: inline-flex;

2
src/scss/partials/popups/_datePicker.scss

@ -109,7 +109,7 @@
.btn-icon.active { .btn-icon.active {
color: #fff; color: #fff;
border-radius: 50%; border-radius: 50%;
background-color: $color-blue; background-color: $color-blue !important;
} }
} }
} }

15
src/scss/partials/popups/_popup.scss

@ -115,27 +115,16 @@
} }
.btn { .btn {
background: none;
outline: none;
border: none;
font-weight: 500; font-weight: 500;
padding: .5rem; padding: .5rem;
text-transform: uppercase; text-transform: uppercase;
border-radius: $border-radius; border-radius: $border-radius;
cursor: pointer;
color: $color-blue;
position: relative; position: relative;
overflow: hidden; overflow: hidden;
max-width: 100%; max-width: 100%;
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
body.animation-level-0 & {
transition: none;
}
@include hover-background-effect(blue);
& + .btn { & + .btn {
margin-top: .5rem; margin-top: .5rem;
text-overflow: ellipsis; text-overflow: ellipsis;
@ -143,10 +132,6 @@
max-width: 286px; max-width: 286px;
overflow: hidden; overflow: hidden;
} }
&.danger {
@include hover-background-effect(red);
}
} }
} }

16
src/scss/style.scss

@ -356,12 +356,24 @@ input, textarea {
line-height: 1.35; line-height: 1.35;
} }
.danger, .danger:before { .danger {
color: $color-error!important; color: $color-error!important;
.c-ripple__circle {
background-color: var(--color-red-hover);
}
} }
.blue, .blue:before { .blue, .primary {
color: $color-blue!important; color: $color-blue!important;
.c-ripple__circle {
background-color: var(--color-blue-hover);
}
}
.blue:before, .primary:before, .danger:before {
color: inherit !important;
} }
.bg-warning { .bg-warning {

Loading…
Cancel
Save