the browser-facing portion of osu!
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at master 94 lines 2.6 kB view raw
1// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0. 2// See the LICENCE file in the repository root for full licence text. 3 4import { action, makeObservable, observable } from 'mobx'; 5import { observer } from 'mobx-react'; 6import * as React from 'react'; 7import { trans } from 'utils/lang'; 8 9interface Props { 10 anchor?: React.RefObject<HTMLElement>; 11} 12 13@observer 14export default class BackToTop extends React.Component<Props> { 15 @observable private lastScrollY: number | null = null; 16 private observer: IntersectionObserver | null = null; 17 18 constructor(props: Props) { 19 super(props); 20 21 makeObservable(this); 22 } 23 24 componentWillUnmount() { 25 document.removeEventListener('scroll', this.onScroll); 26 this.removeObserver(); 27 } 28 29 render() { 30 return ( 31 <button 32 className='floating-toolbar-button' 33 data-tooltip-float='fixed' 34 onClick={this.onClick} 35 title={trans(this.lastScrollY == null ? 'common.buttons.back_to_top' : 'common.buttons.back_to_previous')} 36 > 37 <span className={this.lastScrollY == null ? 'fas fa-angle-up' : 'fas fa-angle-down'} /> 38 </button> 39 ); 40 } 41 42 @action 43 readonly reset = () => { 44 this.lastScrollY = null; 45 }; 46 47 // Workaround Firefox scrollTo and setTimeout(fn, 0) not being dispatched serially. 48 private mountObserver() { 49 // anchor to body if none specified; assumes body's top is 0. 50 const target = this.props.anchor?.current ?? document.body; 51 52 this.observer = new IntersectionObserver((entries) => { 53 for (const entry of entries) { 54 if (entry.target === target && entry.boundingClientRect.top === 0) { 55 document.addEventListener('scroll', this.onScroll); 56 break; 57 } 58 } 59 }); 60 61 this.observer.observe(target); 62 } 63 64 @action 65 private readonly onClick = () => { 66 if (this.lastScrollY == null) { 67 const scrollY = this.props.anchor?.current == null ? 0 : ($(this.props.anchor.current).offset()?.top ?? 0); 68 if (window.pageYOffset > scrollY) { 69 this.lastScrollY = window.pageYOffset; 70 71 window.scrollTo(window.pageXOffset, scrollY); 72 this.mountObserver(); 73 } 74 } else { 75 window.scrollTo(window.pageXOffset, this.lastScrollY); 76 77 this.lastScrollY = null; 78 } 79 }; 80 81 @action 82 private readonly onScroll = () => { 83 this.lastScrollY = null; 84 document.removeEventListener('scroll', this.onScroll); 85 this.removeObserver(); 86 }; 87 88 private removeObserver() { 89 if (this.observer == null) return; 90 91 this.observer.disconnect(); 92 this.observer = null; 93 } 94}