Finished V1.
This commit is contained in:
parent
f795511daa
commit
ff3702bb67
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
*.sw?
|
196
flashcards.css
Normal file
196
flashcards.css
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
font-family: sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 1.5em;
|
||||||
|
margin: 1rem auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#settings {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#settings label {
|
||||||
|
display: block;
|
||||||
|
margin-top: .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#settings textarea {
|
||||||
|
flex-grow: 2;
|
||||||
|
font-size: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#settings input, #settings select {
|
||||||
|
font-size: 100%;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background-color: #FFF;
|
||||||
|
border: solid thin #000;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: inherit;
|
||||||
|
padding: .2em .5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background-color: #1CB841;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #FFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
#settings .visibility-indicator {
|
||||||
|
display: inline-block;
|
||||||
|
transition: all linear 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
#settings #adv-settings {
|
||||||
|
max-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: max-height linear 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
#settings.adv-settings-visible #adv-settings {
|
||||||
|
max-height: 40%;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
#settings.adv-settings-visible .visibility-indicator {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-adv-settings {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
#adv-settings > * {
|
||||||
|
margin: 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#adv-settings label {
|
||||||
|
display: flex;
|
||||||
|
padding: 0 .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#adv-settings label span,
|
||||||
|
#adv-settings label select,
|
||||||
|
#adv-settings label input {
|
||||||
|
flex-grow: 0;
|
||||||
|
min-width: 12em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#adv-settings button {
|
||||||
|
margin: 0 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[name=importFileName] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#cards {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-grow: 1;
|
||||||
|
justify-content: space-around;
|
||||||
|
}
|
||||||
|
|
||||||
|
#card-content { margin: auto; }
|
||||||
|
#card-content.small-text{ font-size: 100%; }
|
||||||
|
#card-content.medium-text{ font-size: 200%; }
|
||||||
|
#card-content.large-text{ font-size: 800%; }
|
||||||
|
|
||||||
|
/* Card View Visible */
|
||||||
|
#settings {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Settings Visible */
|
||||||
|
.settings-visible #settings {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-visible #cards {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile View */
|
||||||
|
@media ( max-width: 600px ) {
|
||||||
|
body {
|
||||||
|
font-size: 125%;
|
||||||
|
height: calc(100vh - 1em);
|
||||||
|
margin: 0.5em;
|
||||||
|
width: calc(100vw - 1em);
|
||||||
|
}
|
||||||
|
|
||||||
|
#settings {
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
#settings > button, #cards > button {
|
||||||
|
font-size: 125%;
|
||||||
|
margin: 1em auto;
|
||||||
|
padding: 0.5em 3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.medium-only, .large-only { display: none; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tablet View */
|
||||||
|
@media (max-width: 1279px) and (min-width: 601px) {
|
||||||
|
body {
|
||||||
|
font-size: 125%;
|
||||||
|
margin: 1em;
|
||||||
|
height: calc(100vh - 2em);
|
||||||
|
width: calc(100vw - 2em);
|
||||||
|
}
|
||||||
|
|
||||||
|
#settings > button, #cards > button {
|
||||||
|
font-size: 125%;
|
||||||
|
margin: 1em auto;
|
||||||
|
padding: 0.5em 3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.small, .small-only, .large-only { display: none; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Desktop View */
|
||||||
|
@media (min-width: 1280px) {
|
||||||
|
body {
|
||||||
|
font-size: 150%;
|
||||||
|
margin: 2em auto;
|
||||||
|
min-height: calc(100vh - 4em);
|
||||||
|
width: 32em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#settings button, #cards button {
|
||||||
|
align-self: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.small, .medium, .small-only, .medium-only { display: none; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#debug {
|
||||||
|
/* display: none; */
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
309
flashcards.html
309
flashcards.html
@ -3,268 +3,9 @@
|
|||||||
<head>
|
<head>
|
||||||
<title>Simple Flashcards</title>
|
<title>Simple Flashcards</title>
|
||||||
<meta charset=utf-8>
|
<meta charset=utf-8>
|
||||||
<style type="text/css">
|
|
||||||
* {
|
|
||||||
box-sizing: border-box;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
html {
|
<link rel=stylesheet href=flashcards.css type="text/css">
|
||||||
font-family: sans-serif;
|
<script src='flashcards.js'></script>
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
font-size: 1.5em;
|
|
||||||
margin: 1rem auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
#settings {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
flex-grow: 1;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#settings label {
|
|
||||||
display: block;
|
|
||||||
margin-top: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
#settings textarea {
|
|
||||||
flex-grow: 2;
|
|
||||||
font-size: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#settings input, #settings select {
|
|
||||||
font-size: 100%;
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
#adv-settings label {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
#adv-settings label span {
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
background-color: #FFF;
|
|
||||||
border: solid thin #000;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: inherit;
|
|
||||||
padding: .2em .5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary {
|
|
||||||
background-color: #1CB841;
|
|
||||||
border: 0;
|
|
||||||
border-radius: 4px;
|
|
||||||
color: #FFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
#settings .visibility-indicator {
|
|
||||||
display: inline-block;
|
|
||||||
transition: all linear 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
#settings #adv-settings {
|
|
||||||
max-height: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
transition: max-height linear 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
#settings.adv-settings-visible .visibility-indicator {
|
|
||||||
transform: rotate(90deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.toggle-adv-settings {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* Settings Visible */
|
|
||||||
#settings {
|
|
||||||
transition: opacity 0.2s linear;
|
|
||||||
z-index: -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.settings-visible #settings {
|
|
||||||
opacity: 1;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Card View Visible */
|
|
||||||
#cards {
|
|
||||||
opacity: 1;
|
|
||||||
transition: opacity 0.2s linear;
|
|
||||||
transition-delay: 0.2s;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.settings-visible #cards {
|
|
||||||
opacity: 0;
|
|
||||||
z-index: -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Mobile View */
|
|
||||||
@media ( max-width: 600px ) {
|
|
||||||
body {
|
|
||||||
font-size: 125%;
|
|
||||||
height: calc(100vh - 1em);
|
|
||||||
margin: 0.5em;
|
|
||||||
width: calc(100vw - 1em);
|
|
||||||
}
|
|
||||||
|
|
||||||
#settings {
|
|
||||||
align-items: stretch;
|
|
||||||
}
|
|
||||||
|
|
||||||
#settings > button {
|
|
||||||
font-size: 125%;
|
|
||||||
margin: 1em auto;
|
|
||||||
padding: 0.5em 3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#settings.adv-settings-visible #adv-settings {
|
|
||||||
max-height: 12em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Tablet View */
|
|
||||||
@media (max-width: 1279px) and (min-width: 601px) {
|
|
||||||
body {
|
|
||||||
font-size: 125%;
|
|
||||||
margin: 1em;
|
|
||||||
height: calc(100vh - 2em);
|
|
||||||
width: calc(100vw - 2em);
|
|
||||||
}
|
|
||||||
|
|
||||||
#settings > button {
|
|
||||||
font-size: 125%;
|
|
||||||
margin: 1em auto;
|
|
||||||
padding: 0.5em 3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#settings.adv-settings-visible #adv-settings {
|
|
||||||
max-height: 10em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Desktop View */
|
|
||||||
@media (min-width: 1280px) {
|
|
||||||
body {
|
|
||||||
font-size: 150%;
|
|
||||||
margin: 2em auto;
|
|
||||||
min-height: calc(100vh - 4em);
|
|
||||||
width: 32em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#settings button {
|
|
||||||
align-self: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
#settings.adv-settings-visible #adv-settings {
|
|
||||||
max-height: 8em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
const FC = {
|
|
||||||
items: [],
|
|
||||||
slidePeriod: 3,
|
|
||||||
nextCardIdx: 0,
|
|
||||||
cardOrder: []
|
|
||||||
};
|
|
||||||
|
|
||||||
// http://stackoverflow.com/questions/2450954/how-to-randomize-shuffle-a-javascript-array
|
|
||||||
// modified to be a pure function, not mutating it's input
|
|
||||||
FC.shuffle = function(inArray) {
|
|
||||||
var currentIndex = inArray.length
|
|
||||||
, temporaryValue
|
|
||||||
, randomIndex
|
|
||||||
;
|
|
||||||
|
|
||||||
var outArray = inArray.slice(0);
|
|
||||||
|
|
||||||
// While there remain elements to shuffle...
|
|
||||||
while (0 !== currentIndex) {
|
|
||||||
|
|
||||||
// Pick a remaining element...
|
|
||||||
randomIndex = Math.floor(Math.random() * currentIndex);
|
|
||||||
currentIndex -= 1;
|
|
||||||
|
|
||||||
// And swap it with the current element.
|
|
||||||
temporaryValue = outArray[currentIndex];
|
|
||||||
outArray[currentIndex] = outArray[randomIndex];
|
|
||||||
outArray[randomIndex] = temporaryValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
return outArray;
|
|
||||||
}
|
|
||||||
|
|
||||||
FC.isRunning = function() {
|
|
||||||
return !FC.bodyEl.classList.contains('settings-visible');
|
|
||||||
}
|
|
||||||
|
|
||||||
FC.toggleAdvSettings = function() {
|
|
||||||
FC.settingsEl.classList.toggle('adv-settings-visible');
|
|
||||||
}
|
|
||||||
|
|
||||||
FC.startCards = function() {
|
|
||||||
FC.items = FC.itemsEl.value.split('\n');
|
|
||||||
FC.slidePeriod =
|
|
||||||
parseInt(FC.settingsEl.querySelector('input[name=slidePeriod]').value);
|
|
||||||
|
|
||||||
FC.nextCardIdx = 0;
|
|
||||||
|
|
||||||
const orderedIndices = [...Array(FC.items.length).keys()];
|
|
||||||
switch (document.querySelector('select[name=cardOrder]').value) {
|
|
||||||
default:
|
|
||||||
case 'in-order':
|
|
||||||
FC.cardOrder = orderedIndices;
|
|
||||||
break;
|
|
||||||
case 'reverse':
|
|
||||||
FC.cardOrder = orderedIndices.reverse();
|
|
||||||
break;
|
|
||||||
case 'random':
|
|
||||||
FC.cardOrder = FC.shuffle(orderedIndices);
|
|
||||||
}
|
|
||||||
|
|
||||||
FC.showNextCard();
|
|
||||||
FC.runningInterval = setInterval(FC.showNextCard, FC.slidePeriod * 1000);
|
|
||||||
FC.bodyEl.classList.remove('settings-visible');
|
|
||||||
}
|
|
||||||
|
|
||||||
FC.stopCards = function() {
|
|
||||||
clearInterval(FC.runningInterval);
|
|
||||||
FC.bodyEl.classList.add('settings-visible');
|
|
||||||
}
|
|
||||||
|
|
||||||
FC.showNextCard = function() {
|
|
||||||
const curItem = FC.items[FC.cardOrder[FC.nextCardIdx]];
|
|
||||||
FC.cardContentEl.innerHTML = curItem;
|
|
||||||
FC.nextCardIdx = (FC.nextCardIdx + 1) % FC.items.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
window.onload = function() {
|
|
||||||
FC.settingsEl = document.querySelector('#settings');
|
|
||||||
FC.itemsEl = document.querySelector('#items-input');
|
|
||||||
FC.bodyEl = document.querySelector('body');
|
|
||||||
FC.cardsEl = document.querySelector('#cards');
|
|
||||||
FC.cardContentEl = document.querySelector('#card-content');
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body class=settings-visible>
|
<body class=settings-visible>
|
||||||
@ -272,7 +13,9 @@ button {
|
|||||||
<form id=settings action=#>
|
<form id=settings action=#>
|
||||||
<label>Items</label>
|
<label>Items</label>
|
||||||
<textarea id=items-input></textarea>
|
<textarea id=items-input></textarea>
|
||||||
<label class=toggle-adv-settings onclick=FC.toggleAdvSettings()>Advanced Settings <span class=visibility-indicator>➤</span></label>
|
<label class=toggle-adv-settings>Advanced Settings
|
||||||
|
<span class=visibility-indicator>➤</span>
|
||||||
|
</label>
|
||||||
<div id=adv-settings>
|
<div id=adv-settings>
|
||||||
<label>
|
<label>
|
||||||
<span>Seconds per slide:</span>
|
<span>Seconds per slide:</span>
|
||||||
@ -285,24 +28,44 @@ button {
|
|||||||
<option value=reverse>in reverse order.</option>
|
<option value=reverse>in reverse order.</option>
|
||||||
<option value=random>in random order.</option>
|
<option value=random>in random order.</option>
|
||||||
</select>
|
</select>
|
||||||
<label>
|
|
||||||
<span>Saved sets:</span>
|
|
||||||
<select name=loadSetName disabled>
|
|
||||||
<option>choose a set to load</option>
|
|
||||||
</select>
|
|
||||||
<button disabled>Load</button>
|
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
<span>Save this set as:</span>
|
<span>Text size: </span>
|
||||||
<input type=text name=saveSetname disabled>
|
<select name=textSize>
|
||||||
<button disabled>Save</button>
|
<option>small</option>
|
||||||
|
<option>medium</option>
|
||||||
|
<option>large</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<span>Saved card sets:</span>
|
||||||
|
<select name=loadSetName>
|
||||||
|
<option value=_NULL_>choose a set to load</option>
|
||||||
|
</select>
|
||||||
|
<button id=load-set>Load</button>
|
||||||
|
<button id=delete-set>Delete</button>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<span>Save this card set as:</span>
|
||||||
|
<input type=text name=saveSetName>
|
||||||
|
<button id=save-set>Save</button>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<button id=export>Export Cards</button>
|
||||||
|
<button id=import>Import Cards</button>
|
||||||
|
<input name=importFileName type=file accept='.json'>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<button class=btn-primary onclick=FC.startCards()>Go!</button>
|
<button class=btn-primary id=start-button>Go!</button>
|
||||||
</form>
|
</form>
|
||||||
<div id=cards>
|
<div id=cards>
|
||||||
<div id=card-content>
|
<div id=card-content></div>
|
||||||
|
<button id=stop-button>Stop</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div id=debug>
|
||||||
|
<span class=small-only>small</span>
|
||||||
|
<span class=medium-only>medium</span>
|
||||||
|
<span class=large-only>large</span>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
242
flashcards.js
Normal file
242
flashcards.js
Normal file
@ -0,0 +1,242 @@
|
|||||||
|
(function() {
|
||||||
|
const FC = {
|
||||||
|
currentSet: [],
|
||||||
|
items: [],
|
||||||
|
nextCardIdx: 0,
|
||||||
|
cardOrder: [], // elements are objects: { name: 'abc', cards: 'xyz' }
|
||||||
|
savedSets: [],
|
||||||
|
$: document.querySelector.bind(document)
|
||||||
|
};
|
||||||
|
|
||||||
|
FC.shuffle = function(inArray) {
|
||||||
|
/* http://stackoverflow.com/questions/2450954/how-to-randomize-shuffle-a-javascript-array
|
||||||
|
* modified to be a pure function, not mutating it's input */
|
||||||
|
var currentIndex = inArray.length
|
||||||
|
, temporaryValue
|
||||||
|
, randomIndex
|
||||||
|
;
|
||||||
|
|
||||||
|
var outArray = inArray.slice(0);
|
||||||
|
|
||||||
|
// While there remain elements to shuffle...
|
||||||
|
while (0 !== currentIndex) {
|
||||||
|
|
||||||
|
// Pick a remaining element...
|
||||||
|
randomIndex = Math.floor(Math.random() * currentIndex);
|
||||||
|
currentIndex -= 1;
|
||||||
|
|
||||||
|
// And swap it with the current element.
|
||||||
|
temporaryValue = outArray[currentIndex];
|
||||||
|
outArray[currentIndex] = outArray[randomIndex];
|
||||||
|
outArray[randomIndex] = temporaryValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return outArray;
|
||||||
|
};
|
||||||
|
|
||||||
|
FC.isRunning = function() {
|
||||||
|
return !FC.bodyEl.classList.contains('settings-visible');
|
||||||
|
};
|
||||||
|
|
||||||
|
FC.toggleAdvSettings = function(ev) {
|
||||||
|
FC.settingsEl.classList.toggle('adv-settings-visible');
|
||||||
|
};
|
||||||
|
|
||||||
|
FC.makeSetFromSettings = function(ev) {
|
||||||
|
if (ev && ev.preventDefault) ev.preventDefault();
|
||||||
|
|
||||||
|
const newSet = {
|
||||||
|
name: (FC.currentSet || {}).name ||
|
||||||
|
FC.$('input[name=saveSetName]').value ||
|
||||||
|
'new set',
|
||||||
|
cards: FC.itemsEl.value,
|
||||||
|
slidePeriod: parseInt(FC.$('input[name=slidePeriod]').value) || 3,
|
||||||
|
textSize: FC.$('select[name=textSize]').value || 'small',
|
||||||
|
sortOrder: FC.$('select[name=cardOrder]').value || 'in-order'
|
||||||
|
};
|
||||||
|
|
||||||
|
return newSet;
|
||||||
|
};
|
||||||
|
|
||||||
|
FC.populateSettingsFromSet = function(set) {
|
||||||
|
FC.$('input[name=saveSetName]').value = set.name;
|
||||||
|
FC.itemsEl.value = set.cards;
|
||||||
|
FC.$('input[name=slidePeriod]').value = set.slidePeriod;
|
||||||
|
FC.$('select[name=cardOrder]').value = set.sortOrder;
|
||||||
|
FC.$('select[name=textSize]').value = set.textSize;
|
||||||
|
};
|
||||||
|
|
||||||
|
FC.startCards = function(ev) {
|
||||||
|
/* Handler for the "Go!" button that starts the flashcard display. */
|
||||||
|
|
||||||
|
ev.preventDefault();
|
||||||
|
|
||||||
|
FC.currentSet = FC.makeSetFromSettings();
|
||||||
|
FC.items = FC.currentSet.cards.split('\n');
|
||||||
|
|
||||||
|
FC.cardContentEl.classList.remove(
|
||||||
|
'small-text', 'medium-text', 'large-text');
|
||||||
|
FC.cardContentEl.classList.add(FC.currentSet.textSize + '-text');
|
||||||
|
|
||||||
|
FC.nextCardIdx = 0;
|
||||||
|
|
||||||
|
const orderedIndices = [...Array(FC.items.length).keys()];
|
||||||
|
switch (FC.currentSet.sortOrder) {
|
||||||
|
default:
|
||||||
|
case 'in-order':
|
||||||
|
FC.cardOrder = orderedIndices;
|
||||||
|
break;
|
||||||
|
case 'reverse':
|
||||||
|
FC.cardOrder = orderedIndices.reverse();
|
||||||
|
break;
|
||||||
|
case 'random':
|
||||||
|
FC.cardOrder = FC.shuffle(orderedIndices);
|
||||||
|
}
|
||||||
|
|
||||||
|
FC.showNextCard();
|
||||||
|
FC.runningInterval = setInterval(FC.showNextCard, FC.currentSet.slidePeriod * 1000);
|
||||||
|
FC.bodyEl.classList.remove('settings-visible');
|
||||||
|
};
|
||||||
|
|
||||||
|
FC.stopCards = function(ev) {
|
||||||
|
clearInterval(FC.runningInterval);
|
||||||
|
FC.bodyEl.classList.add('settings-visible');
|
||||||
|
};
|
||||||
|
|
||||||
|
FC.showNextCard = function() {
|
||||||
|
const curItem = FC.items[FC.cardOrder[FC.nextCardIdx]];
|
||||||
|
FC.cardContentEl.innerHTML = curItem;
|
||||||
|
FC.nextCardIdx = (FC.nextCardIdx + 1) % FC.items.length;
|
||||||
|
};
|
||||||
|
|
||||||
|
FC.handleLoadSet = function(ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
|
||||||
|
const setName = FC.$('select[name=loadSetName]').value;
|
||||||
|
if (setName === '_NULL_') {
|
||||||
|
alert("Can't load: no set selected.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const setToLoad = FC.savedSets.find(item => item.name === setName);
|
||||||
|
FC.populateSettingsFromSet(setToLoad);
|
||||||
|
};
|
||||||
|
|
||||||
|
FC.handleDeleteSet = function(ev) {
|
||||||
|
if (ev) ev.preventDefault();
|
||||||
|
|
||||||
|
const setName = FC.$('select[name=loadSetName]').value;
|
||||||
|
if (setName === '_NULL_') {
|
||||||
|
alert("Can't delete: no set selected.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FC.savedSets = FC.savedSets.filter(set => set.name !== setName);
|
||||||
|
FC.storeSets(FC.savedSets);
|
||||||
|
FC.updateSetList(FC.savedSets);
|
||||||
|
}
|
||||||
|
|
||||||
|
FC.handleSaveSet = function(ev) {
|
||||||
|
if (ev) ev.preventDefault();
|
||||||
|
|
||||||
|
const newSet = FC.makeSetFromSettings();
|
||||||
|
FC.saveSet(newSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
FC.saveSet = function(newSet) {
|
||||||
|
// Look for an existing set with this name
|
||||||
|
const existingIdx = FC.savedSets.findIndex(set => set.name === newSet.name);
|
||||||
|
|
||||||
|
if (existingIdx < 0) FC.savedSets.push(newSet);
|
||||||
|
else FC.savedSets[existingIdx] = newSet;
|
||||||
|
|
||||||
|
FC.storeSets(FC.savedSets);
|
||||||
|
FC.updateSetList(FC.savedSets);
|
||||||
|
};
|
||||||
|
|
||||||
|
FC.updateSetList = function(sets) {
|
||||||
|
const setSelectEl = FC.$('select[name=loadSetName]');
|
||||||
|
var newHtml = '<option value=_NULL_>choose a set to load</option>';
|
||||||
|
|
||||||
|
newHtml += sets.map(set => '<option>' + set.name + '</option>');
|
||||||
|
setSelectEl.innerHTML = newHtml;
|
||||||
|
}
|
||||||
|
|
||||||
|
FC.storeSets = function(cardSets) {
|
||||||
|
window.localStorage.setItem('cardSets', JSON.stringify(cardSets));
|
||||||
|
};
|
||||||
|
|
||||||
|
FC.retrieveSets = function() {
|
||||||
|
return JSON.parse(window.localStorage.getItem('cardSets') || "[]");
|
||||||
|
};
|
||||||
|
|
||||||
|
FC.exportCardSet = function(ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
|
||||||
|
const setToExport = FC.makeSetFromSettings();
|
||||||
|
const blob = new Blob(
|
||||||
|
[JSON.stringify(setToExport)],
|
||||||
|
{type: 'text/json;charset=utf-8', endings: 'native' });
|
||||||
|
|
||||||
|
const downloadLink = document.createElement('a');
|
||||||
|
downloadLink.href = window.URL.createObjectURL(blob);
|
||||||
|
downloadLink.download = setToExport.name + '.json';
|
||||||
|
downloadLink.style.display = 'none';
|
||||||
|
document.body.appendChild(downloadLink);
|
||||||
|
downloadLink.click();
|
||||||
|
document.body.removeChild(downloadLink);
|
||||||
|
};
|
||||||
|
|
||||||
|
FC.importCardSet = function(ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
const fileInput = FC.$('input[name=importFileName]');
|
||||||
|
fileInput.click();
|
||||||
|
};
|
||||||
|
|
||||||
|
FC.handleImport = function(ev) {
|
||||||
|
if (this.files.length === 0 || !this.files[0]) return;
|
||||||
|
|
||||||
|
const fileToImport = this.files[0];
|
||||||
|
console.log("Importing file: ", fileToImport);
|
||||||
|
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = function(readEv) {
|
||||||
|
const importedSet = JSON.parse(readEv.target.result);
|
||||||
|
FC.saveSet(importedSet);
|
||||||
|
FC.storeSets(FC.savedSets);
|
||||||
|
FC.updateSetList(FC.savedSets);
|
||||||
|
FC.populateSettingsFromSet(importedSet);
|
||||||
|
};
|
||||||
|
|
||||||
|
reader.readAsText(fileToImport);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
// Cached element references
|
||||||
|
FC.settingsEl = FC.$('#settings');
|
||||||
|
FC.itemsEl = FC.$('#items-input');
|
||||||
|
FC.bodyEl = FC.$('body');
|
||||||
|
FC.cardsEl = FC.$('#cards');
|
||||||
|
FC.cardContentEl = FC.$('#card-content');
|
||||||
|
|
||||||
|
// Event handlers
|
||||||
|
FC.$('.toggle-adv-settings').addEventListener('click', FC.toggleAdvSettings);
|
||||||
|
FC.$('#start-button').addEventListener('click', FC.startCards);
|
||||||
|
FC.$('#stop-button').addEventListener('click', FC.stopCards);
|
||||||
|
FC.$('#export').addEventListener('click', FC.exportCardSet);
|
||||||
|
FC.$('#import').addEventListener('click', FC.importCardSet);
|
||||||
|
FC.$('input[name=importFileName]')
|
||||||
|
.addEventListener('change', FC.handleImport);
|
||||||
|
|
||||||
|
FC.$('#save-set').addEventListener('click', FC.handleSaveSet);
|
||||||
|
FC.$('#load-set').addEventListener('click', FC.handleLoadSet);
|
||||||
|
FC.$('#delete-set').addEventListener('click', FC.handleDeleteSet);
|
||||||
|
|
||||||
|
// Load saved flashcard lists
|
||||||
|
FC.savedSets = FC.retrieveSets();
|
||||||
|
FC.updateSetList(FC.savedSets);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
window.FC = FC;
|
||||||
|
})();
|
Loading…
x
Reference in New Issue
Block a user