Word count picker added, Share/contact links added

This commit is contained in:
Gene Mecija 2020-02-12 17:48:34 -08:00
parent 1117989502
commit 70b0c4b3ee
16 changed files with 394 additions and 54 deletions

View file

@ -1,11 +1,13 @@
import React, { useContext } from "react" import React, { useContext } from "react"
import { GameClockContext } from "../contexts/gameClockContext" import { GameClockContext } from "../contexts/gameClockContext"
import { ChallengeContext } from "../contexts/challengeContext" import { ChallengeContext } from "../contexts/challengeContext"
import { WordListPickerContext } from "../contexts/wordListPickerContext"
export default (function ChallengeComplete(props) { export default (function ChallengeComplete(props) {
const {gameClockTime} = useContext(GameClockContext) const {gameClockTime} = useContext(GameClockContext)
const {setChallengeState} = useContext(ChallengeContext) const {setChallengeState} = useContext(ChallengeContext)
const {wordListCount, wordListCategory, metadata} = useContext(WordListPickerContext)
function _continue() { function _continue() {
setChallengeState('ready') setChallengeState('ready')
@ -19,7 +21,9 @@ export default (function ChallengeComplete(props) {
return ( return (
<div id="challengeComplete" className="notify"> <div id="challengeComplete" className="notify">
<span id="notify-title">Challenge Complete</span> <span id="notify-title">Challenge Complete</span>
<span id="message">Challenge completed in {time}!</span> <span id="message">You completed <b>{wordListCount}</b> words<br />
from the <b>{metadata[wordListCategory]['name']}</b> word list<br />
in <b>{time}</b>!</span>
<button id="continue" onClick={_continue}>Continue</button> <button id="continue" onClick={_continue}>Continue</button>
</div> </div>

View file

@ -2,7 +2,33 @@ import React from "react"
export default (function Footer() { export default (function Footer() {
const contactLinks = {
'email': {
name: 'Email',
icon: "ri-mail-line",
link: 'mailto:gene@genemecija.com?subject='+encodeURIComponent('Hello, Gene!')
},
'github': {
name: 'GitHub',
icon: 'ri-github-fill',
link: 'https://github.com/genemecija'
},
'twitter': {
name: 'Twitter',
icon: 'ri-twitter-fill',
link:'https://twitter.com/genemecija'
}
}
function handleClick(event) {
window.open(contactLinks[event.target.id]['link'])
}
return ( return (
<div id="footer">Created by Gene Mecija &nbsp;<a href='https://github.com/genemecija/learn-morse-code'>View on Github</a>&nbsp;•&nbsp;<a href='https://twitter.com/genemecija'>Twitter</a></div> <div id="footer">
App by Gene Mecija &nbsp;
Code:&nbsp;<span id="contact-icons"><i id="github" onClick={handleClick} className={contactLinks['github']['icon']}></i></span>&nbsp;
Say Hi!&nbsp;<span id="contact-icons"><i id="twitter" onClick={handleClick} className={contactLinks['twitter']['icon']}></i>&nbsp;<i id="email" onClick={handleClick} className={contactLinks['email']['icon']}></i></span>
</div>
) )
}) })

View file

@ -2,9 +2,73 @@ import React from "react"
export default (function Header () { export default (function Header () {
const shareLinks = {
'twitter': {
name: 'Twitter',
icon: 'ri-twitter-fill',
link:'https://twitter.com/intent/tweet?text=Check%20out%20this%20site%20that%20helps%20you%20learn%20Morse%20Code%3A%20https%3A//learnmorsecode.com%20%40genemecija%20%23morse%20%23morsecode'
},
'facebook': {
name: 'Facebook',
icon: 'ri-facebook-box-fill',
link:'https://www.facebook.com/sharer/sharer.php?u=https%3A//learnmorsecode.com'
},
'email': {
name: 'Email',
icon: "ri-mail-line",
link: 'mailto:?subject='+encodeURIComponent('Check out this site that helps you learn Morse code! https://learnmorsecode.com')
}
}
function PopupCenter(url, title, w, h) {
// Credit: http://www.xtf.dk/2011/08/center-new-popup-window-even-on.html
// Fixes dual-screen position Most browsers Firefox
const dualScreenLeft = window.screenLeft !== undefined ? window.screenLeft : window.screen.left;
const dualScreenTop = window.screenTop !== undefined ? window.screenTop : window.screen.top;
const width = window.innerWidth ? window.innerWidth : document.documentElement.clientWidth ? document.documentElement.clientWidth : window.screen.width;
let height = window.innerHeight ? window.innerHeight : document.documentElement.clientHeight ? document.documentElement.clientHeight : window.screen.height;
const left = ((width / 2) - (w / 2)) + dualScreenLeft;
const top = ((height / 2) - (h / 2)) + dualScreenTop;
const newWindow = window.open(url, title, 'scrollbars=yes, width=' + w + ', height=' + h + ', top=' + top + ', left=' + left);
// Puts focus on the newWindow
if (window.focus) {
newWindow.focus();
}
}
function handleClick(event) {
let link = event.target.id
let url = shareLinks[link]['link']
let title = 'Share'
let width = '900'
let height = '500'
if (link === 'email') {
width = '150'
height = '150'
}
PopupCenter(url, title, width, height)
}
let contacts = Object.keys(shareLinks).map((contact, index) => {
return (
<i id={contact} key={index} onClick={handleClick} className={shareLinks[contact]['icon']}></i>
)
})
return ( return (
<div id="header"> <div id="header">
<div id="title">
Learn Morse Code Learn Morse Code
</div> </div>
<div id="social-links">
Share: <span id="share-icons">{contacts}</span>
</div>
</div>
) )
}) })

View file

@ -1,7 +1,7 @@
import React from "react" import React from "react"
import useMorsePlayer from "../hooks/useMorsePlayer" import useMorsePlayer from "../hooks/useMorsePlayer"
import straight_key from "../images/straight_key.jpg" import straight_key from "../media/images/straight_key.jpg"
import electronic_key from "../images/electronic_key.jpg" import electronic_key from "../media/images/electronic_key.jpg"
export default React.memo(function Info() { export default React.memo(function Info() {

View file

@ -0,0 +1,33 @@
import React, {useContext} from "react"
import { WordListPickerContext } from "../contexts/wordListPickerContext";
import { WordFeederContext } from "../contexts/wordFeederContext";
export default React.memo(function WordCountPicker(props) {
const {setWordListCount, wordListCountMax} = useContext(WordListPickerContext)
const {resetFeeder} = useContext(WordFeederContext)
function handleChange(e) {
resetFeeder()
setWordListCount(e.target.value)
}
// Create Options for Select Input
let options = []
for (let i = 0; i < wordListCountMax; i++) {
options.push(<option value={i+1} key={i}>{i+1}</option>)
}
return (
<div id='word-count' className='mode-picker'>
<div id='title'>
Challenge Word Count: <span id="range">(1-{wordListCountMax})</span>
</div>
<div id="input">
<select id="wordCount-picker" defaultValue={wordListCountMax} onChange={handleChange}>
{options}
</select>
</div>
</div>
)
})

View file

@ -1,10 +1,11 @@
import React, {useContext} from "react" import React, {useContext} from "react"
import { WordListPickerContext } from "../contexts/wordListPickerContext"; import { WordListPickerContext } from "../contexts/wordListPickerContext";
import { WordFeederContext } from "../contexts/wordFeederContext"; import { WordFeederContext } from "../contexts/wordFeederContext";
import WordCountPicker from "./WordCountPicker";
export default React.memo(function WordListPicker() { export default React.memo(function WordListPicker() {
const {wordListCategory, setWordListCategory} = useContext(WordListPickerContext) const {wordListCategory, setWordListCategory, metadata} = useContext(WordListPickerContext)
const {resetFeeder, setOrder} = useContext(WordFeederContext) const {resetFeeder, setOrder} = useContext(WordFeederContext)
const orderOpts = ['sequential', 'random'] const orderOpts = ['sequential', 'random']
@ -26,17 +27,8 @@ export default React.memo(function WordListPicker() {
} }
} }
let wordLists = ['alphabet', 'numbers', 'boys', 'girls', 'startrek', 'common100', 'test', 'short'] let wordLists = Object.keys(metadata)
const metadata = {
'alphabet': {name: 'Alphabet', description: 'Each letter of the alphabet', count: 26},
'numbers': {name: 'Numbers', description: '0-9', count: 10},
'boys': {name: 'Boys Names', description: 'Top 20 Boys Names', count: 20},
'girls': {name: 'Girls Names', description: 'Top 20 Girls Names', count: 20},
'startrek': {name: 'Star Trek', description: 'Word list from the Star Trek universe', count: 20},
'common100': {name: 'Common 100', description: '100 most common words', count: 100},
'test': {name: 'Test List', description: 'A test list', count: 5},
'short': {name: 'Short List', description: 'A short list', count: 1}
}
let options = wordLists.map((wl, index) => (<option value={wl} key={index}>{metadata[wl]['name']}</option>)) let options = wordLists.map((wl, index) => (<option value={wl} key={index}>{metadata[wl]['name']}</option>))
return ( return (
@ -66,6 +58,8 @@ export default React.memo(function WordListPicker() {
</div> </div>
</div> </div>
<WordCountPicker />
<div id="wordlist-description" className="mode-picker"> <div id="wordlist-description" className="mode-picker">
<div id="title"> <div id="title">
Description: Description:
@ -74,14 +68,14 @@ export default React.memo(function WordListPicker() {
{metadata[wordListCategory]['description']} {metadata[wordListCategory]['description']}
</div> </div>
</div> </div>
<div id="wordlist-count" className="mode-picker"> {/* <div id="wordlist-count" className="mode-picker">
<div id="title"> <div id="title">
# of List Items: # of List Items:
</div> </div>
<div id="info"> <div id="info">
{metadata[wordListCategory]['count']} {metadata[wordListCategory]['count']}
</div> </div>
</div> </div> */}
</div> </div>
) )
}) })

View file

@ -12,10 +12,13 @@ const WordListPickerContext = React.createContext()
function WordListPickerContextProvider(props) { function WordListPickerContextProvider(props) {
const [wordListCategory, setWordListCategory] = useState('alphabet') const [wordListCategory, setWordListCategory] = useState('alphabet')
const [wordListCount, setWordListCount] = useState(10)
let wordList = [] let wordList = []
const testList = ['gene', 'anya', 'ali', 'liam', 'last'] const testList = ['gene', 'anya', 'ali', 'liam', 'last']
const short = ['gene'] const short = ['gene']
if (wordListCategory === 'alphabet') { if (wordListCategory === 'alphabet') {
wordList = alphabet.words wordList = alphabet.words
} else if (wordListCategory === 'numbers') { } else if (wordListCategory === 'numbers') {
@ -34,6 +37,19 @@ function WordListPickerContextProvider(props) {
wordList = short wordList = short
} }
const wordListCountMax = wordList.length
const metadata = {
'alphabet': {name: 'Alphabet', description: 'All letters of the alphabet'},
'numbers': {name: 'Numbers', description: '0-9'},
'boys': {name: 'Boys Names', description: 'Popular Boys Names'},
'girls': {name: 'Girls Names', description: 'Popular Girls Names'},
'startrek': {name: 'Star Trek', description: 'Star Trek universe'},
'common100': {name: 'Common Words', description: '100 Most Common Words'},
'test': {name: 'Test List', description: 'A test list'},
'short': {name: 'Short List', description: 'A short list'}
}
function randomize(arr) { function randomize(arr) {
let array = [...arr] let array = [...arr]
@ -56,7 +72,16 @@ function WordListPickerContextProvider(props) {
} }
return ( return (
<WordListPickerContext.Provider value={{wordList: wordList, wordListShuffled: randomize(wordList), wordListCategory: wordListCategory, setWordListCategory: setWordListCategory}}> <WordListPickerContext.Provider value={{
wordList: wordList.slice(0,wordListCount),
wordListShuffled: randomize(wordList).slice(0,wordListCount),
wordListCategory: wordListCategory,
setWordListCategory: setWordListCategory,
metadata: metadata,
wordListCount: wordListCount,
setWordListCount: setWordListCount,
wordListCountMax: wordListCountMax
}}>
{props.children} {props.children}
</WordListPickerContext.Provider> </WordListPickerContext.Provider>
) )

View file

@ -48,15 +48,76 @@ html, body {
-webkit-box-align: center; -webkit-box-align: center;
-ms-flex-align: center; -ms-flex-align: center;
align-items: center; align-items: center;
-webkit-box-pack: justify;
-ms-flex-pack: justify;
justify-content: space-between;
padding-left: 15px; padding-left: 15px;
background: #333; background: #333;
font-family: "Roboto", sans-serif; font-family: "Roboto", sans-serif;
font-size: 2.5em;
color: #dab820;
color: #eee; color: #eee;
z-index: 1000; z-index: 1000;
-webkit-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.45); -webkit-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.45);
box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.45); box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.45);
font-size: 2.5em;
}
#header #title {
font-weight: bold;
text-transform: uppercase;
}
#header #social-links {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: space-evenly;
-ms-flex-pack: space-evenly;
justify-content: space-evenly;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
font-size: 1rem;
font-weight: bold;
text-transform: uppercase;
color: #999;
margin-right: 10px;
}
#header #social-links i {
color: #ccc;
padding-left: 5px;
padding-right: 5px;
font-size: 2rem;
}
#header #social-links i:hover {
color: goldenrod;
}
#header #social-links div {
height: auto;
}
#header #social-links div img {
width: 40px;
height: 40px;
opacity: 30%;
}
#header #social-links div img:hover {
-webkit-animation-name: socialLinkHover;
animation-name: socialLinkHover;
-webkit-animation-duration: 150ms;
animation-duration: 150ms;
-webkit-animation-fill-mode: forwards;
animation-fill-mode: forwards;
-webkit-animation-timing-function: ease-in-out;
animation-timing-function: ease-in-out;
}
#header #social-links div#twitter img {
-webkit-filter: invert(90%);
filter: invert(90%);
} }
#main-content { #main-content {
@ -471,6 +532,9 @@ html, body {
-webkit-box-pack: center; -webkit-box-pack: center;
-ms-flex-pack: center; -ms-flex-pack: center;
justify-content: center; justify-content: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
background: #333; background: #333;
font-family: "Roboto", sans-serif; font-family: "Roboto", sans-serif;
font-size: 1em; font-size: 1em;
@ -478,18 +542,13 @@ html, body {
z-index: 1000; z-index: 1000;
} }
#footer a { #footer i {
color: #eee; font-size: 1.3em;
text-decoration: none; color: #ccc;
} }
#footer a:visited { #footer i:hover {
color: #eee;
}
#footer a:hover {
color: gold; color: gold;
text-decoration: underline;
} }
h2 { h2 {
@ -1496,16 +1555,25 @@ i[class*="ri-"] {
} }
#header { #header {
width: 100vw; width: 100vw;
font-size: 1.5em; font-size: 1.3em;
height: 1.5em; height: 1.5em;
min-height: 1.5em; min-height: 1.5em;
max-height: 1.5em; max-height: 1.5em;
padding-left: 5px;
}
#header #social-links {
margin-right: 0px;
font-size: 1rem;
}
#header #social-links i {
font-size: 1.5rem;
padding-left: 0px;
} }
#root #main-content { #root #main-content {
height: calc(100vh - 4.1em); height: calc(100vh - 4.1em);
} }
#root #main-content .sidebar#left { #root #main-content .sidebar#left {
top: 2.2em; top: 2em;
width: 100vw; width: 100vw;
min-width: 40%; min-width: 40%;
max-width: 100vw; max-width: 100vw;

File diff suppressed because one or more lines are too long

View file

@ -21,6 +21,7 @@
"t", "t",
"u", "u",
"v", "v",
"w",
"x", "x",
"y", "y",
"z" "z"

View file

@ -17,6 +17,38 @@
"William", "William",
"Daniel", "Daniel",
"Jayden", "Jayden",
"Oliver" "Oliver",
"Carter",
"Sebastian",
"Joseph",
"David",
"Gabriel",
"Julian",
"Jackson",
"Anthony",
"Dylan",
"Wyatt",
"Grayson",
"Isaiah",
"Christopher",
"Joshua",
"Christian",
"Andrew",
"Samuel",
"Mateo",
"Jaxon",
"Josiah",
"John",
"Luke",
"Ryan",
"Nathan",
"Isaac",
"Owen",
"Henry",
"Levi",
"Aaron",
"Caleb",
"Jeremiah",
"Landon"
] ]
} }

View file

@ -17,8 +17,38 @@
"Sofia", "Sofia",
"Scarlett", "Scarlett",
"Aria", "Aria",
"Elizabeth" "Elizabeth",
"Camila",
"Layla",
"Ella",
"Chloe",
"Zoey",
"Penelope",
"Skylar",
"Grace",
"Mila",
"Lillian",
"Aaliyah",
"Lily",
"Paisley",
"Bella",
"Brooklyn",
"Savannah",
"Luna",
"Natalie",
"Ellie",
"Leah",
"Audrey",
"Ariana",
"Aurora",
"Zoe",
"Hannah",
"Violet",
"Samantha",
"Nora",
"Nevaeh",
"Serenity",
"Gabriella",
"Hailey"
] ]
} }

View file

@ -3,13 +3,16 @@
"dilithium", "dilithium",
"borg", "borg",
"replicator", "replicator",
"picard",
"vulcan", "vulcan",
"ensign", "ensign",
"phaser", "phaser",
"kirk",
"warbird", "warbird",
"ferengi", "ferengi",
"hypospray", "hypospray",
"tribble", "tribble",
"sisko",
"starfleet", "starfleet",
"engage", "engage",
"holodeck", "holodeck",
@ -18,7 +21,12 @@
"romulan", "romulan",
"quadrant", "quadrant",
"tricorder", "tricorder",
"georgiou",
"futile", "futile",
"klingon" "klingon",
"janeway",
"delta",
"assimilate",
"energize"
] ]
} }

View file

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

View file

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

View file

@ -51,14 +51,61 @@ html, body {
width: 100%; width: 100%;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between;
padding-left: 15px; padding-left: 15px;
background: #333; background: #333;
font-family: $main-font; font-family: $main-font;
font-size: 2.5em;
color: rgb(218, 184, 32);
color: $main-bg-color-light; color: $main-bg-color-light;
z-index: 1000; z-index: 1000;
box-shadow: 0px 2px 2px rgba(0,0,0,0.45); box-shadow: 0px 2px 2px rgba(0,0,0,0.45);
font-size: 2.5em;
#title {
font-weight: bold;
text-transform: uppercase;
}
#social-links {
display: flex;
justify-content: space-evenly;
align-items: center;
font-size: 1rem;
font-weight: bold;
text-transform: uppercase;
color: #999;
margin-right: 10px;
// border: 1px solid red;
i {
color: #ccc;
padding-left: 5px;
padding-right: 5px;
font-size: 2rem;
&:hover {
color: goldenrod;
}
}
// border: 1px solid orange;
div {
// border: 1px solid red;
height: auto;
img {
width: 40px;
height: 40px;
opacity: 30%;
// filter: contrast(100%)
&:hover {
animation-name: socialLinkHover;
animation-duration: 150ms;
animation-fill-mode: forwards;
animation-timing-function: ease-in-out;
}
}
&#twitter img {
filter: invert(90%)
}
}
}
} }
#main-content { #main-content {
// border: 1px solid red; // border: 1px solid red;
@ -385,22 +432,20 @@ html, body {
padding: 0.3em; padding: 0.3em;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center;
background: #333; background: #333;
font-family: $main-font; font-family: $main-font;
font-size: 1em; font-size: 1em;
// font-weight: bold; // font-weight: bold;
color: $main-bg-color-light; color: $main-bg-color-light;
z-index: 1000; z-index: 1000;
a {
color: $main-bg-color-light; i {
text-decoration: none; font-size: 1.3em;
} color: #ccc;
a:visited { &:hover {
color: $main-bg-color-light;
}
a:hover {
color: gold; color: gold;
text-decoration: underline; }
} }
} }
h2 { h2 {
@ -1246,17 +1291,27 @@ $morse-button-color: rgba(112, 138, 144,0.5);
} }
#header { #header {
width: 100vw; width: 100vw;
font-size: 1.5em; font-size: 1.3em;
height: 1.5em; height: 1.5em;
min-height: 1.5em; min-height: 1.5em;
max-height: 1.5em; max-height: 1.5em;
padding-left: 5px;
#social-links {
margin-right: 0px;
font-size: 1rem;
i {
font-size: 1.5rem;
padding-left: 0px;
}
}
} }
#root { #root {
#main-content { #main-content {
height: calc(100vh - 4.1em); height: calc(100vh - 4.1em);
.sidebar#left { .sidebar#left {
top: 2.2em; top: 2em;
width: 100vw; width: 100vw;
min-width: 40%; min-width: 40%;
max-width: 100vw; max-width: 100vw;