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
+2
View File
@@ -1,2 +1,4 @@
node_modules
package-lock.json
build
.idea
-5
View File
@@ -1,5 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
-21
View File
@@ -1,21 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<TypeScriptCodeStyleSettings version="0">
<option name="USE_SEMICOLON_AFTER_STATEMENT" value="false" />
<option name="FORCE_SEMICOLON_STYLE" value="true" />
<option name="REFORMAT_C_STYLE_COMMENTS" value="true" />
<option name="USE_DOUBLE_QUOTES" value="false" />
<option name="FORCE_QUOTE_STYlE" value="true" />
<option name="ENFORCE_TRAILING_COMMA" value="Remove" />
<option name="SPACES_WITHIN_IMPORTS" value="true" />
<option name="SPACES_WITHIN_OBJECT_TYPE_BRACES" value="false" />
</TypeScriptCodeStyleSettings>
<codeStyleSettings language="TypeScript">
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
<option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
<indentOptions>
<option name="SMART_TABS" value="true" />
</indentOptions>
</codeStyleSettings>
</code_scheme>
</component>
-5
View File
@@ -1,5 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>
-6
View File
@@ -1,6 +0,0 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="TsLint" enabled="true" level="WARNING" enabled_by_default="true" />
</profile>
</component>
-6
View File
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptSettings">
<option name="languageLevel" value="ES6" />
</component>
</project>
-8
View File
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/wow-best-places.iml" filepath="$PROJECT_DIR$/.idea/wow-best-places.iml" />
</modules>
</component>
</project>
Generated
-6
View File
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
-4
View File
@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectTasksOptions" suppressed-tasks="SCSS" />
</project>
-12
View File
@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
<excludeFolder url="file://$MODULE_DIR$/temp" />
<excludeFolder url="file://$MODULE_DIR$/tmp" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
+3 -9
View File
@@ -1575,9 +1575,9 @@
"integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug=="
},
"@types/react": {
"version": "16.9.38",
"resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.38.tgz",
"integrity": "sha512-pHAeZbjjNRa/hxyNuLrvbxhhnKyKNiLC6I5fRF2Zr/t/S6zS41MiyzH4+c+1I9vVfvuRt1VS2Lodjr4ZWnxrdA==",
"version": "16.9.41",
"resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.41.tgz",
"integrity": "sha512-6cFei7F7L4wwuM+IND/Q2cV1koQUvJ8iSV+Gwn0c3kvABZ691g7sp3hfEQHOUBJtccl1gPi+EyNjMIl9nGA0ug==",
"dev": true,
"requires": {
"@types/prop-types": "*",
@@ -13065,12 +13065,6 @@
"integrity": "sha512-hSAifV3k+i6lEoCJ2k6R2Z/rp/H3+8sdmcn5NrS3/3kE7+RyZXm9aqvxWqjEXHAd8b0pShatpcdMTvEdvAJltQ==",
"dev": true
},
"uifx": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/uifx/-/uifx-2.0.7.tgz",
"integrity": "sha512-tnPwdYe1dDmsxWJeU84CjDN/rcWOzOcG6tL1bsi5bUXw5nJaq+c4ThsbShMkedX2dAQ5gq1Q5CxQGsxwa5wxfw==",
"dev": true
},
"unicode-canonical-property-names-ecmascript": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz",
+4 -7
View File
@@ -1,6 +1,6 @@
{
"name": "wow-best-places",
"version": "0.1.0",
"version": "1.0.0",
"private": true,
"dependencies": {
"react": "^16.13.1",
@@ -8,18 +8,15 @@
"react-scripts": "3.4.1"
},
"devDependencies": {
"@types/react": "^16.9.38",
"@types/react": "^16.9.41",
"@types/react-dom": "^16.9.8",
"autoprefixer": "^9.8.2",
"node-sass": "^4.14.1",
"tslint": "^6.1.2",
"typescript": "^3.9.5",
"uifx": "^2.0.7"
"typescript": "^3.9.5"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"build": "env GENERATE_SOURCEMAP=false react-scripts build",
"eject": "react-scripts eject"
},
"eslintConfig": {
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

+9
View File
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/mstile-150x150.png"/>
<TileColor>#ffffff</TileColor>
</tile>
</msapplication>
</browserconfig>
Binary file not shown.

After

Width:  |  Height:  |  Size: 734 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

+10 -1
View File
@@ -3,7 +3,16 @@
<head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1, user-scalable=no" name="viewport"/>
<title>React App</title>
<link href="apple-touch-icon.png" rel="apple-touch-icon" sizes="180x180">
<link href="favicon-32x32.png" rel="icon" sizes="32x32" type="image/png">
<link href="favicon-16x16.png" rel="icon" sizes="16x16" type="image/png">
<link href="site.webmanifest" rel="manifest">
<link color="#000000" href="safari-pinned-tab.svg" rel="mask-icon">
<meta content="WoW Best Places" name="apple-mobile-web-app-title">
<meta content="WoW Best Places" name="application-name">
<meta content="#ffffff" name="msapplication-TileColor">
<meta content="#ffffff" name="theme-color">
<title>WoW Best Places</title>
<style>
@font-face {
font-family: 'AvantGarde LT';
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

+47
View File
@@ -0,0 +1,47 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="384.000000pt" height="384.000000pt" viewBox="0 0 384.000000 384.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.11, written by Peter Selinger 2001-2013
</metadata>
<g transform="translate(0.000000,384.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M1671 3824 c-255 -35 -533 -132 -744 -262 l-67 -42 -270 0 -270 0 0
-270 0 -271 -35 -55 c-84 -131 -211 -434 -228 -543 -3 -16 -7 -34 -10 -38 -3
-5 -8 -27 -11 -49 -4 -22 -9 -51 -11 -64 -21 -103 -27 -410 -12 -540 30 -250
133 -547 265 -763 l42 -67 0 -270 0 -270 270 0 270 0 65 -40 c228 -140 526
-243 775 -268 85 -8 368 -8 445 1 253 28 539 127 761 262 l75 45 269 0 270 0
0 274 c0 252 1 275 19 297 29 38 108 188 152 290 44 103 100 288 115 379 2 14
6 35 9 46 19 86 27 442 12 544 -45 302 -127 536 -266 763 l-41 67 0 270 0 270
-270 0 -270 0 -65 40 c-219 134 -500 234 -747 265 -101 13 -396 12 -497 -1z
m474 -319 c17 -3 66 -13 110 -21 92 -19 248 -71 344 -116 77 -36 214 -118 219
-130 2 -4 9 -8 17 -8 7 0 18 -7 25 -15 10 -12 43 -15 176 -15 l164 0 0 -160 0
-160 48 -72 c72 -108 110 -180 157 -297 33 -85 82 -247 77 -258 0 -2 4 -17 9
-35 30 -103 33 -421 5 -568 -44 -235 -121 -427 -248 -618 l-48 -72 0 -160 0
-160 -162 0 c-154 0 -163 -1 -192 -23 -71 -54 -227 -140 -331 -183 -50 -20
-187 -62 -240 -74 -22 -4 -51 -11 -65 -14 -84 -20 -363 -28 -480 -14 -234 28
-483 119 -682 250 l-89 58 -159 0 -160 0 0 160 0 160 -51 75 c-133 198 -219
426 -257 679 -13 94 -5 457 12 485 2 3 7 23 10 43 26 162 124 398 237 566 l49
72 0 160 0 160 160 0 161 0 72 49 c156 105 360 194 532 231 22 4 51 11 65 14
14 4 52 9 85 12 33 3 62 8 64 10 6 5 333 -4 366 -11z"/>
<path d="M810 2714 c0 -3 26 -31 58 -62 31 -32 65 -67 75 -78 14 -17 100 -363
131 -529 2 -11 11 -50 20 -86 8 -37 18 -79 21 -93 10 -52 15 -73 40 -186 15
-63 35 -153 45 -200 10 -47 29 -128 41 -180 35 -157 29 -186 -55 -273 -31 -31
-56 -59 -56 -62 0 -3 142 -5 315 -5 l315 0 -37 74 -37 74 115 321 c63 177 116
321 119 321 3 0 31 -76 64 -168 32 -92 63 -178 69 -192 5 -14 11 -29 13 -35 1
-5 21 -63 45 -129 l42 -118 -36 -74 -37 -74 315 0 c173 0 315 2 315 5 0 3 -27
33 -60 67 -47 47 -62 71 -67 102 -10 53 -10 49 27 211 17 77 33 149 36 160 10
51 107 489 124 560 10 44 37 166 61 270 23 105 45 200 49 211 5 12 43 56 84
98 l76 76 -320 0 -319 0 34 -72 c19 -40 35 -85 35 -100 0 -25 -176 -748 -184
-756 -2 -1 -25 53 -50 120 -26 68 -52 132 -57 143 -5 11 -54 137 -109 280 -56
143 -107 274 -115 290 -7 17 -17 42 -20 58 -7 26 -25 38 -25 16 0 -6 -6 -25
-14 -42 -8 -18 -28 -70 -46 -117 -17 -47 -36 -94 -41 -105 -5 -11 -18 -45 -29
-75 -11 -30 -25 -64 -29 -75 -5 -11 -17 -42 -27 -70 -10 -27 -24 -61 -31 -75
-7 -14 -13 -32 -13 -41 0 -8 -4 -19 -8 -25 -5 -5 -25 -54 -46 -109 -21 -55
-46 -118 -55 -139 l-18 -39 -91 366 c-51 202 -92 379 -92 395 0 15 16 60 35
100 l34 72 -314 0 c-173 0 -315 -2 -315 -6z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

+19
View File
@@ -0,0 +1,19 @@
{
"name": "WoW Best Places",
"short_name": "WoW Best Places",
"icons": [
{
"src": "/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/android-chrome-384x384.png",
"sizes": "384x384",
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}
+72 -23
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
+1 -1
View File
@@ -9,7 +9,7 @@
"new-parens": true,
"no-arg": true,
"semicolon": false,
"no-bitwise": true,
"no-bitwise": false,
"no-conditional-assignment": true,
"no-consecutive-blank-lines": true,
"no-console": {