<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.0/css/bootstrap.min.css" rel="stylesheet" id="bootstrap-css">
<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.0/js/bootstrap.min.js"></script>
<script src="//code.jquery.com/jquery-1.11.1.min.js"></script>
<!------ Include the above in your HEAD tag ---------->
<div class="container">
<div class="row">
<h2>Live Tracker</h2>
<p>
<a role="button" class="btn btn-default" data-toggle="modal" data-target="#settings-modal" id="settings-btn">Settings</a>
</p>
<hr/>
<p>
<a role="button" class="btn btn-primary" id="select-competitor-btn" data-toggle="modal" data-target="#select-competitor-modal" >
Select Competitor
</a>
</p>
<div class="alert" id="competitor-info"></div>
<p>
<a role="button" class="btn btn-danger disabled" id="start-btn">Start</a>
<a role="button" class="btn btn-danger disabled" id="schedule-start-btn" data-toggle="modal" data-target="#schedule-start-modal">Schedule Start</a>
</p>
<hr/>
<div id="app-status" class="alert" role="alert"></div>
</div>
</div>
<div class="modal fade" id="settings-modal" tabindex="-1" role="dialog" aria-labelledby="settings-modal-label" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
<h4 class="modal-title" id="settings-modal-label">Settings</h4>
</div>
<div class="modal-body">
<div class="alert alert-warning error-message" id="settings-errors"></div>
<div class="form-group">
<label for="server-address-input">Server address</label>
<input type="text" class="form-control" id="server-address-input" placeholder="http://127.0.0.1:8000/">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" id="save-settings-btn">Save</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="schedule-start-modal" tabindex="-1" role="dialog" aria-labelledby="schedule-start-modal-label" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
<h4 class="modal-title" id="schedule-start-modal-label">Schedule start</h4>
</div>
<div class="modal-body">
<div class="alert alert-warning error-message" id="schedule-start-errors"></div>
<div class="form-group">
<label for="server-address-input">Minutes before start</label>
<input type="integer" class="form-control" id="start-delay-input" placeholder="5" value="5">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" id="save-schedule-start-btn">Save</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="streaming-modal" tabindex="-1" role="dialog" aria-hidden="true" data-backdrop="static" data-keyboard="false">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title" id="settings-modal-label">Streaming Position</h4>
</div>
<div class="modal-body">
<div id="streamin-info-div"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger" id="stop-streaming-btn">Stop</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="select-competitor-modal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="select-competitor-pane" id="select-competitor-pane-1">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
<h4 class="modal-title" id="select-competitor-modal-label">Select Competition</h4>
<button class="btn btn-default" id="refresh-competition-list-btn">Refresh</button>
</div>
<div class="modal-body" id="competition-list">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
</div>
</div>
<div class="select-competitor-pane" id="select-competitor-pane-2">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
<h4 class="modal-title" id="select-competitor-modal-label">Select Competitor</h4>
<button class="btn btn-default" id="refresh-competitor-list-btn">Refresh</button>
</div>
<div class="modal-body" id="competitor-list">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default back-pane-btn" id="back-pane-btn-1">Back</button>
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
</div>
</div>
<div class="select-competitor-pane" id="select-competitor-pane-3">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
<h4 class="modal-title" id="select-competitor-modal-label">Enter Access code</h4>
</div>
<div class="modal-body" id="access-code-form">
<div class="alert alert-warning error-message" id="access-code-errors"></div>
<div class="form-group">
<label for="access-code-input">Access Code</label>
<input type="password" class="form-control" id="access-code-input" placeholder="12345">
</div>
<div>
<button type="button" class="btn btn-primary" id="validate-access-code-btn">Submit</button>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default back-pane-btn" id="back-pane-btn-2">Back</button>
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
</div>
</div>
</div>
</div>
</div>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jStorage/0.4.12/jstorage.min.js"></script>
var DEFAULT_SERVER_ADDRESS = 'http://127.0.0.1:8000/';
var tmp_competition = null;
var tmp_competitor = null;
var watchPositionId = null;
var isGpsSwitchedOn = false;
var setStatus = function(message, type){
$("#app-status").text(message);
if(type != "success" && type != "info" && type != "warning" && type != "danger"){
type = "info";
}
$("#app-status")
.removeClass("alert-success alert-info alert-warning alert-danger")
.addClass("alert-"+type);
};
var onServerUrlChange = function(){
fetchServerTime();
fetchCompetitionList();
};
var getServerUrl = function(){
return $.jStorage.get('server-address', DEFAULT_SERVER_ADDRESS);
};
var setServerUrl = function(url){
$.jStorage.set('server-address', url);
};
var getServerDrift = function(){
return $.jStorage.get('server-drift', 0);
};
var setServerDrift = function(timeRequest, timeServer, timeResponse){
var drift = timeServer - (timeResponse + timeRequest) / 2;
$.jStorage.set('server-drift', drift);
};
var getServerTime = function(){
return +(new Date())+getServerDrift();
};
var setCompetitionList = function(data){
$.jStorage.set('competition-list', data);
};
var getCompetitionList = function(){
return $.jStorage.get('competition-list', []);
};
var setCompetitorList = function(data){
$.jStorage.set('competitor-list', data);
};
var getCompetitorList = function(){
return $.jStorage.get('competitor-list', []);
};
var setCompetitionDetail = function(competition){
$.jStorage.set('competition-id', competition.id);
$.jStorage.set('competition-name', competition.name);
$.jStorage.set('competition-live_delay', competition.live_delay);
};
var getCompetitionDetail = function(){
return {
id: $.jStorage.get('competition-id', null),
name: $.jStorage.get('competition-name', null),
live_delay: $.jStorage.get('competition-live_delay', 30)
};
};
var setCompetitorDetail = function(competitor){
$.jStorage.set('competitor-id', competitor.id);
$.jStorage.set('competitor-name', competitor.name);
$.jStorage.set('competitor-token', competitor.token);
};
var getCompetitorDetail = function(){
return {
id: $.jStorage.get('competitor-id', null),
name: $.jStorage.get('competitor-name', null),
token: $.jStorage.get('competitor-token', null)
}
};
var saveSettings = function(){
errors = [];
var url = $('#server-address-input').val();
if(!url){
url = getServerUrl();
}
addressRe = /^https?:\/\/.*\/?$/i;
if(!url.match(addressRe)){
errors.push("Invalid Server Address");
} else {
if(url[url.length-1] != "/"){
url = url + "/";
}
}
if(errors.length > 0){
$('#settings-errors').html('<ul><li>'+errors.join('</li><li>')+"</li></ul>");
$('#settings-errors').show();
} else {
$('#settings-errors').hide();
$('#settings-modal').modal('hide');
if(url != getServerUrl()){
setServerUrl(url);
onServerUrlChange();
}
}
};
var fetchServerTime = function(){
setStatus('Fetch server time');
var url = getServerUrl()+"api/time";
var timeReq = +(new Date());
$.ajax({
url: url
})
.success(function(response){
var timeServer = response.time*1e3;
var timeResp = +(new Date());
setServerDrift(timeReq, timeServer, timeResp);
var now = new Date(getServerTime());
setStatus('Server time fetched '+now, 'success');
})
.error(function(response){
setStatus('Error fetching server time', 'danger');
});
};
var fetchCompetitionList = function(url){
setStatus('Fetch competition List');
url = url || getServerUrl()+"api/competition";
$.ajax({
url: url,
data: {
status: ['live', 'upcoming'],
reverse_order: 'true',
result_per_page: 1000
}
}).success(function(response){
var results;
if(response.previous === null){
results = []
}else{
results = getCompetitionList()
}
results = results.concat(response.results)
setCompetitionList(results);
if(response.next===null){
setStatus('Competition list fetched', 'success');
displayCompetitionList();
} else {
fetchCompetitionList(response.next);
}
}).error(function(){
setStatus('Error fetching the competition list', 'danger');
});
};
var fetchCompetitorList = function(url){
setStatus('Fetch competitor List');
competition = tmp_competition;
url = url || getServerUrl()+"api/competitor";
$.ajax({
url: url,
data: {
competition_id: competition.id,
result_per_page: 1000
}
}).success(function(response){
var results;
if(response.previous === null){
results = []
}else{
results = getCompetitorList()
}
results = results.concat(response.results)
setCompetitorList(results);
if(response.next===null){
setStatus('Competitor list fetched', 'success');
displayCompetitorList();
} else {
fetchCompetitorList(response.next);
}
}).error(function(){
setStatus('Error fetching the competitor list', 'danger');
});
};
var displayCompetitionList = function(){
var competitions = getCompetitionList();
$('#competition-list').html('')
if(competitions.length===0){
$('#competition-list').append($('<p class="alert alert-warning">No competitions are available</p>'));
return;
}
$('#competition-list').append('<table class="table table-striped"/>')
$.each(competitions, function(ii, competition){
var link = $('<td><a href="#">'+competition.name+'</a></td>');
link.on('click', function(){
tmp_competition = competition;
fetchCompetitorList();
$('.select-competitor-pane').hide();
$('#select-competitor-pane-2').show();
})
$('#competition-list > table').append($('<tr/>').append(link));
});
};
var displayCompetitorList = function(){
var competitors = getCompetitorList();
$('#competitor-list').html('')
if(competitors.length===0){
$('#competitor-list').append($('<p class="alert alert-warning">No competitors are available</p>'));
return;
}
$('#competitor-list').append('<table class="table table-striped"/>')
$.each(competitors, function(ii, competitor){
var link = $('<td><a href="#">'+competitor.name+'</a></td>');
link.on('click', function(){
tmp_competitor = competitor;
$('.select-competitor-pane').hide();
$('.error-message').hide();
$('#access-code-input').val('');
$('#select-competitor-pane-3').show();
})
$('#competitor-list > table').append($('<tr/>').append(link));
});
};
var isCompetitorSet = function(){
var competition = getCompetitionDetail();
var competitor = getCompetitorDetail();
return (competition.id !== null || competitor.id !== null);
};
var displayCompetitor = function(){
if(!isCompetitorSet()){
$("#competitor-info").text("No competitor selected")
.removeClass('alert-success')
.addClass('alert-warning');
return;
}
var competition = getCompetitionDetail();
var competitor = getCompetitorDetail();
$("#competitor-info")
.text('"'+ competitor.name +'" in competition "'+ competition.name + '"')
.removeClass('alert-warning')
.addClass('alert-success');
$('#start-btn').removeClass('disabled');
$('#schedule-start-btn').removeClass('disabled');
};
var validateAccessCode = function(){
var accessCode = $('#access-code-input').val();
if(accessCode==""){
$('#access-code-errors')
.text('Access code can not be blank.')
.show();
return
}
var competitorId = tmp_competitor.id;
var url = getServerUrl() + 'api/competitor_token/obtain';
setStatus('Fetching competitor token');
$.ajax({
url: url,
data: {
competitor: competitorId,
access_code: accessCode
},
type: 'POST'
}).success(function(response){
setStatus('Competitor token fetched', 'success');
tmp_competitor.token = response.token;
setCompetitorDetail(tmp_competitor);
setCompetitionDetail(tmp_competition);
displayCompetitor();
$('#select-competitor-modal').modal('hide');
$('.select-competitor-pane').hide();
$('#select-competitor-pane-1').show();
}).error(function(){
setStatus('Failed to fetch competitor token', 'danger');
$('#access-code-errors')
.text('Access code did not match selected competitor')
.show();
});
};
var onPressStart = function(){
if(!isCompetitorSet()){
return;
}
console.log('start');
$('#streaming-modal').modal('show');
startStreaming();
};
var startStreaming = function(){
isGpsSwitchedOn = true;
watchPositionId = navigator.geolocation.watchPosition(
onPositionUpdate,
onPositionError,
{
enableHighAccuracy:true,
timeout:999,
maximumAge:1000
}
);
(function positionRequestor(){
navigator.geolocation.getCurrentPosition(
onPositionUpdate,
onPositionError,
{
enableHighAccuracy:true,
timeout:999,
maximumAge:1000
}
);
if(isGpsSwitchedOn){
setTimeout(positionRequestor, 1e3);
}
})();
(function positionArchivePusher(){
pushPositionArchive();
if(isGpsSwitchedOn){
setTimeout(positionArchivePusher, 1e3);
}
})();
};
var pushPositionArchive = function(force){
force = force || false;
// Push data when Archive max age == competition delay -5s
};
var stopStreaming = function(){
isGpsSwitchedOn = false;
pushPositionArchive(true);
navigator.geolocation.clearWatch(watchPositionId);
$('#streaming-modal').modal('hide');
}
var onPositionUpdate = function(position){
console.log(position)
}
var onPositionError = function(){
console.log("position erro")
}
$(function() {
$('.error-message').hide();
setInterval(fetchServerTime, 5*60*1e3);
fetchServerTime();
fetchCompetitionList();
$('#select-competitor-btn').on('click', function(){
$('.select-competitor-pane').hide();
$('#select-competitor-pane-1').show();
});
$('#back-pane-btn-1').on('click', function(){
$('.select-competitor-pane').hide();
$('#select-competitor-pane-1').show();
});
$('#back-pane-btn-2').on('click', function(){
$('.select-competitor-pane').hide();
$('.error-message').hide();
$('#select-competitor-pane-2').show();
});
$('#validate-access-code-btn').on('click', validateAccessCode);
$('#save-settings-btn').on('click', saveSettings);
$('#start-btn').on('click', onPressStart);
displayCompetitor();
$('#refresh-competition-list-btn').on('click', fetchCompetitionList);
$('#refresh-competitor-list-btn').on('click', fetchCompetitorList);
$('#stop-streaming-btn').on('click', stopStreaming);
});