--- /dev/null
+/* Get the table element. */
+function get_table() {
+ return document.getElementById('laps').getElementsByTagName('table')[0];
+}
+
+/* First time row in the table. */
+function first_row(table) {
+ /* First row is heading. */
+ return 1;
+}
+
+/* Last time row in the table. */
+function last_row(table) {
+ /* Last row is summary. */
+ for (var i = first_row(table); i < table.rows.length; i++) {
+ if (table.rows[i].cells[0].firstChild.tagName != 'P') return i - 1;
+ }
+ return table.rows.length - 2;
+}
+
+/* Row in the table with summaries. */
+function summary_row(table) {
+ return last_row(table) + 1;
+}
+
+/* Is this row a time row? */
+function valid_row(table, i, num_excluded) {
+ if (! num_excluded) num_excluded = 0;
+ /* First row is heading; last row is summary. */
+ return i >= first_row(table) && i <= last_row(table) - num_excluded;
+}
+
+/* First time column in the table. */
+function first_column(table) {
+ /* First column is station name; second is distance. */
+ return 2;
+}
+
+/* Last time column in the table. */
+function last_column(table) {
+ return table.rows[0].cells.length - 1;
+}
+
+/* Column in the table for leg distance. */
+function distance_column(table) {
+ return 1;
+}
+
+/* Is this column a time column? */
+function valid_column(table, j, num_excluded) {
+ if (! num_excluded) num_excluded = 0;
+ return j >= first_column(table) && j <= last_column(table) - num_excluded;
+}
+
+/* Does this element have a given class? */
+function has_class(element, c) {
+ for (var i = 0; i < element.classList.length; i++) {
+ if (element.classList[i] == c) return true;
+ }
+ return false;
+}
+
+/* Is this station the start line? */
+function start_line(table, i) {
+ return has_class(table.rows[i].cells[0], 'start_line');
+}
+
+/* Is this station the finish line? */
+function finish_line(table, i) {
+ return has_class(table.rows[i].cells[0], 'finish_line');
+}
+
+/* Get name of (station, lap) pair. */
+function get_cell_name(i, j) {
+ return 'lap' + String(j) + 'station' + String(i);
+}
+
+/* Get time by (station, lap) pair. */
+function get_cell_input(i, j) {
+ var table = get_table();
+ var row = i - first_row(table) + 1;
+ var column = j - first_column(table) + 1;
+ var id = get_cell_name(row, column);
+ var input = document.getElementById(id);
+ if (! input) console.log('Error: cell "' + id + '" not found in ' + Error().stack);
+ return input;
+}
+
+/* Extract an attribute encoded in element's class list. */
+function get_row_attribute_id(table, i, prefix) {
+ var regex = new RegExp('^' + prefix + '(\\d+)$');
+ var p = table.rows[i].cells[0].firstChild;
+ for (var j = 0; j < p.classList.length; j++) {
+ var m = p.classList[j].match(regex);
+ if (m) return parseInt(m[1]);
+ }
+ return null;
+}
+
+/* Get station ID of station. */
+function get_station_id(table, i) {
+ return get_row_attribute_id(table, i, 'station');
+}
+
+/* Get system ID of station; for distance calculation. */
+function get_system_id(table, i) {
+ return get_row_attribute_id(table, i, 'system');
+}
+
+/* Pad string. */
+function zero_pad(n) {
+ if (n < 10) return '0' + String(n);
+ else return String(n);
+}
+
+/* Convert a 21st century date to 34th century. */
+function elite_date(then) {
+ var now = new Date();
+ var year = String(now.getFullYear() + 1286);
+ var month = zero_pad(now.getMonth() + 1);
+ var day = zero_pad(now.getDate());
+ var date = (then) ? then : [year, month, day].join('-');
+ var hours = zero_pad(now.getHours());
+ var minutes = zero_pad(now.getMinutes());
+ var seconds = zero_pad(now.getSeconds());
+ var time = [hours, minutes, seconds].join(':');
+ return [date, time].join(' ');
+}
+
+/* Convert a 34th century date to 21st century. */
+function real_date(elite_date) {
+ var m = elite_date.match(/^(\d\d\d\d)(.*)/);
+ if (! m) return elite_date;
+ var elite_year = parseInt(m[1]);
+ if (elite_year < 3300) return elite_date;
+ return String(elite_year - 1286) + m[2];
+}
+
+/* Convert a date to timestamp. */
+function from_iso8601(date) {
+ var m = date.match(/^(\d\d\d\d)-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)$/);
+ if (! m) return 0;
+ return (new Date(parseInt(m[1]), parseInt(m[2]) - 1, parseInt(m[3]), parseInt(m[4]), parseInt(m[5]), parseInt(m[6])).getTime()) / 1000;
+}
+
+/* Convert a lap time to UNIX timestamp. */
+function parse_laptime(time) {
+ var m = time.match(/^(?:(\d+)\+)?(\d\d):(\d\d):(\d\d)$/);
+ if (! m) return 0;
+ var timestamp = parseInt(m[2]) * 3600 + parseInt(m[3]) * 60 + parseInt(m[4]);
+ if (m[1]) timestamp += parseInt(m[1]) * 86400;
+ return timestamp;
+}
+
+/* Format a time as HH:MM:SS. */
+function format_time(time) {
+ var formatted = '';
+
+ var days = Math.floor(time / 86400);
+ if (days) formatted += String(days) + '+';
+ time -= days * 86400;
+
+ var hours = Math.floor(time / 3600);
+ formatted += zero_pad(hours);
+ time -= hours * 3600;
+ formatted += ':';
+
+ var minutes = Math.floor(time / 60);
+ formatted += zero_pad(minutes);
+ time -= minutes * 60;
+ formatted += ':';
+
+ var seconds = Math.floor(time);
+ formatted += zero_pad(seconds);
+
+ return formatted;
+}
+
+/* Format the difference between two times. */
+function format_difference(time1, time2) {
+ if (! time1 || ! time2) return null;
+ var time = Math.max(time1, time2) - Math.min(time1, time2);
+ return format_time(time);
+}
+
+/* Calculate speed. */
+function speed(distance, time) {
+ /* Ly/h */
+ var speed = (distance / time) * 3600;
+ return speed;
+}
+
+/* Format a distance. */
+function format_distance(distance, suffix) {
+ if (suffix !== false) suffix = 'Ly';
+ return String(distance.toFixed(2)) + suffix;
+}
+
+/* Format a speed. */
+function format_speed(speed, suffix) {
+ if (suffix !== false) suffix = '/h';
+ return format_distance(speed) + suffix;
+}
+
+/* Calculate bonus time. */
+function calculate_bonus(laps) {
+ var time = 0;
+ var hull_start = 0;
+ var hull_end = 0;
+ var last_hull = 100;
+ var repaired = false;
+ for (var lap = 1; lap <= laps; lap++) {
+ var input = document.getElementById('lap' + String(lap) + 'hullstart');
+ if (input.value.match(input.pattern)) {
+ hull_start = parseInt(input.value);
+ if (hull_start > last_hull) repaired = true;
+ }
+ input = document.getElementById('lap' + String(lap) + 'hullend');
+ if (input.value.match(input.pattern)) {
+ hull_end = parseInt(input.value);
+ if (hull_end > last_hull) repaired = true;
+ last_hull = hull_end;
+ if (hull_end > hull_start) repaired = true;
+ else time += Math.floor(hull_end / 2);
+ }
+ }
+ /* Double bonus if no repairs at all. */
+ if (! repaired) time *= 2;
+ return time;
+}
+
+/* Calculate penalty time. */
+function calculate_penalty(allotted_time, bonus_time, raced_time) {
+ if (! allotted_time) return 0;
+ var time = allotted_time + bonus_time;
+ if (raced_time < time) {
+ /* Cut short. */
+ return time - raced_time;
+ }
+ else return raced_time - time;
+}
+
+/* Calculate total time. */
+function calculate_total(allotted_time, bonus_time, penalty_time, raced_time) {
+ if (raced_time > allotted_time) return allotted_time + bonus_time + penalty_time;
+ /* Cut short. */
+ return allotted_time + bonus_time;
+}
+
+/*
+ Update placeholder time based on current date, so subsequent entries can be added quickly.
+*/
+function propagate_placeholder(table, date) {
+ for (var i = first_row(table); valid_row(table, i); i++) {
+ for (var j = first_column(table); valid_column(table, j); j++) {
+ var input = get_cell_input(i, j);
+ if (input) input.placeholder = elite_date(date);
+ }
+ }
+}
+
+/* Update point-to-point distance array. */
+function update_raikogram(table) {
+ var n = 1;
+ var lap_distance = 0.0;
+ for (var i = first_row(table); valid_row(table, i); i++) {
+ var cell = table.rows[i].cells[distance_column(table)];
+ cell.id = 'distance' + String(n);
+ if (n++ < 2) continue;
+ var first = get_system_id(table, i - 1);
+ var second = get_system_id(table, i);
+ if (! first || ! second) continue;
+ var system1 = Math.min(first, second);
+ var system2 = Math.max(first, second);
+ var distance = distances['from' + system1 + 'to' + system2];
+ if (! distance) continue;
+ lap_distance += distance;
+ cell.firstChild.innerHTML = format_distance(distance);
+ }
+ /* Total. */
+ var text = (lap_distance > 0) ? format_distance(lap_distance) : '';
+ table.rows[summary_row(table)].cells[distance_column(table)].firstChild.innerHTML = text;
+}
+
+/* Exchange cells. */
+function swap_cells(table, i1, j1, i2, j2, deleting) {
+ var row1 = table.rows[i1];
+ var row2 = table.rows[i2];
+ row1.cells[j1].insertBefore(row2.cells[j2].firstChild, row1.cells[j1].firstChild);
+ row2.cells[j2].appendChild(row1.cells[j1].children[1]);
+ set_ids(table, Math.min(j1, j2));
+ if (deleting) return;
+ /* Fire update hook unless we're deleting the second cell. */
+ changed_cell(table, i1, j1);
+ changed_cell(table, i2, j2);
+}
+
+/* Reorder station visit so times are sequential across laps. */
+function reorder_row(table, i) {
+ var changed = false;
+ for (var j = first_column(table); valid_column(table, j, 1); j++) {
+ var left = get_cell_input(i, j).value || 'zzz';
+ var right = get_cell_input(i, j + 1).value || 'zzz';
+ if (left == right) continue;
+ if (left < right) continue;
+ swap_cells(table, i, j, i, j + 1);
+ changed = true;
+ j = 0;
+ }
+
+ return changed;
+}
+
+/* Reorder a lap so station visit times are sequential. */
+function reorder_column(table, j) {
+ var changed = false;
+ for (var i = first_row(table); valid_row(table, i, 1); i++) {
+ if (start_line(table, i) || finish_line(table, i)) continue;
+ if (start_line(table, i + 1) || finish_line(table, i + 1)) continue;
+ var above = get_cell_input(i, j).value || 'zzz';
+ var below = get_cell_input(i + 1, j).value || 'zzz';
+ if (above == below) continue;
+ if (above < below) continue;
+ for (var k = 0; k < table.rows[i].cells.length; k++) swap_cells(table, i, k, i + 1, k, true)
+ changed = true;
+ i = first_row(table);
+ }
+ if (changed) update_raikogram(table);
+ return changed;
+}
+
+/* Update cell IDs after row or column reordering. */
+function set_ids(table, j) {
+ j = Math.max(j, first_column(table));
+ for (var i = first_row(table); valid_row(table, i); i++) {
+ /* Don't use valid_column() because we may be removing a column. */
+ for (var k = j; k < table.rows[i].cells.length; k++) {
+ var input = table.rows[i].cells[k].firstChild;
+ var row = i - first_row(table) + 1;
+ var column = k - first_column(table) + 1;
+ input.id = input.name = get_cell_name(row, column);
+ }
+ }
+ var row = table.rows[last_row(table) + 1];
+ for (var k = j; k < row.cells.length; k++) {
+ var lap = k - first_column(table) + 1;
+ var p = row.cells[k].firstChild;
+ var summary = p.getElementsByTagName('span')[0];
+ summary.id = summary.name = 'lap' + String(lap) + 'summary';
+ var hull = p.getElementsByTagName('input')[0];
+ hull.id = hull.name = 'lap' + String(lap) + 'hullstart';
+ hull = p.getElementsByTagName('input')[1];
+ hull.id = hull.name = 'lap' + String(lap) + 'hullend';
+ }
+}
+
+/* Update hidden field and visible field. */
+function do_stat(id, value, visible_value) {
+ if (typeof value == 'undefined') value = '';
+ if (typeof visible_value == 'undefined') visible_value = String(value);
+ console.log([id, visible_value].join(': '));
+ document.getElementsByName('stat_' + id)[0].value = value;
+ document.getElementById(id).innerHTML = visible_value;
+}
+
+/* Update entry stats. */
+function do_stats(table) {
+ var last_lap = first_column(table);
+ var last_station = last_row(table);
+ var complete_laps = 0;
+ /* Find the last lap and last station visited. */
+ for (var j = first_column(table); valid_column(table, j, 1); j++) {
+ for (var i = last_row(table); valid_row(table, i); i--) {
+ var input = get_cell_input(i, j);
+ if (input.value.match(input.pattern)) {
+ last_lap = j;
+ if (i == last_row(table)) complete_laps++;
+ break;
+ }
+ }
+ }
+ for (var i = last_station; valid_row(table, i); i--) {
+ var input = get_cell_input(i, last_lap);
+ if (input.value.match(input.pattern)) {
+ last_station = i;
+ break;
+ }
+ }
+ /* Find total distance. */
+ var lap_distance = parseFloat(table.rows[summary_row(table)].cells[distance_column(table)].firstChild.innerHTML.replace(/Ly$/, ''));
+ var total_distance = lap_distance * complete_laps;
+ var input = get_cell_input(last_row(table), last_lap);
+ if (! input.value.match(input.placeholder)) {
+ /* Last lap incomplete. */
+ for (var i = last_station; valid_row(table, i, 1); i--) {
+ var cell = document.getElementById('distance' + String(i - first_row(table) + 1));
+ var text = cell.firstChild.innerHTML.replace(/Ly$/, '');
+ if (! text) continue;
+ var distance = parseFloat(text);
+ total_distance += distance;
+ }
+ }
+
+ var start_time = from_iso8601(real_date(get_cell_input(first_row(table), first_column(table)).value));
+ var finish_time = from_iso8601(real_date(get_cell_input(last_station, last_lap).value));
+ var raced_time = finish_time - start_time;
+ var bonus_time = calculate_bonus(complete_laps);
+ var penalty_time = calculate_penalty(allotted_time, bonus_time, raced_time);
+ var total_time = calculate_total(allotted_time, bonus_time, penalty_time, raced_time);
+
+ /* Lap distance. */
+ do_stat('lap_distance', lap_distance, String(lap_distance) + 'Ly');
+ /* Complete laps. */
+ do_stat('complete_laps', complete_laps);
+ /* Total distance. */
+ do_stat('total_distance', format_distance(total_distance, false), total_distance ? format_distance(total_distance) : '');
+ /* Times. */
+ do_stat('allotted_time', allotted_time, format_time(allotted_time));
+ do_stat('bonus_time', bonus_time, format_time(bonus_time));
+ do_stat('penalty_time', penalty_time, format_time(penalty_time));
+ do_stat('raced_time', finish_time - start_time, format_difference(finish_time, start_time));
+ do_stat('total_time', total_time, format_time(total_time));
+ if (complete_laps) {
+ /* Fastest lap. */
+ var fastest_lap = null;
+ for (var j = 1; j <= complete_laps; j++) {
+ var laptime = parse_laptime(document.getElementById('lap' + String(j) + 'summary').innerHTML);
+ if (fastest_lap == null || fastest_lap > laptime) fastest_lap = laptime;
+ }
+ do_stat('fastest_lap', fastest_lap, format_time(fastest_lap));
+ /* Average lap. */
+ var average_lap = Math.floor(total_time / complete_laps);
+ do_stat('average_lap', average_lap, format_time(average_lap));
+ /* Best speed. */
+ var best_speed = speed(lap_distance, fastest_lap);
+ do_stat('best_speed', format_speed(best_speed, false), format_speed(best_speed));
+ /* Average speed. */
+ var average_speed = speed(total_distance, total_time);
+ do_stat('average_speed', format_speed(average_speed, false), format_speed(average_speed));
+ }
+ else {
+ do_stat('fastest_lap');
+ do_stat('average_lap');
+ do_stat('best_speed');
+ do_stat('average_speed');
+ }
+}
+
+/* Check entry validity. */
+function validate(table, cloned) {
+ var valid = true;
+ for (var i = first_row(table); valid_row(table, i); i++) {
+ for (var j = first_column(table); valid_column(table, j); j++) {
+ var input = get_cell_input(i, j);
+ if (input) input.setCustomValidity("");
+ }
+ }
+
+ var rows = last_row(table) - first_row(table) + 1;
+ var columns = table.rows[0].cells.length;
+ var last_time = 0;
+ for (var j = first_column(table); valid_column(table, j); j++) {
+//for (var c = first_column(table); valid_column(table, c); c++) {
+// var j = c - first_column(table) + 1;
+ var lap = j - first_column(table) + 1;
+ var first_leg_time = 0;
+ var last_leg_time = 0;
+ var summary = document.getElementById('lap' + String(lap) + 'summary');
+ summary.innerHTML = null;
+ var no_stations_visited = true;
+ var all_stations_visited = true;
+ /* Ensure station times are sequential. */
+ for (var i = first_row(table); valid_row(table, i); i++) {
+ var input = get_cell_input(i, j);
+ var text = input.value;
+ if (text.match(input.pattern)) {
+ input.setCustomValidity("");
+ no_stations_visited = false;
+ var time = from_iso8601(real_date(text));
+ if (i == first_row(table)) first_leg_time = time;
+ else last_leg_time = time;
+ if (time <= last_time) {
+ if (j == first_column(table) && i > first_row(table) && i < last_row(table)) {
+ /* Revalidate if lap was reordered. */
+ if (reorder_column(table, j)) return validate(table);
+ }
+ if (time < last_time || i > first_row(table)) {
+ input.setCustomValidity('Times out of sequence');
+ valid = false;
+ }
+ }
+ last_time = time;
+ }
+ else all_stations_visited = false;
+ }
+ if (! valid) break;
+ if (j >= first_column(table) && j < last_column(table) - 1) {
+ /* Intermediate laps must be complete. */
+ if (! all_stations_visited) {
+ valid = false;
+ for (var i = first_row(table); valid_row(table, i); i++) {
+ var input = get_cell_input(i, j);
+ if (input.value.match(input.pattern)) continue;
+ input.setCustomValidity('Lap ' + String(lap) + ' incomplete');
+ break;
+ }
+ }
+ }
+ else {
+ if (j == first_column(table)) {
+ /* First lap must have stations. */
+ if (no_stations_visited) {
+ valid = false;
+ var input = get_cell_input(1, j);
+ input.setCustomValidity('Race not started');
+ }
+ }
+ }
+ if (! valid) break;
+ if (! no_stations_visited) {
+ /* Incomplete laps are valid if visited stations are in order. */
+ var last = last_row(table) + 1;
+ for (var i = last_row(table); valid_row(table, i); i--) {
+ var input = get_cell_input(i, j);
+ if (! input.value.match(input.pattern)) continue;
+ last = i;
+ break;
+ }
+ for (var i = first_row(table); i < last; i++) {
+ var input = get_cell_input(i, j);
+ if (input.value.match(input.pattern)) continue;
+ valid = false;
+ /* First lap (only) can be reordered. */
+ if (j == first_column(table) && i > first_row(table) && i < last_row(table)) {
+ /* Revalidate if lap was reordered. */
+ if (reorder_column(table, j)) return validate(table);
+ }
+ input.setCustomValidity('Final lap ' + String(lap) + ' incomplete');
+ break;
+ }
+ }
+ if (! valid) break;
+ summary.innerHTML = format_difference(last_leg_time, first_leg_time);
+ if (! no_stations_visited && ! summary.innerHTML) summary.innerHTML = '--:--:--';
+ }
+
+ if (valid) {
+ /* Remember station ordering. */
+ var station_orders = document.getElementsByName('station_order');
+ if (station_orders.length) {
+ var stations = [];
+ for (var i = first_row(table); valid_row(table, i); i++) {
+ var station_id = get_station_id(table, i);
+ if (station_id) stations.push(station_id);
+ }
+ station_orders[0].value = JSON.stringify(stations);
+ }
+ do_stats(table);
+ }
+ else {
+ /* Revalidate if row is reordered. */
+ for (var i = first_row(table); valid_row(table, i); i++) {
+ if (i == cloned) continue;
+ if (reorder_row(table, i)) return validate(table);
+ }
+ }
+
+ var submits = document.getElementsByName('add_entry');
+ if (submits.length) submits[0].disabled = ! valid;
+}
+
+/* A cell was changed. */
+function changed_cell(table, row, column, input) {
+ if (! input) input = get_cell_input(row, column);
+ var text = input.value;
+ if (text && text.length < input.placeholder.length) {
+ /* Pad remainder with placeholder. */
+ var placeheld = input.placeholder.substr(0, input.placeholder.length - text.length) + text;
+ if (placeheld.match(input.pattern)) input.value = text = placeheld;
+ }
+ var m = text.match(input.pattern);
+ if (m) {
+ var date = m[1];
+ /* Add a lap if this was the last column. */
+ if (column == last_column(table) - first_column(table) + 1) add_lap(table, m[1]);
+ }
+ var cloned = null;
+ if (m || ! text) {
+ /*
+ Docking time of previous lap and launch time of next lap default to
+ being the same.
+ */
+ if (row == 1 && column > 1 && column < last_column(table) - first_column(table)) {
+ /* Set docking time to launch time. */
+ cloned = last_row(table);
+ var replaced = table.rows[last_row(table)].cells[first_column(table) + column - 2].firstChild;
+ if (! replaced.value != ! text) replaced.value = text;
+ }
+ else if (row == last_row(table) - first_row(table) + 1) {
+ /* Set launch time to docking time. */
+ cloned = first_row(table);
+ var replaced = table.rows[first_row(table)].cells[first_column(table) + column].firstChild;
+ if (! replaced.value && text) replaced.value = text;
+ }
+ }
+ delete_empty_laps(table);
+ validate(table, cloned);
+}
+
+/* Callback when an input element was changed. */
+function changed(event) {
+ var input = event.target;
+ var m = input.id.match(/^lap(\d+)station(\d+)$/);
+ if (! m) return;
+ var table = get_table();
+ return changed_cell(table, parseInt(m[2]), parseInt(m[1]), input);
+}
+
+/* A hull percentage was changed. */
+function changed_hull_percentage(table, lap, suffix, input) {
+ var text = input.value;
+ var m = text.match(input.pattern);
+ if (text) {
+ if (! m) return;
+ if (suffix == 'end') {
+ var next_lap = lap + 1;
+ var next_input = document.getElementById('lap' + String(next_lap) + 'hullstart');
+ /* Set hull at start of next lap to value at end of this. */
+ if (next_input && ! next_input.value) next_input.value = text;
+ }
+ }
+ return validate(table);
+}
+
+/* Callback when a hull percentage was changed. */
+function changed_hull(event) {
+ var input = event.target;
+ var m = input.id.match(/^lap(\d+)hull(start|end)$/);
+ if (! m) return;
+ var table = get_table();
+ return changed_hull_percentage(table, parseInt(m[1]), m[2], input);
+}
+
+/* Create a new input element. */
+function checkpoint(id) {
+ var date = elite_date();
+ var input = document.createElement('input');
+ input.autocomplete = 'off';
+ input.type = 'text';
+ input.pattern = '^(33[0-9][0-9]-(?:0[1-9]|1[0-2])-(?:[0-2][0-9]|3[01])) ((?:[01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9])$';
+ input.maxLength = input.size = 19;
+ input.id = input.name = id;
+ input.tabIndex = parseInt(id.replace(/^lap(\d+)station\d+$/, function(ignored, ret) { return ret; }));
+ input.onchange = changed;
+ return input;
+}
+
+/* Create a hull percentage element. */
+function hull(lap, suffix) {
+ var input = document.createElement('input');
+ input.autocomplete = 'off';
+ input.type = 'text';
+ input.placeholder = 'Hull';
+ input.pattern = '^(?:0|100|[1-9][0-9]?)$';
+ input.maxLength = input.size = 3;
+ input.id = input.name = 'lap' + String(lap) + 'hull' + suffix;
+ input.onchange = changed_hull;
+ return input;
+}
+
+/* Create an element to display lap summary. */
+function summary(lap) {
+ var span = document.createElement('span');
+ span.id = 'lap' + String(lap) + 'summary';
+ span.style.fontFamily = 'monospace';
+ return span;
+}
+
+/* Add a new empty lap. */
+function add_lap(table, date) {
+ var lap = table.rows[0].cells.length - first_column(table) + 1;
+ var th = document.createElement('th');
+ th.appendChild(document.createTextNode('Lap ' + String(lap)));
+ table.rows[0].appendChild(th);
+ for (var i = first_row(table); valid_row(table, i); i++) {
+ var td = document.createElement('td');
+ td.appendChild(checkpoint(get_cell_name(i, lap)));
+ table.rows[i].appendChild(td);
+ }
+ var td = document.createElement('td');
+ var p = document.createElement('p');
+ p.appendChild(hull(lap, 'start'));
+ p.appendChild(hull(lap, 'end'));
+ p.appendChild(summary(lap));
+ td.appendChild(p);
+ table.rows[last_row(table) + 1].appendChild(td);
+ propagate_placeholder(table, date);
+}
+
+/* Delete empty laps other than the final one. */
+function delete_empty_laps(table) {
+ var columns = table.rows[0].cells.length;
+ for (var j = last_column(table) - 1; valid_column(table, j); j--) {
+ var empty = true;
+ for (var i = first_row(table); valid_row(table, i); i++) {
+ var input = get_cell_input(i, j);
+ if (! input) continue;
+ if (input.value.match(input.pattern)) {
+ empty = false;
+ break;
+ }
+ }
+ if (! empty) continue;
+ /* Include summary. */
+ for (var i = first_row(table); i <= summary_row(table); i++) {
+ /* Shift subsequent laps back. */
+ var row = table.rows[i];
+ swap_cells(table, i, j, i, j + 1, true);
+ row.removeChild(row.cells[j + 1]);
+ }
+ /* Remove header. */
+ table.rows[0].removeChild(table.rows[0].cells[columns - 1]);
+ /* Remove summary. */
+ //table.rows[table.rows.length - 1].removeChild(table.rows[table.rows.length - 1].cells[columns - 1]);
+ //table.rows[summary_row(table)].removeChild(table.rows[summary_row(table)].cells[columns - 1]);
+ }
+}
+
+/* Document onload. */
+function loaded() {
+ var table = get_table();
+ add_lap(table);
+ if (typeof posted != 'undefined') {
+ /* Times were submitted. */
+ var station_order = JSON.parse(posted['station_order']);
+ if (station_order) {
+ /* Stations were rearranged. */
+ for (var i = station_order.length - 1; i >= 0; i--) {
+ row = i + 1;
+ var actual = get_station_id(table, row);
+ if (actual == station_order[i]) continue;
+ for (var other_row = first_row(table); valid_row(table, other_row); other_row++) {
+ var other = get_station_id(table, other_row);
+ if (other != station_order[i]) continue;
+ for (var k = 0; k < table.rows[row].cells.length; k++) swap_cells(table, row, k, other_row, k, true)
+ i = station_order.length - 1;
+ break;
+ }
+ }
+ set_ids(table, first_column(table));
+ }
+ Object.keys(posted).forEach(function(id, index) {
+ if (! id) return;
+ if (! posted[id]) return;
+ var m = id.match(/^lap(\d+)station(\d+)$/);
+ if (m) {
+ /* Insert times. */
+ var input = document.getElementById(id);
+ input.value = posted[id];
+ changed_cell(table, parseInt(m[2]), parseInt(m[1]), input);
+ return;
+ }
+ m = id.match(/^lap(\d+)hull(?:start|end)/);
+ if (m) {
+ /* Insert hull. */
+ var input = document.getElementById(id);
+ input.value = posted[id];
+ return;
+ }
+ });
+ }
+ if (typeof distances != 'undefined') update_raikogram(table);
+ validate(table);
+ var stats = document.getElementById('stats').getElementsByTagName('table')[0];
+ stats.rows[0].cells[0].style.width = String(table.rows[0].cells[0].offsetWidth) + 'px';
+}
+
+document.onload = loaded();
--- /dev/null
+<?php
+ date_default_timezone_set("UTC");
+ $propel_project = "buckyball";
+ $www_root = "members/furrycat/buckyball";
+ $root = join(DIRECTORY_SEPARATOR, array($_SERVER['DOCUMENT_ROOT'], $www_root));
+ $propel_root = join(DIRECTORY_SEPARATOR, array($root, "propel"));
+ $lib_root = join(DIRECTORY_SEPARATOR, array($root, "lib"));
+ require_once(join(DIRECTORY_SEPARATOR, array($root, "vendor", "autoload.php")));
+
+ Propel::init("$propel_root/build/conf/$propel_project-conf.php");
+ set_include_path(join(PATH_SEPARATOR, array(join(DIRECTORY_SEPARATOR, array($propel_root, "build", "classes")), get_include_path())));
+
+ include_once(join(DIRECTORY_SEPARATOR, array($lib_root, "include.php")));
+
+ parse_str($_SERVER['QUERY_STRING'], $params);
+ $module = $params['module'];
+ $action = $params['action'];
+
+ include_once(join(DIRECTORY_SEPARATOR, array($lib_root, "header.php")));
+ if ($module) {
+ $include = join(DIRECTORY_SEPARATOR, array($lib_root, "$module.php"));
+ if (file_exists($include)) {
+ include_once($include);
+ call_user_func("module_$module", $action);
+ }
+ else echo "<p>What's all that about, then?</p>\n";
+ }
+ include_once(join(DIRECTORY_SEPARATOR, array($lib_root, "footer.php")));
+?>
--- /dev/null
+Order deny,allow
+Deny from all
--- /dev/null
+<h2>Competitors</h2>
+<?php
+
+ function add_competitor($cmdr_name, $forum_name) {
+ if (! $cmdr_name) {
+ echo "<p>Missing CMDR name!</p>\n";
+ return false;
+ }
+ $competitor = new Competitor;
+ $competitor->setCmdrName($cmdr_name);
+ if ($forum_name && $forum_name != $cmdr_name) $competitor->setForumName($forum_name);
+ try {
+ $competitor->save();
+ return true;
+ }
+ catch (Exception $e) {
+ echo "<p>Error adding competitor: " . $e->getMessage() . "</p>\n";
+ }
+ return false;
+ }
+
+ function module_competitor($action) {
+ if ($_POST['add_competitor']) {
+ if (add_competitor($_POST["cmdr_name"], $_POST["forum_name"])) unset($_POST);
+ }
+
+ $q = new CompetitorQuery;
+ $competitors = $q->orderByCmdrName()->find();
+ if (! count($competitors)) echo "<p>No competitors</p>\n";
+ foreach ($competitors as $competitor) {
+ $cmdr = $competitor->getCmdrName();
+ echo "<p>CMDR <strong>$cmdr</strong>";
+ $forum = $competitor->getForumName();
+ if ($forum && $forum != $cmdr) echo " ($forum)";
+ "</p>\n";
+ }
+
+ echo "<hr>\n";
+ form();
+ echo "<p>Add a new competitor: CMDR ";
+ input("cmdr_name", $_POST['cmdr_name']);
+ echo " forum: ";
+ input("forum_name", $_POST['forum_name']);
+ submit("add_competitor", "Add");
+ echo "</p>\n";
+ end_form();
+ }
+?>
--- /dev/null
+<?php
+
+ function elite_time($time) {
+ if (! preg_match('/^(\d\d\d\d)(.+)$/', $time, $m)) return null;
+ list($ignored, $year, $remainder) = $m;
+ if ($year >= 3300) return $time;
+ return sprintf("%04d%s", $year + 1286, $remainder);
+ }
+
+ function real_time($time) {
+ if (! preg_match('/^(\d\d\d\d)(.+)$/', $time, $m)) return null;
+ list($ignored, $year, $remainder) = $m;
+ if ($year < 3300) return $time;
+ return sprintf("%04d%s", $year - 1286, $remainder);
+ }
+
+ function format_time($time) {
+ if (! preg_match('/^\d+$/', $time)) return "";
+
+ $ret = "";
+ $days = floor($time / 86400);
+ if ($days) {
+ $ret .= sprintf("%d+", $days);
+ $time -= $days * 86400;
+ }
+
+ $hours = floor($time / 3600);
+ $time -= $hours * 3600;
+
+ $minutes = floor($time / 60);
+ $time -= $minutes * 60;
+
+ $seconds = $time;
+ $ret .= sprintf("%02d:%02d:%02d", $hours, $minutes, $seconds);
+
+ return $ret;
+ }
+
+ function format_distance($distance) {
+ return round($distance, 2);
+ }
+
+ function format_speed($speed) {
+ return round($speed, 2);
+ }
+
+?>
--- /dev/null
+<?php
+
+ $ENDORSEMENTS = array(
+ 'c' => array('entry cut short', 1 << 0),
+ 'd' => array('docking computer', 1 << 1),
+ 'j' => array('joker used', 1 << 2),
+ 'x' => array('excluded from ranking', 1 << 3),
+ );
+
+ function endorsement_keys() {
+ return array_keys($GLOBALS['ENDORSEMENTS']);
+ }
+
+ function endorsement($e, $endorsements = ~0) {
+ return $endorsements & $GLOBALS['ENDORSEMENTS'][$e][1];
+ }
+
+ function endorsement_description($e) {
+ return $GLOBALS['ENDORSEMENTS'][$e][0];
+ }
+
+ function endorsement_string($endorsements) {
+ $ret = "";
+ foreach (endorsement_keys() as $e) {
+ if (endorsement($e, $endorsements)) $ret .= $e;
+ }
+ return $ret;
+ }
+
+?>
--- /dev/null
+<h2>Entries</h2>
+<?php
+
+ function retrieve_entry($entry) {
+ $_POST['add_entry'] = 'retrieve_entry';
+ $station_order = array();
+ $allotted_time = $entry->getRace()->getAllottedTime();
+ foreach ($entry->getLaps() as $lap) {
+ $lap_number = $lap->getLapNumber();
+ if ($allotted_time && $lap->getHullStart() && $lap->getHullEnd()) {
+ $_POST["lap${lap_number}hullstart"] = $lap->getHullStart();
+ $_POST["lap${lap_number}hullend"] = $lap->getHullEnd();
+ }
+ foreach ($lap->getLaptimes() as $laptime) {
+ $station_id = $laptime->getStationId();
+ $station_number = $laptime->getStationOrder();
+ if ($lap_number == 1) $station_order[] = $station_id;
+ $_POST["lap${lap_number}station${station_number}"] = elite_time($laptime->getArrival());
+ }
+ }
+ foreach (endorsement_keys() as $e) {
+ if (endorsement($e, $entry->getEndorsements())) $_POST["endorsement_$e"] = true;
+ }
+ $_POST['station_order'] = json_encode($station_order);
+ }
+
+ function do_entry($race_id, $ship_id, $posted, $allotted_time) {
+ $dbh = Propel::getConnection(LapPeer::DATABASE_NAME);
+ $dbh->beginTransaction();
+
+ $station_order = json_decode($posted['station_order']);
+
+ $endorsements = 0;
+ foreach (array_keys($GLOBALS['ENDORSEMENTS']) as $e) {
+ if ($posted["endorsement_$e"]) $endorsements |= endorsement($e);
+ }
+
+ $entry = new Entry;
+ $entry->setRaceId($race_id);
+ $entry->setShipId($ship_id);
+ $entry->setLapDistance($posted['stat_lap_distance']);
+ $entry->setCompleteLaps($posted['stat_complete_laps']);
+ $entry->setTotalDistance($posted['stat_total_distance']);
+ if ($allotted_time > 0) {
+ $entry->setPenaltyTime($posted['stat_penalty_time']);
+ $entry->setBonusTime($posted['stat_bonus_time']);
+ if ($posted['stat_raced_time'] < $allotted_time) $endorsements |= endorsement('c');
+ if ($posted['stat_bonus_time']) $endorsements |= endorsement('j');
+ }
+ $entry->setRacedTime($posted['stat_raced_time']);
+ $entry->setTotalTime($posted['stat_total_time']);
+ $entry->setFastestLap($posted['stat_fastest_lap']);
+ $entry->setAverageLap($posted['stat_average_lap']);
+ $entry->setBestSpeed($posted['stat_best_speed']);
+ $entry->setAverageSpeed($posted['stat_average_speed']);
+ $entry->setEndorsements($endorsements);
+ $entry->save();
+
+ $lap_number = 1;
+ while (array_key_exists("lap${lap_number}station2", $posted)) {
+ $lap = new Lap;
+ $lap->setEntryId($entry->getId());
+ $lap->setLapNumber($lap_number);
+ if ($posted["lap${lap_number}hullstart"] && $posted["lap${lap_number}hullend"]) {
+ $lap->setHullStart($posted["lap${lap_number}hullstart"]);
+ $lap->setHullEnd($posted["lap${lap_number}hullend"]);
+ }
+ $lap->save();
+ $station_number = 1;
+ while (array_key_exists("lap${lap_number}station${station_number}", $posted)) {
+ $station_id = $station_order[$station_number - 1];
+ $arrival = real_time($posted["lap${lap_number}station${station_number}"]);
+ $laptime = new Laptime;
+ $laptime->setLapId($lap->getId());
+ $laptime->setStationOrder($station_number);
+ $laptime->setStationId($station_id);
+ $laptime->setArrival($arrival);
+ $laptime->save();
+ $station_number++;
+ }
+ $lap_number++;
+ }
+
+ $dbh->commit();
+
+ return true;
+ }
+
+ function add_entry($race_id, $ship_id, $entry) {
+ $q = new CourseQuery;
+ $q->joinWith("Course.Station");
+ $q->joinWith("Course.Race");
+ $q->joinWith("Station.System");
+ $q->findByRaceId($race_id);
+ $course = $q->find();
+ $allotted_time = 0 + $course[0]->getRace()->getAllottedTime();
+ $legs = array();
+ $system_ids = array();
+ $station_order = array();
+ $system_order = array();
+ foreach ($course as $leg) {
+ $station = $leg->getStation();
+ $station_name = $station->getName();
+ $system = $station->getSystem();
+ $system_id = $system->getId();
+ $station_order[] = $system_id;
+ $system_ids[] = $system_id;
+ $system_order[] = $system_id;
+ $flags = 0;
+ if ($leg->getStartLine()) $flags |= 2;
+ else if (! $leg->getFinishLine()) $flags |= 1;
+ $legs[$station_name] = array($flags, $station->getId(), $system_id);
+ }
+ arsort($legs);
+ $system_ids = array_unique($system_ids);
+ $rq = new RaikogramQuery;
+ $rq->filterBySystem1($system_ids);
+ $rq->filterBySystem2($system_ids);
+ $raikograms = $rq->find();
+ $distances = array();
+ foreach ($raikograms as $raikogram) {
+ $system1 = $raikogram->getSystem1();
+ $system2 = $raikogram->getSystem2();
+ $distance = $raikogram->getDistance();
+ $distances["from${system1}to${system2}"] = $distance;
+ }
+ echo "<script type=\"text/javascript\"><!--\n";
+ echo "var distances = " . json_encode($distances) . ";\n";
+ echo "var allotted_time = $allotted_time;\n";
+ if ($entry) retrieve_entry($entry);
+ if ($_POST['lap1station1']) {
+ /*
+ XXX: Trust client validation only because furrycat is the only user
+ and he can vouch for himself.
+ */
+ if ($_POST['station_order']) $encoded_station_order = $_POST['station_order'];
+ else $encoded_station_order = json_encode($station_order);
+ $posted = array('station_order' => $encoded_station_order);
+ foreach ($_POST as $k => $v) {
+ if (! preg_match('/^(?:lap\d+(?:hull|station\d+$)|endorsement_|stat_)/', $k, $m)) continue;
+ if (! $v) continue;
+ $posted[$k] = $v;
+ }
+ echo "var posted = " . json_encode($posted) . ";\n";
+ echo "//--></script>\n";
+ if ($entry) {
+ $entry_id = $entry->getId();
+ $ship_name = $entry->getShip()->getName();
+ $cmdr_name = $entry->getShip()->getCompetitor()->getCmdrName();
+ echo "<p>Entry $entry_id: CMDR <strong>$cmdr_name</strong> flying <strong>$ship_name</strong></p>\n";
+ }
+ else if (do_entry($race_id, $ship_id, $posted, $allotted_time)) return true;
+ }
+ else echo "//--></script>\n";
+ if (! $entry) {
+ $sq = new ShipQuery;
+ $sq->joinWith('Ship.Competitor');
+ $ship = $sq->findOneById($ship_id);
+ $cmdr_name = $ship->getCompetitor()->getCmdrName();
+ $ship_name = $ship->getName();
+ echo "<p>New entry: CMDR <strong>$cmdr_name</strong> flying <strong>$ship_name</strong></p>\n";
+ }
+ form();
+ echo "<div id=\"laps\">\n";
+ echo "<table>\n";
+ echo "<th>Station</th>\n";
+ echo "<th>Leg</th>\n";
+ $n = 0;
+ foreach ($legs as $station => $attributes) {
+ list($flags, $id, $system_id) = $attributes;
+ $n++;
+ $label = $station;
+ $class = ($flags & 2) ? ' class="start_line"' : '';
+ echo "<tr>\n";
+ echo "<td$class><p class=\"station$id system$system_id\">$station</p></td>\n";
+ echo "<td id=\"distance$n\"><p></p></td>\n";
+ echo "</tr>\n";
+ }
+ foreach ($legs as $station => $attributes) {
+ list($flags, $id, $system_id) = $attributes;
+ if ($flags & 1) continue;
+ $class = ' class="finish_line"';
+ echo "<tr>\n";
+ echo "<td$class><p class=\"station$id system$system_id\">$station</p></td>\n";
+ echo "<td id=\"distance$n\"><p></p></td>\n";
+ echo "</tr>\n";
+ }
+ echo "<tr>\n";
+ echo "<td>";
+ hidden('race_id', $race_id);
+ hidden('ship_id', $ship_id);
+ hidden('station_order', $station_order);
+ $stats = array('Lap distance', 'Complete laps', 'Total distance', 'Allotted time', 'Bonus time', 'Raced time', 'Penalty time', 'Total time', 'Fastest lap', 'Average lap', 'Best speed', 'Average speed');
+ foreach ($stats as $stat) {
+ $id = preg_replace('/[^a-z0-9]/', '_', strtolower($stat));
+ hidden("stat_$id");
+ }
+ if ($entry) hidden("add_entry");
+ else submit("add_entry", "Submit entry");
+ echo "</td>";
+ echo "<td><p>total distance</p></td>\n"; /* Distance column. */
+ echo "</tr>\n";
+ echo "</table>\n";
+ echo "<p>";
+ foreach (array('d', 'x') as $e) {
+ $name = "endorsement_$e";
+ $description = ucfirst(endorsement_description($e));
+ echo "$description: <input type=\"checkbox\" name=\"$name\"";
+ if ($_POST[$name]) echo " checked=\"checked\"";
+ echo ">\n";
+ }
+ echo "</p>\n";
+ end_form();
+ echo "</div>\n";
+ echo "<div id=\"stats\">\n";
+ echo "<table>\n";
+ foreach ($stats as $stat) {
+ $id = preg_replace('/[^a-z0-9]/', '_', strtolower($stat));
+ echo "<tr>\n";
+ echo "<th>$stat</th>\n";
+ echo "<td><p id=\"$id\"></p></td>\n";
+ echo "</tr>\n";
+ }
+ echo "</table>\n";
+ echo "</div>\n";
+ return false;
+ }
+
+ function module_entry($action) {
+ if ($action && ! count($_POST)) {
+ $eq = new EntryQuery;
+ $eq->joinWith('Entry.Race');
+ $eq->joinWith('Entry.Ship');
+ $eq->joinWith('Ship.Competitor');
+ $eq->joinWith('Entry.Lap');
+ $eq->joinWith('Lap.Laptime');
+ $entry = $eq->findOneById($action);
+ if ($entry) add_entry($entry->getRace()->getId(), $entry->getShip()->getId(), $entry);
+ }
+ else if ($_POST['add_entry']) {
+ if (add_entry($_POST["race_id"], $_POST["ship_id"])) unset($_POST);
+ }
+
+ $rq = new RaceQuery;
+ $races = $rq->find();
+ if (! count($races)) {
+ echo "<p>Add races before submitting entries.</p>\n";
+ return;
+ }
+ $sq = new ShipQuery;
+ $sq->joinWith("Ship.Competitor");
+ $ships = $sq->orderBy('Competitor.CmdrName')->orderByName()->find();
+ if (! count($ships)) {
+ echo "<p>Add ships before submitting entries.</p>\n";
+ return;
+ }
+ if (! $_POST['add_entry']) {
+ $eq = new EntryQuery;
+ $eq->joinWith('Entry.Race');
+ $eq->joinWith('Entry.Ship');
+ $eq->joinWith('Ship.Competitor');
+ $entries = $eq->orderBy('Race.Id')->orderBy('Competitor.CmdrName')->orderBy('Ship.Name')->orderById()->find();
+ foreach ($entries as $entry) {
+ $entry_link = $entry->getLink();
+ $race_name = $entry->getRace()->getLink();
+ $ship_name = $entry->getShip()->getName();
+ $cmdr_name = $entry->getShip()->getCompetitor()->getCmdrName();
+ echo "<p>CMDR <strong>$cmdr_name</strong> flying <strong>$ship_name</strong> in <strong>$race_name</strong> [$entry_link]</p>\n";
+ }
+ }
+ echo "<hr>\n";
+ form();
+ echo "<p>Add a race entry: ";
+ echo "<select name=\"race_id\">\n";
+ option("race_id", 0, "");
+ foreach ($races as $race) {
+ $id = $race->getId();
+ $name = $race->getName();
+ option("race_id", $id, $name);
+ }
+ echo "</select>\n";
+ echo "<a href=\"?module=ship\">Ship:</a> <select name=\"ship_id\">\n";
+ option("ship_id", 0, "");
+ $last_competitor = null;
+ foreach ($ships as $ship) {
+ $id = $ship->getId();
+ $ship_name = $ship->getName();
+ $competitor = $ship->getCompetitor();
+ $cmdr_name = $competitor->getCmdrName();
+ if ($cmdr_name != $last_competitor) {
+ if ($last_competitor) echo "</optgroup>\n";
+ echo "<optgroup label=\"CMDR $cmdr_name\">\n";
+ }
+ $last_competitor = $cmdr_name;
+ option("ship_id", $id, $ship_name);
+ }
+ if ($last_competitor) echo "</optgroup>\n";
+ echo "</select>\n";
+ submit("add_entry", "Add");
+ echo "</p>\n";
+ end_form();
+ }
+
+?>
--- /dev/null
+<?php
+ if ($module && file_exists("$root/$module.js")) {
+ echo "<script type=\"text/javascript\" src=\"$module.js\"></script>\n";
+ }
+?>
+</body>
+</html>
--- /dev/null
+<?php
+
+ function form($classes = null, $action = null) {
+ if (is_null($classes)) $classes = array();
+ else if (! is_array($classes)) $classes = explode('/\s+/', $classes);
+ if (! isset($action)) $action = $_SERVER['REQUEST_URI'];
+ echo "<form ";
+ if (count($classes)) printf("class=\"%s\" ", implode(" ", $classes));
+ echo "method=\"POST\" action=\"$action\">\n";
+ }
+
+ function end_form() {
+ echo "</form>\n";
+ }
+
+ function input($name, $value = null, $type = null) {
+ echo "<input name=\"$name\"";
+ if (isset($type)) echo " type=\"$type\"";
+ if (isset($value)) echo " value=\"$value\"";
+ else echo " value=\"" . $_POST[$name] . "\"";
+ echo ">";
+ }
+
+ function hidden($name, $value = null) {
+ return input($name, $value, "hidden");
+ }
+
+ function submit($name, $value = null) {
+ return input($name, $value, "submit");
+ }
+
+ function textarea($name, $value = null) {
+ $id = "textarea_$name";
+ echo "<textarea id=\"$id\" name=\"$name\" cols=30>";
+ if (isset($value)) echo $value;
+ else echo $_POST[$name];
+ echo "</textarea>";
+ echo "<script>\n $(function() {\n";
+ echo " $(\"#$id\").autosize();\n";
+ echo " });</script>";
+ }
+
+ function option($select, $value, $text, $selected = null) {
+ echo " <option value=\"$value\"";
+ if (! isset($selected)) $selected = $_POST[$select];
+ if ($value == $selected) echo " selected";
+ echo ">$text\n";
+ }
+
+ function datepicker($name, $value = null, $past = true, $past_picker = null, $future = true, $future_picker = null) {
+ $id = "datepicker_$name";
+ echo "<script>\n $(function() {\n";
+ echo " $(\"#$id\").datepicker({\n";
+ /*
+ If this is the "to" picker in a date range, restrict the end date of the
+ "from" picker to be the date selected by this picker.
+ */
+ if (isset($past_picker)) {
+ echo " onClose: function(selectedDate) {\n";
+ echo " $(\"#datepicker_$past_picker\").datepicker(\"option\", \"maxDate\", selectedDate);\n";
+ echo " },\n";
+ }
+ /*
+ If this is the "from" picker in a date range, restrict the start date of
+ the "to" picker to be the date selected by this picker.
+ */
+ if (isset($future_picker)) {
+ echo " onClose: function(selectedDate) {\n";
+ echo " $(\"#datepicker_$future_picker\").datepicker(\"option\", \"minDate\", selectedDate);\n";
+ echo " },\n";
+ }
+ echo " changeMonth: true,\n";
+ echo " changeYear: true,\n";
+ echo " dateFormat: 'yy-mm-dd',\n";
+ /* Are we allowed to show dates earlier than today? */
+ if (! $past) echo " minDate: 0,\n";
+ /* Are we allowed to show dates later than today? */
+ if (! $future) echo " maxDate: 0,\n";
+ echo " showButtonPanel: true\n";
+ echo " });\n });</script>";
+ echo "<input name=\"$name\" id=\"$id\"";
+ echo " maxlength=10 size=10";
+ if (isset($value)) echo " value=\"$value\"";
+ else echo " value=\"" . $_POST[$name] . "\"";
+ echo "><em class=\"small\">(Y-m-d)</em>";
+ }
+
+?>
--- /dev/null
+<head>
+<link rel="stylesheet" type="text/css" href="style.css">
+<title><?php echo "Buckyball Racing Club"; if ($module) echo " - $module"; ?></title>
+</head>
+<body>
+<?php
+ echo "<p>Buckyball Racing Club";
+ foreach (array("competitor", "hull", "ship", "system", "race", "raikogram", "entry") as $what) {
+ $title = preg_replace('/y$/', 'ie', $what);
+ echo " | <a href=\"?module=$what\">" . ucfirst($title) . "s</a>";
+ }
+ echo "</p>\n";
+?>
+<hr>
--- /dev/null
+<h2>Hulls</h2>
+<?php
+
+ function add_hull($name) {
+ if (! $name) {
+ echo "<p>Missing hull name!</p>\n";
+ return false;
+ }
+ $hull = new Hull;
+ $hull->setName($name);
+ try {
+ $hull->save();
+ return true;
+ }
+ catch (Exception $e) {
+ echo "<p>Error adding hull: " . $e->getMessage() . "</p>\n";
+ }
+ return false;
+ }
+
+ function module_hull($action) {
+ if ($_POST['add_hull']) {
+ if (add_hull($_POST["name"])) unset($_POST);
+ }
+
+ $q = new HullQuery;
+ $hulls = $q->find();
+ if (! count($hulls)) echo "<p>No hulls</p>\n";
+ foreach ($hulls as $hull) {
+ $name = $hull->getName();
+ echo "<p><strong>$name</strong></p>\n";
+ }
+
+ echo "<hr>\n";
+ form();
+ echo "<p>Add a new hull: ";
+ input("name", $_POST['name']);
+ submit("add_hull", "Add");
+ echo "</p>\n";
+ end_form();
+ }
+?>
--- /dev/null
+<?php
+ include_once(join(DIRECTORY_SEPARATOR, array($lib_root, "endorsements.php")));
+ include_once(join(DIRECTORY_SEPARATOR, array($lib_root, "elite_time.php")));
+ include_once(join(DIRECTORY_SEPARATOR, array($lib_root, "forms.php")));
+?>
--- /dev/null
+<?php
+
+ function module_lap($action) {
+
+ $rq = new RaceQuery;
+ $races = $rq->find();
+ if (! count($races)) {
+ echo "<p>Add races before submitting laps.</p>\n";
+ return;
+ }
+ $sq = new ShipQuery;
+ $sq->joinWith("Ship.Competitor");
+ $ships = $sq->orderBy('Competitor.CmdrName')->orderByName()->find();
+ if (! count($ships)) {
+ echo "<p>Add ships before submitting laps.</p>\n";
+ return;
+ }
+ echo "<hr>\n";
+ form();
+ echo "<p>Add a race entry: ";
+ echo "<select name=\"race_id\">\n";
+ option("race_id", 0, "");
+ foreach ($races as $race) {
+ $id = $race->getId();
+ $name = $race->getName();
+ option("race_id", $id, $name);
+ }
+ echo "</select>\n";
+ echo "<a href=\"?module=ship\">Ship:</a> <select name=\"ship_id\">\n";
+ option("ship_id", 0, "");
+ $last_competitor = null;
+ foreach ($ships as $ship) {
+ $id = $ship->getId();
+ $ship_name = $ship->getName();
+ $competitor = $ship->getCompetitor();
+ $cmdr_name = $competitor->getCmdrName();
+ if ($cmdr_name != $last_competitor) {
+ if ($last_competitor) echo "</optgroup>\n";
+ echo "<optgroup label=\"CMDR $cmdr_name\">\n";
+ }
+ $last_competitor = $cmdr_name;
+ option("ship_id", $id, $ship_name);
+ }
+ if ($last_competitor) echo "</optgroup>\n";
+ echo "</select>\n";
+ submit("add_hull", "Add");
+ echo "</p>\n";
+ end_form();
+ }
+
+?>
--- /dev/null
+<h2>Races</h2>
+<?php
+
+ function entry_bgcolor($row, $column) {
+ $blue = array('#006ecb', '#005195');
+ $white = array('#ffffff', '#f5f5f5');
+ $orange = array('#ff6418', '#c74e13');
+ $colours = array(
+ array_merge($blue, $blue, $blue, $blue, $white, $white, $white, $orange, $orange),
+ array('#d9d9d9', '#d4d4d4'),
+ array('#ebebeb', '#e6e6e6')
+ );
+ if (! $row) $row_index = 0;
+ else $row_index = 1 + ($row % 2);
+ $column_index = $column % count($colours[$row_index]);
+ return $colours[$row_index][$column_index];
+ }
+
+ function entry_fgcolor($row, $column, $header = 0) {
+ $white = array('#ffffff');
+ $blue = array('#005195');
+ $orange = array('#ff6418');
+ $colours = array(
+ array_merge($white, $white, $white, $white, $white, $white, $white, $white, $blue, $blue, $blue,$blue, $blue, $blue, $white, $white, $white),
+ $blue,
+ $orange
+ );
+ if (! $row) $row_index = 0;
+ else $row_index = 1 + ($row % 2);
+ $column_index = $column % count($colours[$row_index]);
+ return $colours[$row_index][$column_index];
+ }
+
+ function entry_header($type) {
+ $fields = array('P', 'E', 'CMDR name', 'Forum name', 'Ship type', 'Ship name', 'Distance', 'Penalty', 'Lap distance', 'Laps', 'Allotted', 'Bonus', 'Raced', 'Total', 'Fastest lap', 'Average lap', 'Best speed', 'Average speed');
+ $small_fields = array('Distance', 'Penalty', 'Allotted', 'Lap distance', 'Fastest lap', 'Average lap', 'Average speed');
+ if ($type == 'html') {
+ echo "<table class=\"entries\">\n";
+ echo "<tr>\n";
+ $column = 0;
+ foreach ($fields as $field) {
+ $bgcolor = entry_bgcolor(0, $column);
+ $fgcolor = entry_fgcolor(0, $column);
+ echo "<th style=\"background-color: $bgcolor; color: $fgcolor\">$field</th>\n";
+ $column++;
+ }
+ echo "</tr>\n";
+ }
+ else if ($type == 'bbcode') {
+ echo "<pre>\n";
+ echo "[table=\"class: outer_border\"]\n";
+ echo "[tr]\n";
+ $column = 0;
+ foreach ($fields as $field) {
+ $bgcolor = entry_bgcolor(0, $column);
+ $fgcolor = entry_fgcolor(0, $column);
+ echo "[td=\"bgcolor: $bgcolor\"][color=$fgcolor]";
+ if (in_array($field, $small_fields)) {
+ echo "[size=-2]";
+ echo $field;
+ echo "[/size]";
+ }
+ else echo $field;
+ echo "[/color][/td]\n";
+ $column++;
+ }
+ echo "[/tr]\n";
+ }
+ }
+
+ function entry_footer($type) {
+ if ($type == 'html') {
+ echo "</table>\n";
+ }
+ else if ($type == 'bbcode') {
+ echo "[/table]\n";
+ echo "</pre>\n";
+ }
+ }
+
+ function show_entry($entry, $allotted_time, $position, $row, $type) {
+ $fields = array();
+ $endorsements = $entry->getEndorsements();
+ $fields[] = $position;
+ $fields[] = endorsement_string($endorsements);
+ $ship = $entry->getShip();
+ $competitor = $ship->getCompetitor();
+ $cmdr_name= $competitor->getCmdrName();
+ $fields[] = $cmdr_name;
+ $forum_name = $competitor->getForumName();
+ if (! $forum_name) $forum_name = $cmdr_name;
+ $fields[] = $forum_name;
+ $hull = $ship->getHull();
+ $fields[] = $hull->getName();
+ $fields[] = $ship->getName();
+ $fields[] = format_distance($entry->getTotalDistance());
+ $fields[] = format_time($entry->getPenaltyTime());
+ $fields[] = format_distance($entry->getLapDistance());
+ $fields[] = $entry->getCompleteLaps();
+ $fields[] = ($allotted_time > 0) ? format_time($allotted_time) : '';
+ $fields[] = ($allotted_time > 0) ? format_time($entry->getBonusTime()) : '';
+ $fields[] = format_time($entry->getRacedTime());
+ $fields[] = ($allotted_time > 0) ? format_time($entry->getTotalTime()) : '';
+ $fields[] = format_time($entry->getFastestLap());
+ $fields[] = format_time($entry->getAverageLap());
+ $fields[] = format_speed($entry->getBestSpeed());
+ $fields[] = format_speed($entry->getAverageSpeed());
+
+ if ($type == 'html') {
+ echo "<tr>\n";
+ $column = 0;
+ foreach ($fields as $field) {
+ $bgcolor = entry_bgcolor($row, $column);
+ $fgcolor = entry_fgcolor($row, $column);
+ echo "<td style=\"background-color: $bgcolor; color: $fgcolor\">$field</td>\n";
+ $column++;
+ }
+ echo "</tr>\n";
+ }
+ else if ($type == 'bbcode') {
+ echo "[tr]\n";
+ $column = 0;
+ foreach ($fields as $field) {
+ $bgcolor = entry_bgcolor($row, $column);
+ $fgcolor = entry_fgcolor($row, $column);
+ echo "[td=\"bgcolor: $bgcolor\"][color=$fgcolor]";
+ echo $field;
+ echo "[/color][/td]\n";
+ $column++;
+ }
+ echo "[/tr]\n";
+ }
+ }
+
+ function module_race($action) {
+ $rq = new RaceQuery;
+ if ($action) $rq->filterById($action);
+ $races = $rq->find();
+ foreach ($races as $race) {
+ $id = $race->getId();
+ $name = $race->getName();
+ $allotted_time = $race->getAllottedTime();
+ $eq = new EntryQuery;
+ $eq->joinWith('Entry.Ship');
+ $eq->joinWith('Ship.Competitor');
+ $eq->joinWith('Ship.Hull');
+ $eq->filterByRaceId($id);
+ $entries = $eq->orderByTotalDistance('desc')->orderByPenaltyTime('desc')->orderByTotalTime('desc')->find();
+ if (! count($entries) && ! $action) continue;
+
+ echo "<h3>$name</h3>\n";
+ echo "<div class=\"classification\">\n";
+ foreach (array('html', 'bbcode') as $type) {
+ echo "<div class=\"$type\">\n";
+ entry_header($type);
+ $n = $position = 1;
+ foreach ($entries as $entry) {
+ $endorsements = $entry->getEndorsements();
+ show_entry($entry, $allotted_time, endorsement('x', $endorsements) ? '-' : $position++, $n++, $type);
+ }
+ entry_footer($type);
+ echo "</div>\n";
+ }
+ echo "</div>\n";
+ }
+ }
+
+?>
--- /dev/null
+<h2>Raikograms</h2>
+<?php
+
+ function edit_raikogram($race_id) {
+ $q = new CourseQuery;
+ $q->joinWith("Course.Station");
+ $q->join("Station.System");
+ $q->withColumn("System.Id", "SystemId");
+ $q->withColumn("System.Name", "SystemName");
+ $q->findByRaceId($race_id);
+ $course = $q->find();
+ $systems = array();
+ foreach ($course as $leg) {
+ $station = $leg->getStation();
+ $id = $leg->getSystemId();
+ $name = $leg->getSystemName();
+ $systems[$name] = $id;
+ }
+ ksort($systems);
+ if ($_POST['edit_raikogram'] == 'Update') {
+ foreach ($_POST as $k => $v) {
+ if (! preg_match('/^from(\d+)to(\d+)$/', $k, $m)) continue;
+ if (! $v) continue;
+ list($ignored, $a, $b) = $m;
+ $system1 = min($a, $b);
+ $system2 = max($a, $b);
+ $rq = new RaikogramQuery;
+ $rq->filterBySystem1($system1);
+ $rq->filterBySystem2($system2);
+ $raikogram = $rq->findOne();
+ if (! $raikogram) $raikogram = new Raikogram;
+ $raikogram->setSystem1($system1);
+ $raikogram->setSystem2($system2);
+ $raikogram->setDistance($v);
+ $raikogram->save();
+ }
+ }
+ form();
+ echo "<table>\n";
+ /* Header. */
+ echo "<tr>\n";
+ echo "<th></th>\n";
+ foreach ($systems as $name_x => $id_x) echo "<th>$name_x</th>\n";
+ echo "</tr>\n";
+ foreach ($systems as $name_y => $id_y) {
+ echo "<tr>\n";
+ echo "<th>$name_y</th>\n";
+ foreach ($systems as $name_x => $id_x) {
+ echo "<td>\n";
+ if ($id_x == $id_y) continue;
+ $distance = "";
+ $rq = new RaikogramQuery;
+ $rq->filterBySystem1(min($id_x, $id_y));
+ $rq->filterBySystem2(max($id_x, $id_y));
+ $raikogram = $rq->findOne();
+ if ($raikogram) $distance = $raikogram->getDistance();
+ input("from${id_x}to${id_y}", $distance);
+ echo "</td>\n";
+ }
+ echo "</tr>\n";
+ }
+ echo "<td>";
+ echo "</table>\n";
+ hidden('race_id', $race_id);
+ submit("edit_raikogram", "Update");
+ end_form();
+ echo "<hr>\n";
+ return false;
+ }
+
+ function module_raikogram($action) {
+ if ($_POST['edit_raikogram']) {
+ if (edit_raikogram($_POST["race_id"])) unset($_POST);
+ }
+
+ $rq = new RaceQuery;
+ $races = $rq->find();
+ if (! count($races)) {
+ echo "<p>Add races before editing Raikograms.</p>\n";
+ return;
+ }
+ form();
+ echo "<p>Edit race raikogram: ";
+ echo "<select name=\"race_id\">\n";
+ option("race_id", 0, "");
+ foreach ($races as $race) {
+ $id = $race->getId();
+ $name = $race->getName();
+ option("race_id", $id, $name);
+ }
+ echo "</select>\n";
+ submit("edit_raikogram", "Edit");
+ echo "</p>\n";
+ end_form();
+ }
+
+?>
--- /dev/null
+<h2>Ships</h2>
+<?php
+
+ function add_ship($competitor_id, $hull_id, $name) {
+ if (! $name) {
+ echo "<p>Missing ship name!</p>\n";
+ return false;
+ }
+ $ship = new Ship;
+ $ship->setHullId($hull_id);
+ $ship->setCompetitorId($competitor_id);
+ $ship->setName($name);
+ try {
+ $ship->save();
+ return true;
+ }
+ catch (Exception $e) {
+ echo "<p>Error adding ship: " . $e->getMessage() . "</p>\n";
+ }
+ return false;
+ }
+
+ function module_ship($action) {
+ if ($_POST['add_ship']) {
+ if (add_ship($_POST["competitor_id"], $_POST["hull_id"], $_POST["name"])) unset($_POST);
+ }
+
+ $q = new ShipQuery;
+ $q->joinWith("Ship.Competitor");
+ $q->joinWith("Ship.Hull");
+ $ships = $q->orderByName()->find();
+ if (! count($ships)) echo "<p>No ships</p>\n";
+ foreach ($ships as $ship) {
+ $name = $ship->getName();
+ $hull = $ship->getHull();
+ $hull_name = $hull->getName();
+ $competitor = $ship->getCompetitor();
+ $cmdr = $competitor->getCmdrName();
+ echo "<p><strong>$name</strong> ($hull_name) CMDR $cmdr</p>\n";
+ }
+
+ echo "<hr>\n";
+ $hq = new HullQuery;
+ $hulls = $hq->find();
+ $cq = new CompetitorQuery;
+ $competitors = $cq->find();
+ if (! count($hulls)) {
+ echo "<p>Add hulls before adding ships.</p>\n";
+ return;
+ }
+ if (! count($competitors)) {
+ echo "<p>Add competitors before adding ships.</p>\n";
+ return;
+ }
+ form();
+ echo "<p>Add a new ship: ";
+ echo "<a href=\"?module=hull\">Hull:</a> <select name=\"hull_id\">";
+ option("hull_id", 0, "");
+ foreach ($hulls as $hull) {
+ $id = $hull->getId();
+ $name = $hull->getName();
+ option("hull_id", $id, $name);
+ }
+ echo "</select>\n";
+ echo "<a href=\"?module=competitor\">Competitor:</a> <select name=\"competitor_id\">";
+ option("competitor_id", 0, "");
+ foreach ($competitors as $competitor) {
+ $id = $competitor->getId();
+ $cmdr = $competitor->getCmdrName();
+ option("competitor_id", $id, $cmdr);
+ }
+ input("name", $_POST["name"]);
+ echo "</select>\n";
+
+ submit("add_ship", "Add");
+ echo "</p>\n";
+ end_form();
+ }
+?>
+
--- /dev/null
+Order deny,allow
+Deny from all
--- /dev/null
+<?php
+
+ class BuckyballObject extends BaseObject {
+ function getURL() {
+ /* XXX */
+ $class = get_class($this->getPeer());
+ return sprintf("?module=%s&action=%s", urlencode(strtolower($class::OM_CLASS)), $this->getId());
+ return sprintf("/%s/%s/%d", urlencode(strtolower($class::OM_CLASS)), urlencode($this->getName()), $this->getId());
+ }
+
+ function getLink($blurb = null, $url = null, $classes = null) {
+ if (is_null($classes)) $classes = array();
+ else if (! is_array($classes)) $classes = array($classes);
+ return sprintf("<a %shref=\"%s\">%s</a>", (count($classes)) ? "class=\"" . implode(" ", $classes) . "\" " : "", (isset($url)) ? $url : $this->getURL(), (isset($blurb)) ? htmlspecialchars($blurb) : htmlspecialchars($this->getName()));
+ }
+
+ function getStrongLink($blurb = null, $url = null) {
+ return $this->getLink($blurb, $url, "strong");
+ }
+
+ function getActionLink($action, $blurb, $classes = null) {
+ if (is_null($classes)) $classes = array();
+ else if (! is_array($classes)) $classes = array($classes);
+ return $this->getLink($blurb, sprintf("%s/%s", $this->getURL(), urlencode($action)), array_unique(array_merge($classes, array("small"))));
+ }
+
+ function getDeleteLink($confirm = false) {
+ $link = ($confirm) ? "confirmdelete" : "delete";
+ return $this->getActionLink($link, "Delete", array("delete", "noprint"));
+ }
+ }
+
+?>
--- /dev/null
+<?php
+
+
+
+/**
+ * Skeleton subclass for representing a row from the 'competitor' table.
+ *
+ *
+ *
+ * You should add additional methods to this class to meet the
+ * application requirements. This class will only be generated as
+ * long as it does not already exist in the output directory.
+ *
+ * @package propel.generator.buckyball
+ */
+class Competitor extends BaseCompetitor
+{
+ function getName() {
+ return $this->getCmdrName();
+ }
+}
--- /dev/null
+<?php
+
+
+
+/**
+ * Skeleton subclass for representing a row from the 'entry' table.
+ *
+ *
+ *
+ * You should add additional methods to this class to meet the
+ * application requirements. This class will only be generated as
+ * long as it does not already exist in the output directory.
+ *
+ * @package propel.generator.buckyball
+ */
+class Entry extends BaseEntry
+{
+ function getName() {
+ return sprintf("entry %d", $this->getId());
+ }
+}
</vendor>
<column name="id" type="integer" required="true" primaryKey="true" autoIncrement="true"/>
<column name="name" type="varchar" size="64" required="true"/>
+ <column name="allotted_time" type="integer" required="false"/>
<unique name="name">
<unique-column name="name"/>
</unique>
<unique-column name="station_id"/>
</unique>
<unique name="start_line">
+ <unique-column name="race_id"/>
<unique-column name="start_line"/>
</unique>
<unique name="finish_line">
+ <unique-column name="race_id"/>
<unique-column name="finish_line"/>
</unique>
<foreign-key foreignTable="race" phpName="Race" refPhpName="Course">
<parameter name="Charset" value="utf8"/>
</vendor>
<column name="id" type="integer" required="true" primaryKey="true" autoIncrement="true"/>
- <column name="race_id" type="integer" required="true"/>
- <column name="ship_id" type="integer" required="true"/>
+ <column name="entry_id" type="integer" required="true"/>
<column name="lap_number" type="integer" required="true"/>
- <unique name="ship_lap">
- <unique-column name="race_id"/>
- <unique-column name="ship_id"/>
+ <column name="hull_start" type="integer"/>
+ <column name="hull_end" type="integer"/>
+ <unique name="entry_lap">
+ <unique-column name="entry_id"/>
<unique-column name="lap_number"/>
</unique>
- <foreign-key foreignTable="race" phpName="Race" refPhpName="Lap">
- <reference local="race_id" foreign="id"/>
- </foreign-key>
- <foreign-key foreignTable="ship" phpName="Ship" refPhpName="Lap">
- <reference local="ship_id" foreign="id"/>
+ <foreign-key foreignTable="entry" phpName="Entry" refPhpName="Lap">
+ <reference local="entry_id" foreign="id"/>
</foreign-key>
</table>
</vendor>
<column name="id" type="integer" required="true" primaryKey="true" autoIncrement="true"/>
<column name="lap_id" type="integer" required="true"/>
+ <column name="station_order" type="integer" required="true"/>
<column name="station_id" type="integer" required="true"/>
<column name="arrival" type="timestamp" required="true"/>
<unique name="lap_arrival">
</unique>
<unique name="lap_station">
<unique-column name="lap_id"/>
- <unique-column name="station_id"/>
+ <unique-column name="station_order"/>
</unique>
<foreign-key foreignTable="lap" phpName="Lap" refPhpName="Laptime">
<reference local="lap_id" foreign="id"/>
</foreign-key>
</table>
+ <!-- Race entry -->
+ <table name="entry" phpName="Entry" baseClass="BuckyballObject">
+ <vendor type="mysql">
+ <parameter name="Engine" value="InnoDB"/>
+ <parameter name="Charset" value="utf8"/>
+ </vendor>
+ <column name="id" type="integer" required="true" primaryKey="true" autoIncrement="true"/>
+ <column name="race_id" type="integer" required="true"/>
+ <column name="ship_id" type="integer" required="true"/>
+ <column name="endorsements" type="integer"/>
+ <column name="lap_distance" type="float" required="true"/>
+ <column name="complete_laps" type="integer" required="true"/>
+ <column name="total_distance" type="float" required="true"/>
+ <column name="penalty_time" type="integer"/>
+ <column name="bonus_time" type="integer"/>
+ <column name="raced_time" type="integer" required="true"/>
+ <column name="total_time" type="integer" required="true"/>
+ <column name="fastest_lap" type="integer" required="true"/>
+ <column name="average_lap" type="integer" required="true"/>
+ <column name="best_speed" type="float" required="true"/>
+ <column name="average_speed" type="float" required="true"/>
+ <foreign-key foreignTable="race" phpName="Race" refPhpName="Entry">
+ <reference local="race_id" foreign="id"/>
+ </foreign-key>
+ <foreign-key foreignTable="ship" phpName="Ship" refPhpName="Entry">
+ <reference local="ship_id" foreign="id"/>
+ </foreign-key>
+ </table>
+
+ <!-- Distance between stations -->
+ <table name="raikogram" phpName="Raikogram" baseClass="BuckyballObject">
+ <vendor type="mysql">
+ <parameter name="Engine" value="InnoDB"/>
+ <parameter name="Charset" value="utf8"/>
+ </vendor>
+ <column name="id" type="integer" required="true" primaryKey="true" autoIncrement="true"/>
+ <column name="system1" type="integer" required="true"/>
+ <column name="system2" type="integer" required="true"/>
+ <column name="distance" type="float" required="true"/>
+ <unique name="systems">
+ <unique-column name="system1"/>
+ <unique-column name="system2"/>
+ </unique>
+ <foreign-key foreignTable="system" phpName="LowSystem" refPhpName="LowRaikogram">
+ <reference local="system1" foreign="id"/>
+ </foreign-key>
+ <foreign-key foreignTable="system" phpName="HighSystem" refPhpName="HighRaikogram">
+ <reference local="system2" foreign="id"/>
+ </foreign-key>
+ </table>
+
</database>
--- /dev/null
+/* Copy table to clipboard. */
+function copy_classification(event) {
+ try {
+ var text = event.currentTarget.getElementsByClassName('bbcode')[0].getElementsByTagName('pre')[0].innerHTML;
+ if (text) {
+ var area = document.createElement('textarea');
+ area.style.position = 'fixed';
+ area.style.top = 0;
+ area.style.left = 0;
+ area.style.height = '2em';
+ area.style.width = '2em';
+ area.style.padding = 0;
+ area.style.background = 'transparent';
+ area.value = text;
+ document.body.appendChild(area);
+ area.select();
+ if (document.execCommand('copy')) console.log('Copied bbcode to clipboard.');
+ else console.log('Failed to copy bbcode to clipboard.');
+ document.body.removeChild(area);
+ }
+ }
+ catch (e) {
+ console.log(e);
+ }
+}
+
+/* Switch between HTML and bbcode. */
+function toggle_classification(event) {
+ for (div of event.currentTarget.getElementsByTagName('div')) {
+ if (window.getComputedStyle(div).getPropertyValue('display') == 'none') {
+ div.style.display = 'block';
+ }
+ else {
+ div.style.display = 'none';
+ }
+ }
+}
+
+/* Document onload. */
+function loaded() {
+ for (classification of document.getElementsByClassName('classification')) {
+ classification.addEventListener('dblclick', copy_classification, true);
+ }
+}
+
+document.onload = loaded();
--- /dev/null
+#laps { overflow-x: auto; }
+#laps table { background-color: rgba(20, 50, 100, 0.2); }
+#laps th, #laps td { padding-left: 1em; }
+th { text-align: left; }
+th:nth-child(n+2),td:nth-child(n+2) { text-align: center; }
+#stats td:nth-child(n+1) { text-align: left; }
+table.entries { background-color: black; }
+table.entries th, table.entries td { text-align: center; }
+.classification .bbcode { display: none; background-color: rgba(0, 0, 0, 0.1); }