Add main menu; add panel fixes

This commit is contained in:
obergodmar
2020-07-01 21:35:19 +03:00
parent 4b1666c584
commit 839355f1de
15 changed files with 194 additions and 78 deletions
+1 -3
View File
@@ -29,9 +29,7 @@
"since 2010" "since 2010"
], ],
"development": [ "development": [
"last 1 chrome version", "since 2010"
"last 1 firefox version",
"last 1 safari version"
] ]
} }
} }
+19 -1
View File
@@ -1,7 +1,15 @@
import * as React from 'react' import * as React from 'react'
import { KeyboardEvent, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react' import { KeyboardEvent, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
import { MusicComponent, PanelComponent, PreviewComponent, SettingsComponent, ViewComponent } from '../components' import {
MainMenuComponent,
MenuItemComponent,
MusicComponent,
PanelComponent,
PreviewComponent,
SettingsComponent,
ViewComponent
} from '../components'
import places from '../assets' import places from '../assets'
import { delay, soundLoad, UI_SOUND_VOLUME } from '../utils' import { delay, soundLoad, UI_SOUND_VOLUME } from '../utils'
import { useSettings } from '../hooks' import { useSettings } from '../hooks'
@@ -131,6 +139,16 @@ export default function App() {
className='main' className='main'
> >
<ViewComponent src={places[activePlace].view[activeView]}/> <ViewComponent src={places[activePlace].view[activeView]}/>
<MainMenuComponent>
<div className='author'>
<a href="https://github.com/obergodmar">obergodmar</a>
<span>v1.2.0</span>
</div>
<MenuItemComponent
isActive={isSettingsShown}
handleClick={openCloseSettings}
/>
</MainMenuComponent>
<PanelComponent <PanelComponent
openSoundPlay={panelOpenSound.playSound} openSoundPlay={panelOpenSound.playSound}
closeSoundPlay={panelCloseSound.playSound} closeSoundPlay={panelCloseSound.playSound}
+24
View File
@@ -33,6 +33,30 @@ body {
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;
overflow: hidden; overflow: hidden;
position: relative;
}
.author {
padding: 0 10px;
width: 130px;
font-family: $font;
text-shadow: $fontShadow;
color: $fontColor;
font-size: 16px;
display: flex;
align-items: center;
justify-content: space-between;
a {
cursor: $cursorPointer, auto;
color: inherit;
text-decoration: none;
&:focus {
outline: none;
box-shadow: $hoverBox;
}
}
} }
button { button {
+2
View File
@@ -25,3 +25,5 @@ $radio: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAAAQCAYAAACm53kpA
$rangeBorder: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIoAAAAQCAYAAADJeudBAAAABmJLR0QAKAAqADbykokVAAABBUlEQVRo3u3asW2DQBiG4RfkghG49qp4hLjGA3BI3gNdEUWurYjCyh5I2APAHqQxc9CRBmySULhJxfdUCKh+vbqTThfw8AYkiDw0QAGwmUfic69Q5O78eZ4ei808kt3rjuyQdRqRVGVlgWQWC7XP/VCV1QDcgFpjWr0auFVlNfjcD0AdAhhjppWkA/aa0+rtgS47ZJ0xBoBw4QeRPy2Emoc8Q6HI86G0batJyKKpDa0ooq1H/i8UnaHIYgshQBzHuNRZwCoWGRuwLnU2jmMAAsYj/OP7MWm/Wi7Xi47wBZc6u33Zcvo4NUATjO9/xNL3vSa1YlEUMY8EKILZd10zkN/u1wy+AWM3S3inoVC2AAAAAElFTkSuQmCC'); $rangeBorder: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIoAAAAQCAYAAADJeudBAAAABmJLR0QAKAAqADbykokVAAABBUlEQVRo3u3asW2DQBiG4RfkghG49qp4hLjGA3BI3gNdEUWurYjCyh5I2APAHqQxc9CRBmySULhJxfdUCKh+vbqTThfw8AYkiDw0QAGwmUfic69Q5O78eZ4ei808kt3rjuyQdRqRVGVlgWQWC7XP/VCV1QDcgFpjWr0auFVlNfjcD0AdAhhjppWkA/aa0+rtgS47ZJ0xBoBw4QeRPy2Emoc8Q6HI86G0batJyKKpDa0ooq1H/i8UnaHIYgshQBzHuNRZwCoWGRuwLnU2jmMAAsYj/OP7MWm/Wi7Xi47wBZc6u33Zcvo4NUATjO9/xNL3vSa1YlEUMY8EKILZd10zkN/u1wy+AWM3S3inoVC2AAAAAElFTkSuQmCC');
$range: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAB+ElEQVR4nO2VP2gTURzHP6kOJ1nurR1vcwghOaHERRuuoygGOjQIFeUQddOhq5OtZBShXdO0EIl26ImQs+Io2kBSsVtwu3a6Gxo5muEc8g4zONy7kizmA8e993jv9/vy4/cHZsz437l0gbcCmJfrcNoCDOAWYMq1Lz9l5lI6twCz862zLkVY8nziAkTsfOPlhg3QarbsMRFiGgJE7DxGihBpBKjmgAGY7ic3zGQyxpK1RBAEVJYrLqMc6AOeikGVCAjA1PSSWFldsx49eUbh2lVCNBpNxwJdgG6SIgpJMYH1ldW1qPX+Y6Tppbaml9qgtxtNJ2o0nQj0OCkTo1wFlds3qd5/4QLW3Ts3rIXSglVdrrqqdlILGOfd3peLPAfgsuJ98cFxCINjCsU8AOHgBAjwfh0DwcQECGSj6XR6vH5Ts54+fi7DHlB7VbO6va6yc4BMwntGNpu1B4OBD7pRKObtBw/vARCeBXR7Xerb9bfAIeDKfyIS94HhcOgBPmjzJ95p6HmnRrGY5/D7V+rbdZdR/QvpPHEvSCog/Gs0NMqL1+0r2hzh7zPOh+fkcjnj6MeRC+wDP1GYjqpV4AP+weeDrfHDnd2dLUZdsE/KqaiCAdjAZnmxHAGbcp9qGiZNwn+JiKefzyjx+iltpSYuzYn1/hkzZkyFP182nWrkPhvCAAAAAElFTkSuQmCC'); $range: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAB+ElEQVR4nO2VP2gTURzHP6kOJ1nurR1vcwghOaHERRuuoygGOjQIFeUQddOhq5OtZBShXdO0EIl26ImQs+Io2kBSsVtwu3a6Gxo5muEc8g4zONy7kizmA8e993jv9/vy4/cHZsz437l0gbcCmJfrcNoCDOAWYMq1Lz9l5lI6twCz862zLkVY8nziAkTsfOPlhg3QarbsMRFiGgJE7DxGihBpBKjmgAGY7ic3zGQyxpK1RBAEVJYrLqMc6AOeikGVCAjA1PSSWFldsx49eUbh2lVCNBpNxwJdgG6SIgpJMYH1ldW1qPX+Y6Tppbaml9qgtxtNJ2o0nQj0OCkTo1wFlds3qd5/4QLW3Ts3rIXSglVdrrqqdlILGOfd3peLPAfgsuJ98cFxCINjCsU8AOHgBAjwfh0DwcQECGSj6XR6vH5Ts54+fi7DHlB7VbO6va6yc4BMwntGNpu1B4OBD7pRKObtBw/vARCeBXR7Xerb9bfAIeDKfyIS94HhcOgBPmjzJ95p6HmnRrGY5/D7V+rbdZdR/QvpPHEvSCog/Gs0NMqL1+0r2hzh7zPOh+fkcjnj6MeRC+wDP1GYjqpV4AP+weeDrfHDnd2dLUZdsE/KqaiCAdjAZnmxHAGbcp9qGiZNwn+JiKefzyjx+iltpSYuzYn1/hkzZkyFP182nWrkPhvCAAAAAElFTkSuQmCC');
$rangeBackground: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAFUlEQVR4nGNkYGDoYMADmPBJDh8FAJNoAJjpM54wAAAAAElFTkSuQmCC'); $rangeBackground: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAFUlEQVR4nGNkYGDoYMADmPBJDh8FAJNoAJjpM54wAAAAAElFTkSuQmCC');
$helpButton: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAApCAMAAABjq9sOAAABp1BMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAACAgIDAgLDQsODQsODwsOEA4TDQsTDw4WFhMWFxYYGBgYHBgZGBYbGRYbGxYbHBsbHRsbHxshGBAhHxshIBghICEhJCEkIRskIx4kIyEmEwsmGxYmHRsmIx4mJRsmJSEpGBgpICEpJCEpKCEpKCkpLCksKyYxFBAxGBAxHBgxJBgxLCkxLSw3GBA3MzE3NzQ5GBM5KCE5LCk5OzlCQTlHRkJKExBKQUJKRUJKSUJKTUpNDxBNEAhNHQ1STUpSUUpSU1JSVVJVExBYV1JdEAtdFBBjBAhjCABjCwhjEAhlY2BoFQ1oZ2hrDAhrDQhrFAhrFBBraWNwJRtwOClzCAhzKBBzb257DAh7EAh7dWt7dXN7eXt7fXuEAACEfXuEgnuJDAWJDAiMCACMKh6RIBaUjoyXKBuaLB6cmpyfKxinKyGtpqWwEwuwJx6wWUG1BAC1CAC4NSO9bUrIRTTLLSHLQS/OXUrTSjneSTHee2XvKCHveVrvmIH3EAj3MCn3XUr/WUL/aVL/tpzPGTKNAAAACHRSTlMAESIzVYjM7umjQSUAAAIfSURBVDjLhdT7W9JQGMDxMZw7k25aUWqCsATXSior7aYIRasIAiIw5NK9GBia3TAvtQGK/tG9Z7MaZwO/vwDP++GMhz17KQqy0XaLaBt1EN3HWNZH/533s5b16wLm6Eup9AZ6C72GPkCrq891YYPvl0rIIk3YKJph3+UQmr9LdNPh+PSRZWjKzrCPEMrN3yN6BuIJy9h1kEP8KSKHwwh4vid4zE0dHz4NnYNeQlch3+AgkkwgnU5rIG8CwxjcSn9ttdbWfu01vufzPp8ZvIJ5C+Z7jUYjf8cAQigwMjI9Lcs7ULVQ/dncbyrF8wMDDztAXP4B88/VQqGqNPeVByYgx+XNzZ1iAcC2FYjLslyEMFAUZbvjEtz1sbGnUCYTFa8tLantdrt2e+IEChvBfQ2IIszV9lYtNUGCcUgUxeg3VVVrqVQ3EM3AXE0RQMIA7hd/IVip1Ov12AvAl84e/fdHaUDAoFKplMvl2HgXIAQPAXwwm83GYrErJAjDzZrCvyEDLULwVjABQRAALK/v7m4s4nmXE5Z/64DnzSfw+ITM+sbKiukSIS4wGvBOeieT75PJSCSS8Hv8/otnjnEhAxj1QhgkEokZD2QNkhjMuDwulxm43V636yA4oOMSEnfD6XQTXR4aQv8B53SeJIK5DuDpnl3gOO4IEcznQvjpxvthAQsimM9p+0HbQLOSFIZCkAThV/ho2EE9d9ThW673nvwDLWrF2ZF5DfoAAAAASUVORK5CYII=');
$helpButtonClick: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAApCAMAAABjq9sOAAABPlBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAwMIAAALAAAOAAAQAAAQBAAYAAAZAwAbAAAeHh4hAAAhICEpAAAsCQUsEw4xBAMxBwMxMDE0MjE3AAA3BAM3MzE8OzxCAABCBABCBQVCFhBCQTlEEAtKBABKBQNKCABKCANKGBBKQUJKRTlKRUJKSUJKTUpMGBNPEwtSAABSCABSGBBSTUpSUUpSU1JSVVJYJRtYV1JaWVpbDQVdDQhjCAVjCAhoFw5oZ2hrCAhuCAVuIxlwGxNzHBBzKyFzb257dWt7dXN7eXt7fXuBIBaBMSaDCwWDDAWEFAiEHxiEOCmEfXuEgnuHKRuJPjGPHRWUDAiUIBiXW02cNimclpScmpylDAilPCmlQTGlWUqtIBitpqW1STm1TTm9EAi9MCG9fWsZTHqDAAAACHRSTlMAESIzVYjM7umjQSUAAAGuSURBVDjLjdTpUoJgFIBhRBGiyLIyK8nIlKgosGjPirJodWmx1FIxt/u/gQ4EDX4i9f4RPA84MsPBMMiH+13CfZgVHiBcC+D2PEi6FvwRMKe+NO0WuoNuoGfo8/P8R/jgek2jXDKFD8MJ8v6Icu3jhSRwzE+Qe9SQDknCb4FweBqpHxzF4/NICw6wP+K4LQ3Zx4oLYJNJD8Cy7EUSOph1BROrF7Va7aHb7T5ZwgaiccIwfK4JlXRdr1TW1ozvdvsAz3dgnt3eLuk9EAOA53MAsgAu9UpvEIQA5N6aOzsASj0EbMBx7BSSZZlWVbXdarUkA6w4QHoZikRoAG0AkgFCThAzAU3LarVafZSkEHoH6zHLV0W1qMK8Dyi/jzqaee908kLKOhVREM1k3vN5QaCGgLEMJAjCuDWfQsEMAkZtsGmBWApKMAxj/8QWAiZNkEh4gLPXeqMxHKRTZ/V643oQiBZYOjkplwuFYxr9mw7AcXNzHoDjDED/AehBoAx79f4H4O1eFN3n66Lxdhv7QQQxggTzdXM/mBtoUVE2IRFSIOMTTh07yHNH/b3lvPfkN1HZaz2zDt62AAAAAElFTkSuQmCC');
+2
View File
@@ -7,3 +7,5 @@ export { SettingsComponent } from './settings-component/settings-component'
export { CheckBoxComponent } from './checkbox-component/checkbox-component' export { CheckBoxComponent } from './checkbox-component/checkbox-component'
export { BorderedHeader } from './bordered-header/bordered-header' export { BorderedHeader } from './bordered-header/bordered-header'
export { MusicComponent } from './music-component/music-component' export { MusicComponent } from './music-component/music-component'
export { MainMenuComponent } from './main-menu-component/main-menu-component'
export { MenuItemComponent } from './menu-item-component/menu-item-component'
@@ -0,0 +1,21 @@
@import "../../app/style";
$menuHeight: 42px;
$menuWidth: 290px;
.main-menu {
position: absolute;
right: 0;
top: 0;
z-index: 3;
display: flex;
align-items: center;
justify-content: flex-end;
border: 1px double $fontColor;
border-image: $rangeBorder 5 5 5 5;
background: $panelBackground center repeat;
border-image-width: 5px;
border-image-repeat: round round;
border-radius: 6px;
padding: 2px 3px;
}
@@ -0,0 +1,17 @@
import * as React from 'react'
import './main-menu-component.scss'
interface Props {
children: React.ReactNode
}
export const MainMenuComponent = ({children}: Props) => {
return (
<div className='main-menu'>
{children}
</div>
)
}
MainMenuComponent.displayName = 'MainMenuComponent'
@@ -0,0 +1,31 @@
@import '../../app/style';
$menuItemWidth: 32px;
$menuItemHeight: 41px;
.menu-item {
width: $menuItemWidth - 4px;
height: $menuItemHeight - 4px;
border-radius: 6px;
background-image: $helpButton;
background-position: center;
background-repeat: no-repeat;
background-size: $menuItemWidth $menuItemHeight;
&--active {
background-image: $helpButtonClick;
}
&:hover {
box-shadow: $hoverBox;
}
&:focus {
outline: none;
box-shadow: $hoverBox;
}
&:active {
background-image: $helpButtonClick;
}
}
@@ -0,0 +1,30 @@
import * as React from 'react'
import { KeyboardEvent } from 'react'
import './menu-item-component.scss'
interface Props {
isActive: boolean
handleClick: () => void
}
export const MenuItemComponent = ({isActive, handleClick}: Props) => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.keyCode !== 13 && e.keyCode !== 32) {
return
}
handleClick()
}
return (
<div
tabIndex={0}
onClick={handleClick}
onKeyDown={handleKeyDown}
className={`menu-item ${isActive ? 'menu-item--active' : ''}`}
/>
)
}
MenuItemComponent.displayName = 'MainMenuComponent'
@@ -57,7 +57,7 @@ export const PanelComponent = ({
} }
} }
const resizePanel = useCallback((animate = true) => { const resetPanel = useCallback((animate = true) => {
if (!panel.current) { if (!panel.current) {
return return
} }
@@ -73,7 +73,7 @@ export const PanelComponent = ({
useEffect(() => { useEffect(() => {
let timeout: NodeJS.Timeout let timeout: NodeJS.Timeout
const handleResize = debounce(() => { const handleResize = debounce(() => {
resizePanel() resetPanel()
timeout = setTimeout(() => { timeout = setTimeout(() => {
if (!panel || !panel.current) { if (!panel || !panel.current) {
return return
@@ -88,33 +88,48 @@ export const PanelComponent = ({
} }
window.removeEventListener('resize', handleResize) window.removeEventListener('resize', handleResize)
} }
}, [panel, resizePanel]) }, [panel, resetPanel])
useEffect(() => { useEffect(() => {
if (!isShown) { if (!isShown) {
resizePanel(false) resetPanel(false)
} }
}, [isShown, resizePanel]) }, [isShown, resetPanel])
const handleDragScroll = (e: MouseEvent) => { const handleDragScroll = (e: MouseEvent) => {
if (!isDrag) { if (!isDrag) {
return return
} }
const overflow = windowOverflow()
if (!overflow) {
return
}
const {clientX, clientY} = e
const value = isBottom ? clientX : clientY
const diff = limiter(value, overflow)
setPosition(diff)
changePosition()
}
const windowOverflow = () => {
const {innerWidth, innerHeight} = window const {innerWidth, innerHeight} = window
const windowSize = isBottom ? innerWidth : innerHeight const windowSize = isBottom ? innerWidth : innerHeight
const containerSize = itemsCount * ((isBottom ? PREVIEW_WIDTH : PREVIEW_HEIGHT) + 15) const containerSize = itemsCount * ((isBottom ? PREVIEW_WIDTH : PREVIEW_HEIGHT) + 15)
if (!(containerSize > windowSize)) { if (!(containerSize > windowSize)) {
return return 0
} }
const overflow = Math.abs(containerSize - windowSize) return Math.abs(containerSize - windowSize)
const {clientX, clientY} = e
const value = isBottom ? clientX : clientY
const diff = trackMouse - value + lastPosition
if (Math.abs(diff) > overflow + 40 || diff < 0) {
return
} }
setPosition(diff)
changePosition() const limiter = (value: number, overflow: number, isWheel: boolean = false) => {
let diff = (!isWheel ? trackMouse : 0) - value + lastPosition
if (Math.abs(diff) > overflow + 40) {
diff = overflow + 40
} else if (diff < 0) {
diff = 0
}
return diff
} }
const changePosition = () => { const changePosition = () => {
@@ -125,8 +140,9 @@ export const PanelComponent = ({
} }
const handleMouseDown = (e: MouseEvent) => { const handleMouseDown = (e: MouseEvent) => {
const {clientX, clientY} = e
e.nativeEvent.stopImmediatePropagation() e.nativeEvent.stopImmediatePropagation()
setTrackMouse(isBottom ? e.clientX : e.clientY) setTrackMouse(isBottom ? clientX : clientY)
setDrag(true) setDrag(true)
} }
@@ -139,19 +155,14 @@ export const PanelComponent = ({
const handleTouchMove = (e: TouchEvent) => { const handleTouchMove = (e: TouchEvent) => {
const {touches} = e const {touches} = e
const {innerWidth, innerHeight} = window
const windowSize = isBottom ? innerWidth : innerHeight
const containerSize = itemsCount * ((isBottom ? PREVIEW_WIDTH : PREVIEW_HEIGHT) + 15)
if (!(containerSize > windowSize)) {
return
}
const overflow = Math.abs(containerSize - windowSize)
const {clientX, clientY} = touches[0] const {clientX, clientY} = touches[0]
const value = isBottom ? clientX : clientY const overflow = windowOverflow()
const diff = trackMouse - value + lastPosition if (!overflow) {
if (Math.abs(diff) > overflow + 40 || diff < 0) {
return return
} }
const value = isBottom ? clientX : clientY
const diff = limiter(value, overflow)
setPosition(diff) setPosition(diff)
changePosition() changePosition()
} }
@@ -164,17 +175,13 @@ export const PanelComponent = ({
const handleScroll = (e: WheelEvent) => { const handleScroll = (e: WheelEvent) => {
const {deltaY} = e const {deltaY} = e
const {innerWidth, innerHeight} = window const overflow = windowOverflow()
const windowSize = isBottom ? innerWidth : innerHeight if (!overflow) {
const containerSize = itemsCount * ((isBottom ? PREVIEW_WIDTH : PREVIEW_HEIGHT) + 15)
if (!(containerSize > windowSize)) {
return
}
const overflow = Math.abs(containerSize - windowSize)
const diff = (deltaY > 0 ? 80 : -80) + lastPosition
if (Math.abs(diff) > overflow + 40 || diff < 0) {
return return
} }
const value = deltaY > 0 ? 80 : -80
const diff = limiter(value, overflow, true)
setPosition(diff) setPosition(diff)
changePosition() changePosition()
setLastPosition(position) setLastPosition(position)
@@ -56,7 +56,7 @@ export const RangeComponent = ({handleChange, defaultValue}: Props) => {
} }
const {width, left} = (stick.current.parentNode as HTMLDivElement).getBoundingClientRect() const {width, left} = (stick.current.parentNode as HTMLDivElement).getBoundingClientRect()
const {clientX} = e const {clientX} = e
const diff = clientX - left const diff = clientX - left - 20
if (diff > width - 35 || diff < 0) { if (diff > width - 35 || diff < 0) {
return return
} }
@@ -25,34 +25,6 @@
opacity: 0; opacity: 0;
} }
} }
&-author {
z-index: 2;
position: absolute;
right: 10px;
top: 10px;
width: 110px;
font-family: $font;
text-shadow: $fontShadow;
color: $fontColor;
font-size: 16px;
display: flex;
align-items: center;
justify-content: space-between;
opacity: .3;
a {
cursor: $cursorPointer, auto;
color: inherit;
text-decoration: none;
&:focus {
outline: none;
box-shadow: $hoverBox;
}
}
}
} }
@keyframes pulse { @keyframes pulse {
@@ -37,10 +37,6 @@ export const ViewComponent = ({src}: Props) => {
}} }}
> >
<div className={`view-background ${isLoaded ? 'view-background--loaded' : ''}`}/> <div className={`view-background ${isLoaded ? 'view-background--loaded' : ''}`}/>
<div className='view-author'>
<a href="https://github.com/obergodmar">obergodmar</a>
<span>1.1.0</span>
</div>
</div> </div>
) )
} }
+4 -7
View File
@@ -20,15 +20,12 @@ export const randomNumber = (min: number, max: number) => (
Math.floor(Math.random() * (max - min)) + min Math.floor(Math.random() * (max - min)) + min
) )
export const debounce = (fn: () => any, ms: number) => { export function debounce(fn: (args: any) => any, ms: number) {
let timer: NodeJS.Timeout | null let timer: NodeJS.Timeout
return () => { return (...args: any) => {
if (timer) { if (timer) {
clearTimeout(timer) clearTimeout(timer)
} }
timer = setTimeout(() => { timer = setTimeout(() => fn.apply(this, args), ms)
timer = null
fn()
}, ms)
} }
} }
+1
View File
@@ -15,6 +15,7 @@
"module": "esnext", "module": "esnext",
"moduleResolution": "node", "moduleResolution": "node",
"downlevelIteration": true, "downlevelIteration": true,
"noImplicitThis": false,
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true,
"noEmit": true, "noEmit": true,