Finished up project - with bonus: card filters!

This commit is contained in:
John Washam
2016-07-04 19:24:38 -07:00
parent d065628e25
commit 6c1e865591
13 changed files with 364 additions and 110 deletions

View File

@@ -31,15 +31,14 @@ To Do:
- [x] Auth errors
- [x] Session set
- [x] Logout
- [ ] Card Area
- [x] Card Area
- [x] List cards
- [x] Add cards
- [x] Edit card
- [ ] Delete card
- [x] Delete card
- Card Memorization
- [ ] Card type toggle
- [ ] Show card
- [ ] Flip card
- [ ] Next card
- [ ] Last card
- [ ] Mark as known
- [x] Card type toggle
- [x] Flip card
- [x] Mark as known, next card
- [x] Next card
- [x] Card filters

View File

@@ -57,7 +57,10 @@ def close_db(error):
@app.route('/')
def index():
return render_template('index.html')
if session.get('logged_in'):
return redirect(url_for('general'))
else:
return redirect(url_for('login'))
@app.route('/cards')
@@ -65,9 +68,39 @@ def cards():
if not session.get('logged_in'):
return redirect(url_for('login'))
db = get_db()
cur = db.execute('SELECT id, type, front, back, known FROM cards ORDER BY id DESC')
query = '''
SELECT id, type, front, back, known
FROM cards
ORDER BY id DESC
'''
cur = db.execute(query)
cards = cur.fetchall()
return render_template('cards.html', cards=cards)
return render_template('cards.html', cards=cards, filter_name=None)
@app.route('/filter_cards/<filter_name>')
def filter_cards(filter_name):
if not session.get('logged_in'):
return redirect(url_for('login'))
filters = {
"all": "where 1 = 1",
"general": "where type = 1",
"code": "where type = 2",
"known": "where known = 1",
"unknown": "where known = 0",
}
query = filters.get(filter_name)
if not query:
return redirect(url_for('cards'))
db = get_db()
fullquery = "SELECT id, type, front, back, known FROM cards " + query + " ORDER BY id DESC"
cur = db.execute(fullquery)
cards = cur.fetchall()
return render_template('cards.html', cards=cards, filter_name=filter_name)
@app.route('/add', methods=['POST'])
@@ -90,7 +123,12 @@ def edit(card_id):
if not session.get('logged_in'):
return redirect(url_for('login'))
db = get_db()
cur = db.execute('SELECT * FROM cards WHERE id = ?', [card_id])
query = '''
SELECT id, type, front, back, known
FROM cards
WHERE id = ?
'''
cur = db.execute(query, [card_id])
card = cur.fetchone()
return render_template('edit.html', card=card)
@@ -102,7 +140,16 @@ def edit_card():
selected = request.form.getlist('known')
known = bool(selected)
db = get_db()
db.execute('UPDATE cards set type = ?, front = ?, back = ?, known = ? where id = ?',
command = '''
UPDATE cards
SET
type = ?,
front = ?,
back = ?,
known = ?
WHERE id = ?
'''
db.execute(command,
[request.form['type'],
request.form['front'],
request.form['back'],
@@ -129,14 +176,62 @@ def delete(card_id):
def general():
if not session.get('logged_in'):
return redirect(url_for('login'))
return render_template('general.html')
return memorize("general")
@app.route('/code')
def code():
if not session.get('logged_in'):
return redirect(url_for('login'))
return render_template('code.html')
return memorize("code")
def memorize(card_type):
if card_type == "general":
type = 1
elif card_type == "code":
type = 2
else:
return redirect(url_for('cards'))
card = get_card(type)
if not card:
flash("You've learned all the " + card_type + " cards.")
return redirect(url_for('cards'))
short_answer = (len(card['back']) < 75)
return render_template('memorize.html',
card=card,
card_type=card_type,
short_answer=short_answer)
def get_card(type):
db = get_db()
query = '''
SELECT
id, type, front, back, known
FROM cards
WHERE
type = ?
and known = 0
ORDER BY RANDOM()
LIMIT 1
'''
cur = db.execute(query, [type])
return cur.fetchone()
@app.route('/mark_known/<card_id>/<card_type>')
def mark_known(card_id, card_type):
if not session.get('logged_in'):
return redirect(url_for('login'))
db = get_db()
db.execute('UPDATE cards SET known = 1 WHERE id = ?', [card_id])
db.commit()
flash('Card marked as known.')
return redirect(url_for(card_type))
@app.route('/login', methods=['GET', 'POST'])

1
static/fastclick.min.js vendored Normal file

File diff suppressed because one or more lines are too long

57
static/general.js Normal file
View File

@@ -0,0 +1,57 @@
$(document).ready(function(){
if ($('.memorizePanel').length != 0) {
$('.flipCard').click(function(){
$('.cardFront').hide();
$('.cardBack').show();
});
}
if ($('.cardForm').length != 0) {
$('.cardForm').submit(function(){
var frontTrim = $.trim($('#front').val());
$('#front').val(frontTrim);
var backTrim = $.trim($('#back').val());
$('#back').val(backTrim);
if (! $('#front').val() || ! $('#back').val()) {
return false;
}
});
}
if ($('.editPanel').length != 0) {
function checkit() {
var checkedVal = $('input[name=type]:checked').val();
if (checkedVal === undefined) {
// hide the fields
$('.fieldFront').hide();
$('.fieldBack').hide();
$('.saveButton').hide();
} else {
$('.toggleButton').removeClass('toggleSelected');
$(this).addClass('toggleSelected');
if (checkedVal == '1') {
$('textarea[name=back]').attr('rows', 5);
} else {
$('textarea[name=back]').attr('rows', 12);
}
$('.fieldFront').show();
$('.fieldBack').show();
$('.saveButton').show();
}
}
$('.toggleButton').click(checkit);
checkit();
}
// to remove the short delay on click on touch devices
FastClick.attach(document.body);
});

View File

@@ -6,3 +6,45 @@
textarea {
font-family: monospace;
}
.cardContent h4 {
margin-top: 0;
}
.alignContainer {
display: table;
width: 100%;
}
.alignMiddle {
height: 20em;
display: table-cell;
vertical-align: middle;
}
.cardBack {
display: none;
}
.largerText {
font-size: 2em;
}
/* disables collapsing header menu */
.navbar-collapse.collapse {
display: block!important;
}
.navbar-nav>li, .navbar-nav {
float: left !important;
}
.navbar-nav.navbar-right:last-child {
margin-right: 0 !important;
}
.navbar-right {
float: right!important;
}

View File

@@ -1,9 +1,9 @@
{% extends "layout.html" %}
{% block body %}
<div class="well">
<div class="well editPanel">
<h2>Add a Card</h2>
<form action="{{ url_for('add_card') }}" method=post>
<form action="{{ url_for('add_card') }}" method="post" class="cardForm">
<div class="form-group">
<label for="general" class="toggleButton btn btn-default btn-lg">General &nbsp;
<input type="radio" name="type" value="1" id="general"/>
@@ -14,7 +14,7 @@
</div>
<div class="form-group fieldFront">
<label for="front">Front of Card</label>
<input type="text" name="front" class="form-control">
<input type="text" name="front" id="front" class="form-control">
</div>
<div class="form-group fieldBack">
<label for="back">Back of Card</label>
@@ -33,55 +33,44 @@
<div class="page-header">
<h2>{{ cards|length }} Card{{ '' if (cards|length == 1) else 's' }}</h2>
</div>
<div class="btn-group btn-group-md" role="group" aria-label="filters">
<a href="{{ url_for('filter_cards', filter_name="all") }}" class="btn btn-{{ "primary" if filter_name == "all" else "default" }}">All</a>
<a href="{{ url_for('filter_cards', filter_name="general") }}" class="btn btn-{{ "primary" if filter_name == "general" else "default" }}">General</a>
<a href="{{ url_for('filter_cards', filter_name="code") }}" class="btn btn-{{ "primary" if filter_name == "code" else "default" }}">Code</a>
<a href="{{ url_for('filter_cards', filter_name="known") }}" class="btn btn-{{ "primary" if filter_name == "known" else "default" }}">Known</a>
<a href="{{ url_for('filter_cards', filter_name="unknown") }}" class="btn btn-{{ "primary" if filter_name == "unknown" else "default" }}">Unknown</a>
</div>
<br />
<br />
<table class="table table-bordered">
{% for card in cards %}
<div>
<h3>
<tr>
<td>
<a href="{{ url_for('edit', card_id=card.id) }}" class="btn btn-xs btn-primary"><i class="fa fa-pencil" aria-hidden="true"></i></a>
</td>
<td class="cardContent">
<h4>
{{ card.front }}
</h3>
</h4>
{% if card.type == 1 %}
{{ card.back|replace("\n", "<br />")|safe }}
{% else %}
<pre>{{ card.back|safe }}</pre>
{% endif %}
<hr>
</div>
</td>
</tr>
{% else %}
<li><em>Unbelievable. No cards here so far.</em>
<tr>
<td>
<em>No cards to show.</em>
</td>
</tr>
{% endfor %}
</table>
<script type=text/javascript>
$(document).ready(function () {
function checkit() {
var checkedVal = $('input[name=type]:checked').val();
if (checkedVal === undefined) {
// hide the fields
$('.fieldFront').hide();
$('.fieldBack').hide();
$('.saveButton').hide();
} else {
$('.toggleButton').removeClass('toggleSelected');
$(this).addClass('toggleSelected');
if (checkedVal == '1') {
$('textarea[name=back]').attr('rows', 5);
} else {
$('textarea[name=back]').attr('rows', 12);
}
$('.fieldFront').show();
$('.fieldBack').show();
$('.saveButton').show();
}
}
$('.toggleButton').click(checkit);
checkit();
});
</script>
{% endblock %}

View File

@@ -1,4 +0,0 @@
{% extends "layout.html" %}
{% block body %}
code
{% endblock %}

View File

@@ -2,7 +2,7 @@
{% block body %}
<div class="well">
<h2>Edit Card #{{ card.id }}</h2>
<form action="{{ url_for('edit_card') }}" method=post>
<form action="{{ url_for('edit_card') }}" method="post" class="cardForm">
<div class="form-group">
<label for="general" class="btn btn-default btn-lg">General &nbsp;
@@ -15,7 +15,7 @@
</div>
<div class="form-group">
<label for="front">Front of Card</label>
<input type="text" name="front" class="form-control" value="{{ card.front|e }}">
<input type="text" name="front" id="front" class="form-control" value="{{ card.front|e }}">
</div>
<div class="form-group">
<label for="back">Back of Card</label>

View File

@@ -1,4 +0,0 @@
{% extends "layout.html" %}
{% block body %}
general
{% endblock %}

View File

@@ -1,4 +0,0 @@
{% extends "layout.html" %}
{% block body %}
{% endblock %}

View File

@@ -2,11 +2,9 @@
<html>
<head>
<title>CS Flash Cards</title>
<link rel="stylesheet" type="text/css" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" />
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css"
integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
<link rel=stylesheet type=text/css href="{{ url_for('static', filename='style.css') }}"/>
<!-- I know this doesn't belong in head, but I have script in a template that needs to have jQuery -->
<script src="https://code.jquery.com/jquery-2.2.4.min.js" integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>
<script src="https://use.fontawesome.com/8cea844162.js"></script>
</head>
<body>
<div class="container">
@@ -15,20 +13,9 @@
<br/>
<nav class="navbar navbar-default">
<div class="container-fluid">
<!-- toggle menu -->
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">CS Flash Cards</a>
</div>
<a class="navbar-brand" href="{{ url_for('index') }}">CS Flash Cards</a>
<!-- full menu -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav navbar-right">
{% if not session.logged_in %}
<li><a href="{{ url_for('login') }}">log in</a></li>
@@ -36,14 +23,13 @@
<li><a href="{{ url_for('cards') }}">cards</a></li>
<li><a href="{{ url_for('general') }}">general</a></li>
<li><a href="{{ url_for('code') }}">code</a></li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;</li>
<li><a href="{{ url_for('logout') }}">log out</a></li>
{% endif %}
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</div>
</nav>
{% for message in get_flashed_messages() %}
<div class="alert alert-success" role="alert">{{ message }}</div>
{% endfor %}
@@ -51,6 +37,11 @@
</div>
</div>
</div>
<script src="https://code.jquery.com/jquery-2.2.4.min.js"
integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
<script src="https://use.fontawesome.com/8cea844162.js"></script>
<script src="{{ url_for('static', filename='fastclick.min.js') }}"></script>
<script src="{{ url_for('static', filename='general.js') }}"></script>
</body>
</html>

View File

@@ -2,7 +2,8 @@
{% block body %}
<h2>Login</h2>
{% if error %}
<div class="alert alert-danger" role="alert">{{ error }}</div>{% endif %}
<div class="alert alert-danger" role="alert">{{ error }}</div>
{% endif %}
<div class="well">
<form action="{{ url_for('login') }}" method=post>

91
templates/memorize.html Normal file
View File

@@ -0,0 +1,91 @@
{% extends "layout.html" %}
{% block body %}
<div class="row">
<div class="col-xs-12 text-center">
<div class="btn-group btn-group-lg" role="group" aria-label="card type">
<a href="{{ url_for('general') }}" class="btn btn-{{ "primary" if card_type == "general" else "default" }}">General</a>
<a href="{{ url_for('code') }}" class="btn btn-{{ "primary" if card_type == "code" else "default" }}">Code</a>
</div>
</div>
</div>
<hr/>
<div class="row memorizePanel">
<!--
<div class="col-xs-2">
<div class="alignContainer">
<div class="alignMiddle text-right">
<br/>
<br/>
<a href="wewetw"><i class="fa fa-chevron-left fa-5x"></i></a>
</div>
</div>
</div>
-->
<div class="col-xs-8 col-xs-offset-2">
<div class="panel panel-default cardFront">
<div class="panel-body">
<div class="alignContainer">
<div class="alignMiddle frontText">
<h3 class="text-center">{{ card.front }}</h3>
</div>
</div>
</div>
</div>
<div class="panel panel-primary cardBack">
<div class="panel-body">
<div class="alignContainer">
<div class="alignMiddle frontText">
{% if card.type == 1 %}
{% if short_answer %}
<div class="text-center largerText">
{% endif %}
{{ card.back|replace("\n", "<br />")|safe }}
{% if short_answer %}
</div>
{% endif %}
{% else %}
<pre>{{ card.back|safe }}</pre>
{% endif %}
</div>
</div>
</div>
</div>
</div>
<!--
<div class="col-xs-2">
<div class="alignContainer">
<div class="alignMiddle text-left">
<br/>
<br/>
<a href="ergergerge"><i class="fa fa-chevron-right fa-5x"></i></a>
</div>
</div>
</div>
-->
</div>
<div class="row">
<div class="col-xs-12 text-center">
<a href="javascript:" class="btn btn-primary btn-lg flipCard">
<i class="fa fa-exchange"></i>
Flip Card
</a>
&nbsp;
&nbsp;
<a href="{{ url_for('mark_known', card_id=card.id, card_type=card_type) }}" class="btn btn-success btn-lg">
<i class="fa fa-check"></i>
I Know It
</a>
&nbsp;
&nbsp;
<a href="{{ url_for(card_type) }}" class="btn btn-primary btn-lg">
Next Card
<i class="fa fa-arrow-right"></i>
</a>
</div>
</div>
{% endblock %}