Add background music; add favicon; add sound module
@@ -1,2 +1,4 @@
|
||||
node_modules
|
||||
package-lock.json
|
||||
build
|
||||
.idea
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
@@ -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>
|
||||
@@ -1,5 +0,0 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||
</state>
|
||||
</component>
|
||||
@@ -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>
|
||||
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="JavaScriptSettings">
|
||||
<option name="languageLevel" value="ES6" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectTasksOptions" suppressed-tasks="SCSS" />
|
||||
</project>
|
||||
@@ -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>
|
||||
@@ -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",
|
||||
|
||||
@@ -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": {
|
||||
|
||||
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
@@ -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>
|
||||
|
After Width: | Height: | Size: 734 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 10 KiB |
@@ -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';
|
||||
|
||||
|
After Width: | Height: | Size: 4.5 KiB |
@@ -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 |
@@ -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"
|
||||
}
|
||||
@@ -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}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -5,5 +5,6 @@
|
||||
"ui.main-menu": "Главное меню",
|
||||
"ui.uiSound": "Звуки интерфейса",
|
||||
"ui.uiLanguage": "Язык",
|
||||
"ui.language": "Русский"
|
||||
"ui.language": "Русский",
|
||||
"place.stormwind-park": "Парк Штормграда"
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
/// <reference types="react-scripts" />
|
||||
declare module '*.ogg'
|
||||
declare module '*.mp3'
|
||||
@@ -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
|
||||
|
||||
@@ -3,6 +3,7 @@ import { LOADING_DURATION } from './constants'
|
||||
export {
|
||||
PREVIEW_WIDTH,
|
||||
PREVIEW_HEIGHT,
|
||||
UI_MUSIC_VOLUME,
|
||||
UI_SOUND_VOLUME,
|
||||
LOADING_DURATION,
|
||||
ANIMATION_DURATION
|
||||
|
||||
@@ -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": {
|
||||
|
||||