Added players to the UI.

master
Pacman Ghost 6 years ago
parent e997caacf4
commit 040292279b
  1. 2
      vasl_templates/webapp/data/default-templates/players.j2
  2. 35
      vasl_templates/webapp/data/nationalities.json
  3. 19
      vasl_templates/webapp/generate.py
  4. 29
      vasl_templates/webapp/static/css/main.css
  5. 69
      vasl_templates/webapp/static/generate.js
  6. 103
      vasl_templates/webapp/static/main.js
  7. 30
      vasl_templates/webapp/templates/main.html
  8. 74
      vasl_templates/webapp/tests/test_generate.py
  9. 45
      vasl_templates/webapp/tests/test_players.py
  10. 10
      vasl_templates/webapp/tests/utils.py

@ -0,0 +1,2 @@
player1=[{{PLAYER_1}}] ; ELR=[{{PLAYER_1_ELR}}] ; SAN=[{{PLAYER_1_SAN}}]
player2=[{{PLAYER_2}}] ; ELR=[{{PLAYER_2_ELR}}] ; SAN=[{{PLAYER_2_SAN}}]

@ -0,0 +1,35 @@
[
{ "display_name": "German",
"ob_colors": [ "OBCOL:german", "OBCOL2:german" ]
},
{ "display_name": "Russian",
"ob_colors": [ "OBCOL:russian", "OBCOL2:russian" ]
},
{ "display_name": "American",
"ob_colors": [ "OBCOL:american", "OBCOL2:american" ]
},
{ "display_name": "British",
"ob_colors": [ "OBCOL:british", "OBCOL2:british" ]
},
{ "display_name": "French",
"ob_colors": [ "OBCOL:french", "OBCOL2:french" ]
},
{ "display_name": "Italian",
"ob_colors": [ "OBCOL:italian", "OBCOL2:italian" ]
},
{ "display_name": "Finnish",
"ob_colors": [ "OBCOL:finns", "OBCOL2:finns" ]
},
{ "display_name": "Japanese",
"ob_colors": [ "OBCOL:japanese", "OBCOL2:japanese" ]
}
]

@ -1,6 +1,7 @@
""" Webapp handlers. """
import os
import json
from flask import jsonify
@ -24,3 +25,21 @@ def get_templates():
templates[os.path.splitext(fname)[0]] = fp.read()
return jsonify( templates )
# ---------------------------------------------------------------------
@app.route( "/nationalities" )
def get_nationalities():
"""Get the nationalities table."""
# load the nationalities table
fname = os.path.join( DATA_DIR, "nationalities.json" )
with open(fname,"r") as fp:
nationalities = json.load( fp )
# auto-generate ID's for those entries that don't already have one
for nat in nationalities:
if "id" not in nat:
nat["id"] = nat["display_name"].lower()
return jsonify( { n["id"]: n for n in nationalities } )

@ -17,7 +17,6 @@ body { height: 100% ; }
grid-template-columns: 25em 1fr ; -ms-grid-columns: 25em 1fr ;
}
#panel-scenario {
height: 100% ;
grid-row-start: 1 ; -ms-grid-row: 1 ;
grid-row-end: 3 ; -ms-grid-row-span: 2 ;
}
@ -26,6 +25,7 @@ body { height: 100% ; }
}
#panel-ssr {
height: 100% ;
grid-column-start: 2 ;
-ms-grid-row: 2 ; -ms-grid-column: 2 ;
}
@ -67,7 +67,7 @@ fieldset legend { padding: 0 0.2em 0 0.2em ; font-style: italic ; font-weight: b
.label { font-weight: bold ; }
input[type="text"] { margin-bottom: 0.25em ; }
#panel-scenario .form-grid {
#panel-scenario {
display: grid ; display: -ms-grid ;
grid-template-columns: 5em 1fr ; -ms-grid-columns: 5em 1fr ;
}
@ -80,6 +80,31 @@ input[type="text"] { margin-bottom: 0.25em ; }
#panel-scenario div[data-labelfor="scenario_date"] { -ms-grid-row: 3 ; -ms-grid-column: 1 ; }
#panel-scenario div.scenario_date { -ms-grid-row: 3 ; -ms-grid-column: 2 ; }
#panel-players {
margin-top: 1em ;
display: grid ; display: -ms-grid ;
grid-template-columns: 5em 1fr 3em 3em ; -ms-grid-columns: 5em 1fr 3em 3em ;
}
#panel-players div[data-labelfor="elr"] { margin-left: 0.25em ; }
#panel-players div[data-labelfor="san"] { margin-left: 0.25em ; }
#panel-players select[name="player_1"] { margin-right: 0.5em ; }
#panel-players select[name="player_2"] { margin-right: 0.5em ; }
#panel-players select[name="player_1_elr"] { margin-right: 0.25em ; }
#panel-players select[name="player_2_elr"] { margin-right: 0.25em ; }
#panel-players input[data-id="players"] { margin-top: 0.25em ; }
/* FUDGE! IE hackamathon follows (nb: <label> doesn't work, we use <div> for labels instead :-/) */
#panel-players div[data-labelfor="elr"] { -ms-grid-row: 1 ; -ms-grid-column: 3 ; }
#panel-players div[data-labelfor="san"] { -ms-grid-row: 1 ; -ms-grid-column: 4 ; }
#panel-players div[data-labelfor="player_1"] { -ms-grid-row: 2 ; -ms-grid-column: 1 ; }
#panel-players select[name="player_1"] { -ms-grid-row: 2 ; -ms-grid-column: 2 ; }
#panel-players select[name="player_1_elr"] { -ms-grid-row: 2 ; -ms-grid-column: 3 ; }
#panel-players select[name="player_1_san"] { -ms-grid-row: 2 ; -ms-grid-column: 4 ; }
#panel-players div[data-labelfor="player_2"] { -ms-grid-row: 3 ; -ms-grid-column: 1 ; }
#panel-players select[name="player_2"] { -ms-grid-row: 3 ; -ms-grid-column: 2 ; }
#panel-players select[name="player_2_elr"] { -ms-grid-row: 3 ; -ms-grid-column: 3 ; }
#panel-players select[name="player_2_san"] { -ms-grid-row: 3 ; -ms-grid-column: 4 ; }
#panel-players input[data-id="players"] { -ms-grid-row: 4 ; -ms-grid-column: 4 ; }
#panel-vc textarea[name="victory_conditions"] { width: calc(100% - 2px) ; height: calc(100% - 1.5em) ; resize: none ; }
#panel-vc input[type="button"] { float: right ; }

@ -0,0 +1,69 @@
// NOTE: These fields aren't mandatory in the sense that snippet generation will fail
// if they're not set, but they're really, really, really expected to be there.
var _MANDATORY_PARAMS = {
scenario: { "SCENARIO_NAME": "scenario name", "SCENARIO_DATE": "scenario date" },
} ;
// --------------------------------------------------------------------
function generate_snippet( $btn )
{
// collect all the template parameters
var params = {} ;
add_param = function($elem) { params[ $elem.attr("name").toUpperCase() ] = $elem.val() ; } ;
$("input[type='text'].param").each( function() { add_param($(this)) ; } ) ;
$("textarea.param").each( function() { add_param($(this)) ; } ) ;
$("select.param").each( function() { add_param($(this)) ; } ) ;
// check for mandatory parameters
var template_id = $btn.data( "id" ) ;
if ( template_id in _MANDATORY_PARAMS ) {
var missing_params = [] ;
for ( var param_id in _MANDATORY_PARAMS[template_id] ) {
if ( ! (param_id in params && params[param_id].length > 0) )
missing_params.push( _MANDATORY_PARAMS[template_id][param_id] ) ;
}
if ( missing_params.length > 0 ) {
var buf = [ "Missing parameters:<ul>" ] ;
for ( var i=0 ; i < missing_params.length ; ++i )
buf.push( "<li>" + escapeHTML(missing_params[i]) ) ;
buf.push( "</ul>" ) ;
showWarningMsg( buf.join("") ) ;
}
}
// check that the players have different nationalities
if ( params.PLAYER_1 === params.PLAYER_2 )
showWarningMsg( "Both players have the same nationality!" ) ;
// get the template to generate the snippet from
if ( ! (template_id in gDefaultTemplates) ) {
showErrorMsg( "Unknown template: " + escapeHTML(template_id) ) ;
return ;
}
var func, val ;
try {
func = jinja.compile( gDefaultTemplates[template_id] ).render ;
}
catch( ex ) {
showErrorMsg( "Can't compile template:<pre>" + escapeHTML(ex) + "</pre>" ) ;
return ;
}
// process the template
try {
val = func( params ) ;
}
catch( ex ) {
showErrorMsg( "Can't process template <em>\"" + template_id + "\"</em>:<pre>" + escapeHTML(ex) + "</pre>" ) ;
return ;
}
try {
copyToClipboard( val ) ;
}
catch( ex ) {
showErrorMsg( "Can't copy to the clipboard:<pre>" + escapeHTML(ex) + "</pre>" ) ;
return ;
}
showInfoMsg( "The HTML snippet has been copied to the clipboard." ) ;
}

@ -1,11 +1,6 @@
var gNationalities = {} ;
var gDefaultTemplates = {} ;
// NOTE: These fields aren't mandatory in the sense that snippet generation will fail
// if they're not set, but they're really, really, really expected to be there.
var _MANDATORY_PARAMS = {
scenario: { "SCENARIO_NAME": "scenario name", "SCENARIO_DATE": "scenario date" },
} ;
// --------------------------------------------------------------------
$(document).ready( function () {
@ -15,6 +10,41 @@ $(document).ready( function () {
heightStyle: "fill",
} ).show() ;
var navHeight = $("#tabs .ui-tabs-nav").height() ;
$("input[name='scenario_name']").focus().focus() ;
// load the ELR's and SAN's
var buf = [] ;
for ( var i=0 ; i <= 5 ; ++i ) // nb: A19.1: ELR is 0-5
buf.push( "<option value='" + i + "'>" + i + "</option>" ) ;
buf = buf.join( "" ) ;
$("select[name='player_1_elr']").html( buf ).val( 5 ) ;
$("select[name='player_2_elr']").html( buf ).val( 5 ) ;
buf = [ "<option></option>" ] ; // nb: allow scenarios that have no SAN
for ( i=2 ; i <= 7 ; ++i ) // nb: A14.1: SAN is 2-7
buf.push( "<option value='" + i + "'>" + i + "</option>" ) ;
buf = buf.join( "" ) ;
$("select[name='player_1_san']").html( buf ).val( 2 ) ;
$("select[name='player_2_san']").html( buf ).val( 2 ) ;
// load the nationalities
$.getJSON( gGetNationalitiesUrl, function(data) {
gNationalities = data ;
var buf = [] ;
for ( var id in gNationalities )
buf.push( "<option value='" + id + "'>" + gNationalities[id].display_name + "</option>" ) ;
on_player_change(
$("select[name='player_1']").html( buf ).val( "german" )
) ;
on_player_change(
$("select[name='player_2']").html( buf ).val( "russian" )
) ;
} ).fail( function( xhr, status, errorMsg ) {
showErrorMsg( "Can't get the nationalities:<pre>" + escapeHTML(errorMsg) + "</pre>" ) ;
} ) ;
// add handlers for player changes
$("select[name='player_1']").change( function() { on_player_change($(this)) ; } ) ;
$("select[name='player_2']").change( function() { on_player_change($(this)) ; } ) ;
// get the default templates
$.getJSON( gGetTemplatesUrl, function(data) {
@ -49,59 +79,14 @@ $(document).ready( function () {
// --------------------------------------------------------------------
function generate_snippet( $btn )
function on_player_change( $select )
{
// collect all the template parameters
var params = {} ;
add_param = function($elem) { params[ $elem.attr("name").toUpperCase() ] = $elem.val() ; } ;
$("input[type='text'].param").each( function() { add_param($(this)) ; } ) ;
$("textarea.param").each( function() { add_param($(this)) ; } ) ;
// check for mandatory parameters
var template_id = $btn.data( "id" ) ;
if ( template_id in _MANDATORY_PARAMS ) {
var missing_params = [] ;
for ( var param_id in _MANDATORY_PARAMS[template_id] ) {
if ( ! (param_id in params && params[param_id].length > 0) )
missing_params.push( _MANDATORY_PARAMS[template_id][param_id] ) ;
}
if ( missing_params.length > 0 ) {
var buf = [ "Missing parameters:<ul>" ] ;
for ( var i=0 ; i < missing_params.length ; ++i )
buf.push( "<li>" + escapeHTML(missing_params[i]) ) ;
buf.push( "</ul>" ) ;
showWarningMsg( buf.join("") ) ;
}
}
// get the template to generate the snippet from
if ( ! (template_id in gDefaultTemplates) ) {
showErrorMsg( "Unknown template: " + escapeHTML(template_id) ) ;
return ;
}
var func, val ;
try {
func = jinja.compile( gDefaultTemplates[template_id] ).render ;
}
catch( ex ) {
showErrorMsg( "Can't compile template:<pre>" + escapeHTML(ex) + "</pre>" ) ;
return ;
}
// figure out which player was changed
var name = $select.attr( "name" ) ;
var player_id = name.substring( name.length-1 ) ;
// process the template
try {
val = func( params ) ;
}
catch( ex ) {
showErrorMsg( "Can't process template <em>\"" + template_id + "\"</em>:<pre>" + escapeHTML(ex) + "</pre>" ) ;
return ;
}
try {
copyToClipboard( val ) ;
}
catch( ex ) {
showErrorMsg( "Can't copy to the clipboard:<pre>" + escapeHTML(ex) + "</pre>" ) ;
return ;
}
showInfoMsg( "The HTML snippet has been copied to the clipboard." ) ;
// update the tab label
var nat = $select.find( "option:selected" ).val() ;
var $elem = $("#tabs .ui-tabs-nav a[href='#tabs-ob" + player_id + "']") ;
$elem.text( gNationalities[nat].display_name + " OB" ) ;
}

@ -21,19 +21,23 @@
</ul>
<div id="tabs-scenario">
<div id="panel-scenario">
<fieldset> <legend>Scenario</legend>
<div class="form-grid">
<div class="label" data-labelfor="scenario_name">Name:</div> <input name="scenario_name" type="text" class="param">
<div class="label" data-labelfor="scenario_location">Location:</div> <input name="scenario_location" type="text" class="param">
<div class="label" data-labelfor="scenario_date">Date:</div>
<div class="scenario_date">
<input name="scenario_date" type="text" size="10" class="param">
<input type="button" class="generate" data-id="scenario" value="Go">
</div>
<fieldset> <legend>Scenario</legend>
<div id="panel-scenario">
<div class="label" data-labelfor="scenario_name">Name:</div> <input name="scenario_name" type="text" class="param">
<div class="label" data-labelfor="scenario_location">Location:</div> <input name="scenario_location" type="text" class="param">
<div class="label" data-labelfor="scenario_date">Date:</div>
<div class="scenario_date">
<input name="scenario_date" type="text" size="10" class="param">
<input type="button" class="generate" data-id="scenario" value="Go">
</div>
</fieldset>
</div>
</div>
<div id="panel-players">
<div></div> <div></div> <div class="label" data-labelfor="elr">ELR</div> <div class="label" data-labelfor="san">SAN</div>
<div class="label" data-labelfor="player_1">Player 1:</div> <select name="player_1" class="param"></select> <select name="player_1_elr" class="param"></select> <select name="player_1_san" class="param"></select>
<div class="label" data-labelfor="player_2">Player 2:</div> <select name="player_2" class="param"></select> <select name="player_2_elr" class="param"></select> <select name="player_2_san" class="param"></select>
<div></div> <div></div> <div></div> <input type="button" class="generate" data-id="players" value="Go">
</div>
</fieldset>
<div id="panel-vc">
<fieldset> <legend>Victory Conditions</legend>
<textarea name="victory_conditions" type="text" class="param"> </textarea>
@ -92,8 +96,10 @@
<script src="{{url_for('static',filename='growl/jquery.growl.js')}}"></script>
<script>
gGetTemplatesUrl = "{{url_for('get_templates')}}" ;
gGetNationalitiesUrl = "{{url_for('get_nationalities')}}" ;
</script>
<script src="{{url_for('static',filename='main.js')}}"></script>
<script src="{{url_for('static',filename='generate.js')}}"></script>
<script src="{{url_for('static',filename='utils.js')}}"></script>
</html>

@ -1,5 +1,7 @@
""" Test HTML snippet generation. """
from selenium.webdriver.support.ui import Select
from vasl_templates.webapp.tests.utils import get_clipboard, get_stored_msg, find_child
# ---------------------------------------------------------------------
@ -10,12 +12,16 @@ def _test_snippet( webdriver, template_id, params, expected, expected2 ):
# set the template parameters
for key,val in params.items():
elem = find_child( webdriver, "input[name='{}']".format(key) )
if not elem:
elem = find_child( webdriver, "textarea[name='{}']".format(key) )
elem.clear()
if val:
elem.send_keys( val )
elem = next( c for c in ( \
find_child( webdriver, "{}[name='{}']".format(elem_type,key) ) \
for elem_type in ["input","textarea","select"]
) if c )
if elem.tag_name == "select":
Select(elem).select_by_value( val )
else:
elem.clear()
if val:
elem.send_keys( val )
# generate the snippet
submit = find_child( webdriver, "input[class='generate'][data-id='{}']".format(template_id) )
@ -27,12 +33,21 @@ def _test_snippet( webdriver, template_id, params, expected, expected2 ):
# check warnings for mandatory parameters
last_warning = get_stored_msg( "_last-warning_" ) or ""
param_names = [ "scenario name", "scenario location", "scenario date" ]
for pname in param_names:
if pname in expected2:
assert pname in last_warning
else:
assert pname not in last_warning
if isinstance( expected2, list):
# check for mandatory parameters
param_names = [ "scenario name", "scenario location", "scenario date" ]
for pname in param_names:
if pname in expected2:
assert pname in last_warning
else:
assert pname not in last_warning
elif isinstance(expected2, str):
# check for a specific error message
assert expected2 == last_warning
else:
# make sure there was no warning message
assert expected2 is None
assert not last_warning
# ---------------------------------------------------------------------
@ -49,7 +64,7 @@ def test_scenario_snippets( webapp, webdriver ):
"scenario_date": "now",
},
"name = [my scenario] | loc = [here] | date = [now]",
[]
None
)
# generate a SCENARIO snippet with some fields missing
@ -85,7 +100,7 @@ def test_vc_snippets( webapp, webdriver ):
"victory_conditions": "Kill 'Em All!",
},
"VC: Kill 'Em All!",
[]
None
)
# generate a VC snippet
@ -93,5 +108,34 @@ def test_vc_snippets( webapp, webdriver ):
"victory_conditions": "",
},
"VC:",
[]
None
)
# ---------------------------------------------------------------------
def test_players_snippets( webapp, webdriver ):
"""Test HTML snippet generation."""
# initialize
webdriver.get( webapp.url_for( "main", store_msgs=1 ) )
# generate a PLAYERS snippet
_test_snippet( webdriver, "players", {
"player_1": "french",
"player_1_elr": "1",
"player_1_san": "2",
"player_2": "british",
"player_2_elr": "3",
"player_2_san": "4",
},
"player1=[french] ; ELR=[1] ; SAN=[2] | player2=[british] ; ELR=[3] ; SAN=[4]",
None
)
# generate a PLAYERS snippet with both players the same nationality
_test_snippet( webdriver, "players", {
"player_1": "british",
},
"player1=[british] ; ELR=[1] ; SAN=[2] | player2=[british] ; ELR=[3] ; SAN=[4]",
[ "Both players have the same nationality!" ],
)

@ -0,0 +1,45 @@
""" Test how players are handled. """
from selenium.webdriver.support.ui import Select
from vasl_templates.webapp.tests.utils import get_nationalities, find_child
# ---------------------------------------------------------------------
def _get_player( webdriver, player_id ):
"""Get the nationality of the specified player."""
sel = Select(
find_child( webdriver, "select[name='player_{}']".format( player_id ) )
)
return sel.first_selected_option.get_attribute( "value" )
# ---------------------------------------------------------------------
def test_player_change( webapp, webdriver ):
"""Test changing players."""
# initialize
webdriver.get( webapp.url_for( "main" ) )
nationalities = get_nationalities( webapp )
# make sure that the UI was updated correctly for the initial players
for player_no in [1,2]:
player_id = _get_player( webdriver, player_no )
elem = find_child( webdriver, "#tabs .ui-tabs-nav a[href='#tabs-ob{}']".format( player_no ) )
assert elem.text == "{} OB".format( nationalities[player_id]["display_name"] )
# change player 1
sel = Select(
find_child( webdriver, "select[name='player_1']" )
)
sel.select_by_value( "finnish" )
elem = find_child( webdriver, "#tabs .ui-tabs-nav a[href='#tabs-ob1']" )
assert elem.text == "{} OB".format( nationalities["finnish"]["display_name"] )
# change player 2
sel = Select(
find_child( webdriver, "select[name='player_2']" )
)
sel.select_by_value( "japanese" )
elem = find_child( webdriver, "#tabs .ui-tabs-nav a[href='#tabs-ob2']" )
assert elem.text == "{} OB".format( nationalities["japanese"]["display_name"] )

@ -1,5 +1,8 @@
""" Helper utilities. """
import urllib.request
import json
from PyQt5.QtWidgets import QApplication
from selenium.common.exceptions import NoSuchElementException
@ -7,6 +10,13 @@ _webdriver = None
# ---------------------------------------------------------------------
def get_nationalities( webapp ):
"""Get the nationalities table."""
url = webapp.url_for( "get_nationalities" )
return json.load( urllib.request.urlopen( url ) )
# ---------------------------------------------------------------------
def get_stored_msg( msg_id ):
"""Get a message stored for us by the front-end."""
elem = find_child( _webdriver, "#"+msg_id )

Loading…
Cancel
Save