Add background music; add favicon; add sound module

This commit is contained in:
obergodmar
2020-06-25 18:33:10 +03:00
parent 8647adc66c
commit 1103ebec6f
38 changed files with 324 additions and 140 deletions
+73 -24
View File
@@ -1,10 +1,10 @@
import * as React from 'react'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import UIfx from 'uifx'
import { KeyboardEvent, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
import Sound from '../modules/sound'
import { PanelComponent, PreviewComponent, SettingsComponent, ViewComponent } from '../components'
import places from '../assets'
import { delay, UI_SOUND_VOLUME } from '../utils'
import { delay, UI_MUSIC_VOLUME, UI_SOUND_VOLUME } from '../utils'
import { useSettings } from '../hooks'
import PanelOpenAudio from '../assets/audio/sound/panel-open.ogg'
@@ -16,23 +16,37 @@ import SettingsCloseAudio from '../assets/audio/sound/menu-close.ogg'
import CheckBoxOnAudio from '../assets/audio/sound/check-box-on.ogg'
import CheckBoxOffAudio from '../assets/audio/sound/check-box-off.ogg'
import StormwindParkMusic1 from '../assets/audio/music/stormwind-park-music-1.mp3'
import StormwindParkMusic2 from '../assets/audio/music/stormwind-park-music-2.mp3'
import './style.scss'
export default function App() {
const {settings} = useSettings()
const [isSettingsShown, setSettingsShown] = useState(false)
const [isLoading, setLoading] = useState(false)
const [isPlaying, setPlaying] = useState(false)
const [isLeftPanelShown, setLeftPanelShown] = useState(false)
const [isBottomPanelShown, setBottomPanelShown] = useState(false)
const [activePlace, setActivePlace] = useState(0)
const [activeView, setActiveView] = useState(0)
const panelOpenSound = useMemo(() => new UIfx(PanelOpenAudio), [])
const panelCloseSound = useMemo(() => new UIfx(PanelCloseAudio), [])
const settingsOpenSound = useMemo(() => new UIfx(SettingsOpenAudio), [])
const settingsCloseSound = useMemo(() => new UIfx(SettingsCloseAudio), [])
const checkboxOnSound = useMemo(() => new UIfx(CheckBoxOnAudio), [])
const checkboxOffSound = useMemo(() => new UIfx(CheckBoxOffAudio), [])
const soundLoad = (soundFile: string, soundVolume: number) => {
const sound = new Sound(soundFile)
sound.setVolume(soundVolume)
return sound
}
const panelOpenSound = useMemo(() => soundLoad(PanelOpenAudio, UI_SOUND_VOLUME), [])
const panelCloseSound = useMemo(() => soundLoad(PanelCloseAudio, UI_SOUND_VOLUME), [])
const settingsOpenSound = useMemo(() => soundLoad(SettingsOpenAudio, UI_SOUND_VOLUME), [])
const settingsCloseSound = useMemo(() => soundLoad(SettingsCloseAudio, UI_SOUND_VOLUME), [])
const checkboxOnSound = useMemo(() => soundLoad(CheckBoxOnAudio, UI_SOUND_VOLUME), [])
const checkboxOffSound = useMemo(() => soundLoad(CheckBoxOffAudio, UI_SOUND_VOLUME), [])
const StormwindMusic1 = useMemo(() => soundLoad(StormwindParkMusic1, UI_MUSIC_VOLUME), [])
const StormwindMusic2 = useMemo(() => soundLoad(StormwindParkMusic2, UI_MUSIC_VOLUME), [])
const [currentPlaying, setCurrentPlaying] = useState(StormwindMusic1)
const app = useRef<HTMLDivElement>(null)
@@ -62,29 +76,56 @@ export default function App() {
}
useEffect(() => {
appFocus()
}, [])
const appFocus = () => {
if (app && app.current) {
app.current.focus()
}
}
}, [app])
useLayoutEffect(() => {
document.title = settings.language['place.stormwind-park']
}, [settings.language])
useEffect(() => {
StormwindMusic1.audio.onplay = () => {
setCurrentPlaying(StormwindMusic1)
setPlaying(true)
}
StormwindMusic2.audio.onplay = () => {
setCurrentPlaying(StormwindMusic2)
setPlaying(true)
}
StormwindMusic1.audio.onended = () => {
StormwindMusic2.playMusic()
}
StormwindMusic2.audio.onended = () => {
StormwindMusic1.playMusic()
}
return () => {
StormwindMusic1.audio.onplay = null
StormwindMusic2.audio.onplay = null
StormwindMusic1.audio.onended = null
StormwindMusic2.audio.onended = null
}
}, [StormwindMusic1, StormwindMusic2])
const appClick = () => currentPlaying.playMusic()
const openCloseSettings = () => {
setSettingsShown(!isSettingsShown)
appFocus()
if (app && app.current) {
app.current.focus()
}
if (!settings.uiSound) {
return
}
if (isSettingsShown) {
settingsCloseSound.play(UI_SOUND_VOLUME)
settingsCloseSound.playSound()
} else {
settingsOpenSound.play(UI_SOUND_VOLUME)
settingsOpenSound.playSound()
}
}
const handleOpenSettings = (e: React.KeyboardEvent) => {
const handleOpenSettings = (e: KeyboardEvent) => {
switch (e.keyCode) {
case 27:
if (isLeftPanelShown || isBottomPanelShown) {
@@ -94,20 +135,28 @@ export default function App() {
}
openCloseSettings()
break
case 32:
if (isPlaying) {
currentPlaying.pause()
setPlaying(false)
} else {
currentPlaying.playMusic()
}
}
}
return (
<div
ref={app}
onClick={appClick}
onKeyDown={handleOpenSettings}
tabIndex={0}
className='main'
>
<ViewComponent src={places[activePlace].view[activeView]}/>
<PanelComponent
openSound={panelOpenSound}
closeSound={panelCloseSound}
openSoundPlay={panelOpenSound.playSound}
closeSoundPlay={panelCloseSound.playSound}
itemsCount={places.length || 0}
orientation='left'
isShown={isLeftPanelShown}
@@ -124,8 +173,8 @@ export default function App() {
))}
</PanelComponent>
<PanelComponent
openSound={panelOpenSound}
closeSound={panelCloseSound}
openSoundPlay={panelOpenSound.playSound}
closeSoundPlay={panelCloseSound.playSound}
itemsCount={places[activePlace].preview.length || 0}
orientation='bottom'
isShown={isBottomPanelShown}
@@ -144,8 +193,8 @@ export default function App() {
{isSettingsShown && (
<SettingsComponent
closeSettings={openCloseSettings}
checkboxOnSound={checkboxOnSound}
checkboxOffSound={checkboxOffSound}
checkboxOnSoundPlay={checkboxOnSound.playSound}
checkboxOffSoundPlay={checkboxOffSound.playSound}
/>
)
}
Binary file not shown.
Binary file not shown.
@@ -1,8 +1,7 @@
import * as React from 'react'
import { FocusEvent, MouseEvent, useEffect, useMemo, useRef, useState, WheelEvent } from 'react'
import UIfx from 'uifx'
import { PREVIEW_HEIGHT, PREVIEW_WIDTH, UI_SOUND_VOLUME } from '../../utils'
import { PREVIEW_HEIGHT, PREVIEW_WIDTH } from '../../utils'
import { useSettings } from '../../hooks'
import './panel-component.scss'
@@ -12,8 +11,8 @@ interface Props {
isShown: boolean
itemsCount: number
setShown: () => void
openSound: UIfx
closeSound: UIfx
openSoundPlay: (volume?: number) => void
closeSoundPlay: (volume?: number) => void
children: React.ReactNode
}
@@ -21,8 +20,8 @@ export const PanelComponent = ({
isShown,
setShown,
children,
openSound,
closeSound,
openSoundPlay,
closeSoundPlay,
itemsCount,
orientation
}: Props) => {
@@ -49,9 +48,9 @@ export const PanelComponent = ({
return
}
if (isShown) {
openSound.play(UI_SOUND_VOLUME)
openSoundPlay()
} else {
closeSound.play(UI_SOUND_VOLUME)
closeSoundPlay()
}
}
@@ -1,25 +1,22 @@
import * as React from 'react'
import UIfx from 'uifx'
import { useSettings } from '../../hooks'
import ru from '../../locales/ru.json'
import en from '../../locales/en.json'
import { useSettings } from '../../hooks'
import { Settings } from '../../settings-context'
import { CheckBoxComponent, SelectComponent } from '..'
import './settings-component.scss'
import { Settings } from '../../settings-context'
import { UI_SOUND_VOLUME } from '../../utils'
import { CheckBoxComponent, SelectComponent } from '..'
type languageValue = keyof typeof ru;
interface Props {
closeSettings: () => void
checkboxOnSound: UIfx
checkboxOffSound: UIfx
checkboxOnSoundPlay: (volume?: number) => void
checkboxOffSoundPlay: (volume?: number) => void
}
export const SettingsComponent = ({closeSettings, checkboxOnSound, checkboxOffSound}: Props) => {
export const SettingsComponent = ({closeSettings, checkboxOnSoundPlay, checkboxOffSoundPlay}: Props) => {
const {settings, saveSettings} = useSettings()
const handleCheckboxClick = (option: keyof Settings) => {
@@ -28,9 +25,9 @@ export const SettingsComponent = ({closeSettings, checkboxOnSound, checkboxOffSo
return
}
if (settings[option]) {
checkboxOffSound.play(UI_SOUND_VOLUME)
checkboxOffSoundPlay()
} else {
checkboxOnSound.play(UI_SOUND_VOLUME)
checkboxOnSoundPlay()
}
}
@@ -46,7 +43,7 @@ export const SettingsComponent = ({closeSettings, checkboxOnSound, checkboxOffSo
if (!settings.uiSound) {
return
}
checkboxOnSound.play(UI_SOUND_VOLUME)
checkboxOnSoundPlay()
}
const renderOption = (option: keyof Settings, value: boolean | string[] = []) => {
@@ -9,9 +9,45 @@
min-height: 100vh;
background-position: center;
background-repeat: no-repeat;
transition: background-image $transitionDuration $transitionType;
animation: pulse 10s infinite;
animation-direction: alternate;
&-background {
z-index: 2;
opacity: 1;
position: absolute;
min-width: inherit;
min-height: inherit;
background: $backgroundTexture;
transition: opacity $transitionDuration $transitionType;
&--loaded {
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;
}
}
}
@keyframes pulse {
@@ -12,19 +12,22 @@ interface Props {
export const ViewComponent = ({src}: Props) => {
const [imageSrc, setImageSrc] = useState(Background)
const [isLoaded, setLoaded] = useState(false)
useEffect(() => {
setLoaded(false)
const timer = setTimeout(() => {
const image = new Image()
image.src = src
image.onload = () => {
setImageSrc(src)
setLoaded(true)
}
}, ANIMATION_DURATION)
return () => {
clearTimeout(timer)
}
}, [src])
}, [src, imageSrc])
return (
<div
@@ -32,7 +35,13 @@ export const ViewComponent = ({src}: Props) => {
style={{
backgroundImage: `url(${imageSrc})`
}}
/>
>
<div className={`view-background ${isLoaded ? 'view-background--loaded' : ''}`}/>
<div className='view-author'>
<a href="https://github.com/obergodmar">obergodmar</a>
<span>1.0.0</span>
</div>
</div>
)
}
+2 -1
View File
@@ -5,5 +5,6 @@
"ui.main-menu": "Main Menu",
"ui.uiSound": "UI Sound",
"ui.uiLanguage": "Language",
"ui.language": "English"
"ui.language": "English",
"place.stormwind-park": "Stormwind Park"
}
+2 -1
View File
@@ -5,5 +5,6 @@
"ui.main-menu": "Главное меню",
"ui.uiSound": "Звуки интерфейса",
"ui.uiLanguage": "Язык",
"ui.language": "Русский"
"ui.language": "Русский",
"place.stormwind-park": "Парк Штормграда"
}
+83
View File
@@ -0,0 +1,83 @@
interface Config {
volume?: number
}
const name = 'UIAudio'
export default class Sound {
public audio: HTMLAudioElement
private volume: number
constructor(file: string, config?: Config) {
const validateURI = (fileURI: string) => {
if (fileURI) {
return fileURI
} else {
throw Error('Requires valid URI path for "file"')
}
}
const volume = this.validateVolume(config && config.volume)
const appendAudioElement = (fileValue: string) => {
const hashFn = (str: string) => {
let hash = 0
if (str.length === 0) {
return hash
}
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i)
hash = (hash << 5) - hash + char
hash = hash & hash
}
return Math.abs(hash)
}
const id = `${name}-${hashFn(fileValue)}`
const audioElement = document.createElement('audio')
audioElement.id = id
audioElement.src = file
audioElement.preload = 'auto'
document.body.appendChild(audioElement)
return file
}
const audioNode = appendAudioElement(validateURI(file))
this.audio = new Audio(audioNode)
this.audio.load()
this.volume = volume
}
public setVolume = (volume: number) => {
this.validateVolume(volume)
this.volume = volume
return this
}
public playSound = (volume: number = this.volume) => {
this.audio.volume = this.validateVolume(volume)
if (!this.audio.readyState) {
return
}
this.audio.play().catch((error: Error) => console.error(`Error playback: ${error}`))
}
public playMusic = (volume: number = this.volume) => {
this.audio.volume = this.validateVolume(volume)
if (!this.audio.readyState) {
return
}
this.audio.play().catch((error: Error) => console.error(`Error playback: ${error}`))
}
public pause = () => this.audio.pause()
private validateVolume = (volumeValue: number = 1.0) => {
if (volumeValue && (volumeValue < 0 || volumeValue > 1)) {
throw Error('"Volume" must be an number between 0.0 and 1.0')
}
return volumeValue
}
}
+3
View File
@@ -0,0 +1,3 @@
/// <reference types="react-scripts" />
declare module '*.ogg'
declare module '*.mp3'
+1
View File
@@ -3,3 +3,4 @@ export const LOADING_DURATION = 800
export const PREVIEW_WIDTH = 320
export const PREVIEW_HEIGHT = 180
export const UI_SOUND_VOLUME = 0.2
export const UI_MUSIC_VOLUME = 1
+1
View File
@@ -3,6 +3,7 @@ import { LOADING_DURATION } from './constants'
export {
PREVIEW_WIDTH,
PREVIEW_HEIGHT,
UI_MUSIC_VOLUME,
UI_SOUND_VOLUME,
LOADING_DURATION,
ANIMATION_DURATION