Add mobile improvements

This commit is contained in:
obergodmar
2020-07-13 02:04:00 +03:00
parent 8cebb45636
commit d3cc79fac5
16 changed files with 240 additions and 72 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "wow-best-places",
"description": "This app is supposed to make you feel nostalgic",
"version": "1.2.0",
"version": "1.3.0",
"author": {
"name": "obergodmar",
"email": "obergodmar@gmail.com",
+14 -9
View File
@@ -69,9 +69,13 @@ export default function App() {
})
}, [isLoading])
const handleLeftPreviewClick = (value: number) => delayedChange(setActivePlace, value)
const handleLeftPreviewClick = useCallback((value: number) =>
delayedChange(setActivePlace, value),
[delayedChange])
const handleBottomPreviewClick = (value: number) => delayedChange(setActiveView, value)
const handleBottomPreviewClick = useCallback((value: number) =>
delayedChange(setActiveView, value),
[delayedChange])
useEffect(() => {
if (app && app.current) {
@@ -89,11 +93,12 @@ export default function App() {
return
}
currentPlaying.setVolume(musicVolume)
currentPlaying.playMusic()
}, [currentPlaying, musicVolume])
const appClick = () => currentPlaying && currentPlaying.playMusic()
const appClick = useCallback(() => currentPlaying && currentPlaying.playMusic(), [currentPlaying])
const openCloseSettings = () => {
const openCloseSettings = useCallback(() => {
setSettingsShown(!isSettingsShown)
if (app && app.current) {
app.current.focus()
@@ -106,9 +111,9 @@ export default function App() {
} else {
settingsOpenSound.playSound()
}
}
}, [app, isSettingsShown, uiSound, settingsCloseSound, settingsOpenSound])
const handleOpenSettings = (e: KeyboardEvent) => {
const handleOpenSettings = useCallback((e: KeyboardEvent) => {
switch (e.keyCode) {
case 27:
if (isLeftPanelShown || isBottomPanelShown) {
@@ -129,7 +134,7 @@ export default function App() {
currentPlaying.playMusic()
}
}
}
}, [isLeftPanelShown, isBottomPanelShown, isPlaying, currentPlaying, openCloseSettings])
return (
<div
@@ -139,11 +144,11 @@ export default function App() {
tabIndex={0}
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>
<span>v1.3.0</span>
</div>
<MenuItemComponent
isActive={isSettingsShown}
+3 -1
View File
@@ -38,13 +38,15 @@ body {
.author {
padding: 0 10px;
width: 130px;
width: 0;
opacity: 0;
font-family: $font;
text-shadow: $fontShadow;
color: $fontColor;
font-size: 16px;
display: flex;
align-items: center;
transition: width $transitionDuration $transitionType, opacity $transitionDuration $transitionType;
justify-content: space-between;
a {
@@ -1,5 +1,5 @@
import * as React from 'react'
import { KeyboardEvent } from 'react'
import { KeyboardEvent, useCallback } from 'react'
import './checkbox-component.scss'
@@ -11,12 +11,13 @@ interface Props {
export const CheckBoxComponent = ({handleClick, optionName, value}: Props) => {
const handleKeyDown = (e: KeyboardEvent, option: any) => {
const handleKeyDown = useCallback((e: KeyboardEvent, option: any) => {
if (e.keyCode !== 13 && e.keyCode !== 32) {
return
}
handleClick(option)
}
}, [handleClick])
return (
<div
tabIndex={0}
@@ -18,4 +18,11 @@ $menuWidth: 290px;
border-image-repeat: round round;
border-radius: 6px;
padding: 2px 3px;
&:hover {
.author {
width: 130px;
opacity: 1;
}
}
}
@@ -4,6 +4,7 @@ $menuItemWidth: 32px;
$menuItemHeight: 41px;
.menu-item {
z-index: 4;
width: $menuItemWidth - 4px;
height: $menuItemHeight - 4px;
border-radius: 6px;
@@ -1,5 +1,5 @@
import * as React from 'react'
import { KeyboardEvent } from 'react'
import { KeyboardEvent, useCallback } from 'react'
import './menu-item-component.scss'
@@ -10,12 +10,12 @@ interface Props {
export const MenuItemComponent = ({isActive, handleClick}: Props) => {
const handleKeyDown = (e: KeyboardEvent) => {
const handleKeyDown = useCallback((e: KeyboardEvent) => {
if (e.keyCode !== 13 && e.keyCode !== 32) {
return
}
handleClick()
}
}, [handleClick])
return (
<div
@@ -11,7 +11,7 @@ import {
WheelEvent
} from 'react'
import { ANIMATION_DURATION, debounce, PREVIEW_HEIGHT, PREVIEW_WIDTH } from '../../utils'
import { ANIMATION_DURATION, debounce, PREVIEW_HEIGHT, PREVIEW_WIDTH, SPACE } from '../../utils'
import { useSettings } from '../../hooks'
import './panel-component.scss'
@@ -37,7 +37,7 @@ export const PanelComponent = ({
}: Props) => {
const {settings: {language, uiSound}} = useSettings()
const [isDrag, setDrag] = useState(false)
const [trackMouse, setTrackMouse] = useState(0)
const [trackPosition, setTrackPosition] = useState(0)
const [position, setPosition] = useState(0)
const [lastPosition, setLastPosition] = useState(0)
@@ -65,7 +65,7 @@ export const PanelComponent = ({
panel.current.style.transition = `transform 0.5s`
}
panel.current.style.transform = `unset`
setTrackMouse(0)
setTrackPosition(0)
setPosition(0)
setLastPosition(0)
}, [panel])
@@ -123,9 +123,9 @@ export const PanelComponent = ({
}
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
let diff = (!isWheel ? trackPosition : 0) - value + lastPosition
if (Math.abs(diff) > overflow + SPACE) {
diff = overflow + SPACE
} else if (diff < 0) {
diff = 0
}
@@ -142,14 +142,14 @@ export const PanelComponent = ({
const handleMouseDown = (e: MouseEvent) => {
const {clientX, clientY} = e
e.nativeEvent.stopImmediatePropagation()
setTrackMouse(isBottom ? clientX : clientY)
setTrackPosition(isBottom ? clientX : clientY)
setDrag(true)
}
const handleTouchstart = (e: TouchEvent) => {
const {touches} = e
e.nativeEvent.stopImmediatePropagation()
setTrackMouse(isBottom ? touches[0].clientX : touches[0].clientY)
setTrackPosition(isBottom ? touches[0].clientX : touches[0].clientY)
setDrag(true)
}
@@ -180,7 +180,7 @@ export const PanelComponent = ({
return
}
const value = deltaY > 0 ? 80 : -80
const value = deltaY > 0 ? -80 : 80
const diff = limiter(value, overflow, true)
setPosition(diff)
changePosition()
@@ -1,5 +1,5 @@
import * as React from 'react'
import { useEffect, useMemo, useState } from 'react'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { useSettings } from '../../hooks'
import { Plug } from '../../assets'
@@ -33,10 +33,10 @@ export const PreviewComponent = ({name = '', src, value, handleChange, isLoading
}
}, [image])
const handleClick = (e: React.MouseEvent) => {
const handleClick = useCallback((e: React.MouseEvent) => {
e.preventDefault()
handleChange(value)
}
}, [handleChange, value])
return (
<div
@@ -16,6 +16,9 @@ export const RangeComponent = ({handleChange, defaultValue}: Props) => {
const stick = useRef<HTMLDivElement>(null)
const handleFocus = useCallback(() => setPressed(true), [])
const handleFree = useCallback(() => setPressed(false), [])
const handleKeyDown = (e: KeyboardEvent) => {
if (!stick || !stick.current || !stick.current.parentNode) {
return
@@ -23,43 +26,57 @@ export const RangeComponent = ({handleChange, defaultValue}: Props) => {
const {width} = (stick.current.parentNode as HTMLDivElement).getBoundingClientRect()
switch (e.keyCode) {
case 37:
if (position - 5 < 0) {
return
}
setPosition(position - 5)
handleChange(position / MAX)
const minusValue = position - 5
const minusDiff = limiter(minusValue, width)
setPosition(minusDiff)
handleChange(minusDiff / MAX)
break
case 39:
if (position + 5 > width - 35) {
return
}
setPosition(position + 5)
handleChange(position / MAX)
const plusValue = position + 5
const plusDiff = limiter(plusValue, width)
setPosition(plusDiff)
handleChange(plusDiff / MAX)
break
}
}
const handleMouseDown = useCallback(() => {
setPressed(true)
}, [])
const handleMouseUp = useCallback(() => {
setPressed(false)
}, [])
const handleMouseMove = useCallback((e: MouseEvent) => {
if (!isPressed) {
return
}
const handlePoint = useCallback((e: React.MouseEvent | MouseEvent) => {
if (!stick || !stick.current || !stick.current.parentNode) {
return
}
const {width, left} = (stick.current.parentNode as HTMLDivElement).getBoundingClientRect()
const {clientX} = e
const diff = clientX - left - 20
if (diff > width - 35 || diff < 0) {
const value = clientX - left - 20
const diff = limiter(value, width)
setPosition(diff)
handleChange(diff / MAX)
}, [stick, handleChange])
const handleMouseMove = useCallback((e: MouseEvent) => {
if (!isPressed) {
return
}
handlePoint(e)
}, [isPressed, handlePoint])
const handleTouchMove = useCallback((e: TouchEvent) => {
if (!isPressed) {
return
}
const {touches} = e
const {clientX} = touches[0]
if (!stick || !stick.current || !stick.current.parentNode) {
return
}
const {width, left} = (stick.current.parentNode as HTMLDivElement).getBoundingClientRect()
const value = clientX - left - 20
const diff = limiter(value, width)
setPosition(diff)
handleChange(diff / MAX)
}, [isPressed, stick, handleChange])
@@ -73,27 +90,42 @@ export const RangeComponent = ({handleChange, defaultValue}: Props) => {
range.focus()
const {deltaY} = e
const value = position + (deltaY > 0 ? -5 : 5)
if (value > width - 35 || value < 0) {
return
const diff = limiter(value, width)
setPosition(diff)
handleChange(diff / MAX)
}
const limiter = (value: number, width: number) => {
let diff = value
if (diff > width - 35) {
diff = MAX
} else if (diff < 0) {
diff = 0
}
setPosition(value)
handleChange(value / MAX)
return diff
}
useEffect(() => {
window.addEventListener('mousemove', handleMouseMove)
window.addEventListener('mouseup', handleMouseUp)
window.addEventListener('mouseup', handleFree)
window.addEventListener('touchmove', handleTouchMove)
window.addEventListener('touchend', handleFree)
return () => {
window.removeEventListener('mousemove', handleMouseMove)
window.removeEventListener('mouseup', handleMouseUp)
window.removeEventListener('mouseup', handleFree)
window.removeEventListener('touchmove', handleTouchMove)
window.removeEventListener('touchend', handleFree)
}
}, [handleMouseMove, handleMouseUp])
}, [handleMouseMove, handleTouchMove, handleFree])
return (
<div
tabIndex={0}
onKeyDown={handleKeyDown}
onMouseDown={handleMouseDown}
onMouseDown={handleFocus}
onClick={handlePoint}
onTouchStart={handleFocus}
onWheel={handleScroll}
className='range'
>
@@ -1,5 +1,5 @@
import * as React from 'react'
import { FocusEvent, KeyboardEvent, useRef, useState } from 'react'
import { FocusEvent, KeyboardEvent, useCallback, useRef, useState } from 'react'
import './select-component.scss'
@@ -14,7 +14,7 @@ export const SelectComponent = ({children, options, current, handleChange}: Prop
const [isSelectShown, setSelectShown] = useState(false)
const dropDownRef = useRef<HTMLDivElement>(null)
const handleSelectClick = () => setSelectShown(!isSelectShown)
const handleSelectClick = useCallback(() => setSelectShown(!isSelectShown), [isSelectShown])
const handleBlur = (e: FocusEvent) => {
if (!dropDownRef || !dropDownRef.current) {
@@ -52,7 +52,7 @@ export const SelectComponent = ({children, options, current, handleChange}: Prop
className={`select ${isSelectShown ? 'select--opened' : ''}`}
>
{children}
<div className='select-arrow'/>
<div className='select-arrow' />
{isSelectShown && (
<div
ref={dropDownRef}
@@ -7,7 +7,6 @@
left: 0;
min-width: 100vw;
min-height: 100vh;
background-position: center;
background-repeat: no-repeat;
animation: pulse 10s infinite;
animation-direction: alternate;
@@ -18,6 +17,7 @@
position: absolute;
min-width: inherit;
min-height: inherit;
background-size: cover;
background: $backgroundTexture;
transition: opacity $transitionDuration $transitionType;
@@ -1,8 +1,8 @@
import * as React from 'react'
import { useEffect, useState } from 'react'
import { FocusEvent, MouseEvent, TouchEvent, useCallback, useEffect, useState } from 'react'
import { Background } from '../../assets'
import { ANIMATION_DURATION } from '../../utils'
import { ANIMATION_DURATION, DEFAULT_HEIGHT, DEFAULT_WIDTH } from '../../utils'
import './view-component.scss'
@@ -10,11 +10,42 @@ interface Props {
src: string
}
interface Position {
x: number
y: number
}
const initialPosition = {
x: 0,
y: 0
}
export const ViewComponent = ({src}: Props) => {
const [imageSrc, setImageSrc] = useState(Background)
const [isLoaded, setLoaded] = useState(false)
const [isDrag, setDrag] = useState(false)
const [trackPosition, setTrackPosition] = useState(initialPosition)
const [position, setPosition] = useState(initialPosition)
const [lastPosition, setLastPosition] = useState(initialPosition)
const handleResize = useCallback(() => {
const {innerWidth, innerHeight} = window
let width = (innerWidth - DEFAULT_WIDTH) / 2
let height = (innerHeight - DEFAULT_HEIGHT) / 2
if (innerWidth >= DEFAULT_WIDTH) {
width = 0
}
if (innerHeight >= DEFAULT_HEIGHT) {
height = 0
}
setPosition({x: width, y: height})
setLastPosition({x: width, y: height})
}, [])
useEffect(() => {
handleResize()
setLoaded(false)
const timer = setTimeout(() => {
const image = new Image()
@@ -27,16 +58,99 @@ export const ViewComponent = ({src}: Props) => {
return () => {
clearTimeout(timer)
}
}, [src])
}, [src, handleResize])
useEffect(() => {
window.addEventListener('resize', handleResize)
return () => {
window.removeEventListener('resize', handleResize)
}
}, [handleResize])
const handleTouchMove = (e: TouchEvent) => {
const {touches} = e
const {innerWidth, innerHeight} = window
const width = DEFAULT_WIDTH - innerWidth
const height = DEFAULT_HEIGHT - innerHeight
const {clientX: x, clientY: y} = touches[0]
const diff = limiter({x, y}, width, height)
setPosition(diff)
}
const limiter = (value: Position, width: number, height: number) => {
const {x: xValue, y: yValue} = value
let x = trackPosition.x - xValue + lastPosition.x
let y = trackPosition.y - yValue + lastPosition.y
if (x > 0) {
x = 0
} else if (x < -width) {
x = -width
}
if (y > 0) {
y = 0
} else if (y < -height) {
y = -height
}
return ({x, y})
}
const handleTouchstart = (e: TouchEvent) => {
const {touches} = e
e.nativeEvent.stopImmediatePropagation()
const {clientX: x, clientY: y} = touches[0]
setTrackPosition({x, y})
setDrag(true)
}
const handleMouseDown = (e: MouseEvent) => {
const {clientX, clientY} = e
e.nativeEvent.stopImmediatePropagation()
setTrackPosition({x: clientX, y: clientY})
setDrag(true)
}
const handleFree = (e: MouseEvent | FocusEvent | TouchEvent) => {
e.nativeEvent.stopImmediatePropagation()
setDrag(false)
setLastPosition(position)
}
const handleDragScroll = (e: MouseEvent) => {
if (!isDrag) {
return
}
const {innerWidth, innerHeight} = window
const width = DEFAULT_WIDTH - innerWidth
const height = DEFAULT_HEIGHT - innerHeight
const {clientX: x, clientY: y} = e
const diff = limiter({x, y}, width, height)
setPosition(diff)
}
return (
<div
className='view'
style={{
backgroundImage: `url(${imageSrc})`
backgroundImage: `url(${imageSrc})`,
backgroundPosition: `${position.x}px ${position.y}px`
}}
onMouseDown={handleMouseDown}
onMouseMove={handleDragScroll}
onMouseUp={handleFree}
onTouchMove={handleTouchMove}
onTouchStart={handleTouchstart}
onTouchEnd={handleFree}
onMouseLeave={handleFree}
onBlur={handleFree}
>
<div className={`view-background ${isLoaded ? 'view-background--loaded' : ''}`}/>
<div className={`view-background ${isLoaded ? 'view-background--loaded' : ''}`} />
</div>
)
}
+1 -1
View File
@@ -17,7 +17,7 @@ const defaultSettings: Settings = {
ReactDom.render(
<SettingsProvider settings={defaultSettings}>
<App/>
<App />
</SettingsProvider>,
document.getElementById('root')
)
+3
View File
@@ -4,3 +4,6 @@ export const PREVIEW_WIDTH = 320
export const PREVIEW_HEIGHT = 180
export const UI_SOUND_VOLUME = 0.2
export const UI_MUSIC_VOLUME = 1
export const SPACE = 200
export const DEFAULT_WIDTH = 1920
export const DEFAULT_HEIGHT = 1080
+4 -1
View File
@@ -7,7 +7,10 @@ export {
UI_MUSIC_VOLUME,
UI_SOUND_VOLUME,
LOADING_DURATION,
ANIMATION_DURATION
ANIMATION_DURATION,
DEFAULT_HEIGHT,
DEFAULT_WIDTH,
SPACE
} from './constants'
export const delay = () => new Promise(resolve => setTimeout(resolve, LOADING_DURATION))