(function(WGo){
"use strict";
// player counter - for creating unique ids
var pl_count = 0;
// generate DOM of region
var playerBlock = function(name, parent, visible) {
var e = {};
e.element = document.createElement("div");
e.element.className = "wgo-player-"+name;
e.wrapper = document.createElement("div");
e.wrapper.className = "wgo-player-"+name+"-wrapper";
e.element.appendChild(e.wrapper);
parent.appendChild(e.element);
if(!visible) e.element.style.display = "none";
return e;
}
// generate all DOM of player
var BPgenerateDom = function() {
// wrapper object for common DOM
this.dom = {};
// center element
this.dom.center = document.createElement("div");
this.dom.center.className = "wgo-player-center";
// board wrapper element
this.dom.board = document.createElement("div");
this.dom.board.className = "wgo-player-board";
// object wrapper for regions (left, right, top, bottom)
this.regions = {};
/*
pseudo DOM structure:
*/
this.regions.left = playerBlock("left", this.element);
this.element.appendChild(this.dom.center);
this.regions.right = playerBlock("right", this.element);
this.regions.top = playerBlock("top", this.dom.center);
this.dom.center.appendChild(this.dom.board);
this.regions.bottom = playerBlock("bottom", this.dom.center);
}
var getCurrentLayout = function() {
var cl = this.config.layout;
if(cl.constructor != Array) return cl;
var bh = this.height || this.maxHeight;
for(var i = 0; i < cl.length; i++) {
if(!cl[i].conditions || (
(!cl[i].conditions.minWidth || cl[i].conditions.minWidth <= this.width) &&
(!cl[i].conditions.minHeight || !bh || cl[i].conditions.minHeight <= bh) &&
(!cl[i].conditions.maxWidth || cl[i].conditions.maxWidth >= this.width) &&
(!cl[i].conditions.maxHeight || !bh || cl[i].conditions.maxHeight >= bh) &&
(!cl[i].conditions.custom || cl[i].conditions.custom.call(this))
)) {
return cl[i];
}
}
}
var appendComponents = function(area) {
var components;
if(this.currentLayout.layout) components = this.currentLayout.layout[area];
else components = this.currentLayout[area];
if(components) {
this.regions[area].element.style.display = "block";
if(components.constructor != Array) components = [components];
for(var i in components) {
if(!this.components[components[i]]) this.components[components[i]] = new BasicPlayer.component[components[i]](this);
this.components[components[i]].appendTo(this.regions[area].wrapper);
// remove detach flag
this.components[components[i]]._detachFromPlayer = false;
}
}
else {
this.regions[area].element.style.display = "none";
}
}
var manageComponents = function() {
// add detach flags to every widget
for(var key in this.components) {
this.components[key]._detachFromPlayer = true;
}
appendComponents.call(this, "left");
appendComponents.call(this, "right");
appendComponents.call(this, "top");
appendComponents.call(this, "bottom");
// detach all invisible components
for(var key in this.components) {
if(this.components[key]._detachFromPlayer && this.components[key].element.parentNode) this.components[key].element.parentNode.removeChild(this.components[key].element);
}
}
/**
* Main object of player, it binds all magic together and produces visible player.
* It inherits some functionality from WGo.Player, but full html structure is done here.
*
* Layout of player can be set. It can be even dynamic according to screen resolution.
* There are 5 areas - left, right, top and bottom, and there is special region for board.
* You can put BasicPlayer.Component objects to these regions. Basic components are:
* - BasicPlayer.CommentBox - box with comments and game informations
* - BasicPlayer.InfoBox - box with information about players
* - BasicPlayer.Control - buttons and staff for control
*
* Possible configurations:
* - sgf: sgf string (default: undefined)
* - json: kifu stored in json/jgo (default: undefined)
* - sgfFile: sgf file path (default: undefined)
* - board: configuration object of board (default: {})
* - enableWheel: allow player to be controlled by mouse wheel (default: true)
* - lockScroll: disable window scrolling while hovering player (default: true)
* - enableKeys: allow player to be controlled by arrow keys (default: true)
* - kifuLoaded: extra Player's kifuLoaded event listener (default: undefined)
* - update: extra Player's update event listener (default: undefined)
* - frozen: extra Player's frozen event listener (default: undefined)
* - unfrozen: extra Player's unfrozen event listener (default: undefined)
* - layout: layout object. Look below how to define your own layout (default: BasicPlayer.dynamicLayout)
*
* You also must specify main DOMElement of player.
*/
var BasicPlayer = WGo.extendClass(WGo.Player, function(elem, config) {
this.config = config;
// add default configuration of BasicPlayer
for(var key in BasicPlayer.default) if(this.config[key] === undefined && BasicPlayer.default[key] !== undefined) this.config[key] = BasicPlayer.default[key];
// add default configuration of Player class
for(var key in WGo.Player.default) if(this.config[key] === undefined && WGo.Player.default[key] !== undefined) this.config[key] = WGo.Player.default[key];
this.element = elem
this.element.innerHTML = "";
this.classes = (this.element.className ? this.element.className+" " : "")+"wgo-player-main" ;
this.element.className = this.classes;
if(!this.element.id) this.element.id = "wgo_"+(pl_count++);
BPgenerateDom.call(this);
this.board = new WGo.Board(this.dom.board, this.config.board);
this.init();
this.components = {};
window.addEventListener("resize", function() {
if(!this.noresize) {
this.updateDimensions();
}
}.bind(this));
this.updateDimensions();
this.initGame();
});
/**
* Append player to different element.
*/
BasicPlayer.prototype.appendTo = function(elem) {
elem.appendChild(this.element);
this.updateDimensions();
}
/**
* Set right dimensions of all elements.
*/
BasicPlayer.prototype.updateDimensions = function() {
var css = window.getComputedStyle(this.element);
var els = [];
while(this.element.firstChild) {
els.push(this.element.firstChild);
this.element.removeChild(this.element.firstChild);
}
var tmp_w = parseInt(css.width);
var tmp_h = parseInt(css.height);
var tmp_mh = parseInt(css.maxHeight) || 0;
for(var i = 0; i < els.length; i++) {
this.element.appendChild(els[i]);
}
if(tmp_w == this.width && tmp_h == this.height && tmp_mh == this.maxHeight) return;
this.width = tmp_w;
this.height = tmp_h;
this.maxHeight = tmp_mh;
this.currentLayout = getCurrentLayout.call(this);
if(this.currentLayout && this.lastLayout != this.currentLayout) {
if(this.currentLayout.className) this.element.className = this.classes+" "+this.currentLayout.className;
else this.element.className = this.classes;
manageComponents.call(this);
this.lastLayout = this.currentLayout;
}
//var bw = this.width - this.regions.left.element.clientWidth - this.regions.right.element.clientWidth;
var bw = this.dom.board.clientWidth;
var bh = this.height || this.maxHeight;
if(bh) {
bh -= this.regions.top.element.offsetHeight + this.regions.bottom.element.offsetHeight;
}
if(bh && bh < bw) {
if(bh != this.board.height) this.board.setHeight(bh);
}
else {
if(bw != this.board.width) this.board.setWidth(bw);
}
var diff = bh - bw;
if(diff > 0) {
this.dom.board.style.height = bh+"px";
this.dom.board.style.paddingTop = (diff/2)+"px";
}
else {
this.dom.board.style.height = "auto";
this.dom.board.style.paddingTop = "0";
}
this.regions.left.element.style.height = this.dom.center.offsetHeight+"px";
this.regions.right.element.style.height = this.dom.center.offsetHeight+"px";
for(var i in this.components) {
if(this.components[i].updateDimensions) this.components[i].updateDimensions();
}
}
/**
* Layout contains built-in info box, for displaying of text(html) messages.
* You can use this method to display a message.
*
* @param text or html to display
* @param closeCallback optional callback function which is called when message is closed
*/
BasicPlayer.prototype.showMessage = function(text, closeCallback, permanent) {
this.info_overlay = document.createElement("div");
this.info_overlay.style.width = this.element.offsetWidth+"px";
this.info_overlay.style.height = this.element.offsetHeight+"px";
this.info_overlay.className = "wgo-info-overlay";
this.element.appendChild(this.info_overlay);
var info_message = document.createElement("div");
info_message.className = "wgo-info-message";
info_message.innerHTML = text;
var close_info = document.createElement("div");
close_info.className = "wgo-info-close";
if(!permanent) close_info.innerHTML = WGo.t("BP:closemsg");
info_message.appendChild(close_info);
this.info_overlay.appendChild(info_message);
if(closeCallback) {
this.info_overlay.addEventListener("click",function(e) {
closeCallback(e);
});
}
else if(!permanent) {
this.info_overlay.addEventListener("click",function(e) {
this.hideMessage();
}.bind(this));
}
this.setFrozen(true);
}
/**
* Hide a message box.
*/
BasicPlayer.prototype.hideMessage = function() {
this.element.removeChild(this.info_overlay);
this.setFrozen(false);
}
/**
* Error handling
*/
BasicPlayer.prototype.error = function(err) {
if(!WGo.ERROR_REPORT) throw err;
var url = "#";
switch(err.name) {
case "InvalidMoveError":
this.showMessage("
"+err.name+"
"+err.message+"
If this message isn't correct, please report it by clicking here, otherwise contact maintainer of this site.
");
break;
case "FileError":
this.showMessage(""+err.name+"
"+err.message+"
Please contact maintainer of this site. Note: it is possible to read files only from this host.
");
break;
default:
this.showMessage(""+err.name+"
"+err.message+"
"+err.stacktrace+"
Please contact maintainer of this site. You can also report it here.
");
}
}
BasicPlayer.component = {};
/**
* Preset layouts
* They have defined regions as arrays, which can contain components. For each of these layouts each component specifies where it is placed.
* You can create your own layout in same manners, but you must specify components manually.
*/
BasicPlayer.layouts = {
"one_column": {
top: [],
bottom: [],
},
"no_comment": {
top: [],
bottom: [],
},
"right_top": {
top: [],
right: [],
},
"right": {
right: [],
},
"minimal": {
bottom: []
},
};
/**
* WGo player can have more layouts. It allows responsive design of the player.
* Possible layouts are defined as array of object with this structure:
*
* layout = {
* Object layout, // layout as specified above
* Object conditions, // conditions that has to be valid to apply this layout
* String className // custom classnames
* }
*
* possible conditions:
* - minWidth - minimal width of player in px
* - maxWidth - maximal width of player in px
* - minHeight - minimal height of player in px
* - maxHeight - maximal height of player in px
* - custom - function which is called in template context, must return true or false
*
* Player's template evaluates layouts step by step and first layout that matches the conditions is applied.
*
* Look below at the default dynamic layout. Layouts are tested after every window resize.
*/
BasicPlayer.dynamicLayout = [
{
conditions: {
minWidth: 650,
},
layout: BasicPlayer.layouts["right_top"],
className: "wgo-twocols wgo-large",
},
{
conditions: {
minWidth: 550,
minHeight: 600,
},
layout: BasicPlayer.layouts["one_column"],
className: "wgo-medium"
},
{
conditions: {
minWidth: 350,
},
layout: BasicPlayer.layouts["no_comment"],
className: "wgo-small"
},
{ // if conditions object is omitted, layout is applied
layout: BasicPlayer.layouts["no_comment"],
className: "wgo-xsmall",
},
];
// default settings, they are merged with user settings in constructor.
BasicPlayer.default = {
layout: BasicPlayer.dynamicLayout,
}
WGo.i18n.en["BP:closemsg"] = "click anywhere to close this window";
//--- Handling with HTML5 data attributes -----------------------------------------------------------------
BasicPlayer.attributes = {
"data-wgo": function(value) {
if(value) {
if(value[0] == "(") this.sgf = value;
else this.sgfFile = value;
}
},
"data-wgo-board": function(value) {
// using eval to parse strings like "stoneStyle: 'painted'"
this.board = eval("({"+value+"})");
},
"data-wgo-onkifuload": function(value) {
this.kifuLoaded = new Function(value);
},
"data-wgo-onupdate": function(value) {
this.update = new Function(value);
},
"data-wgo-onfrozen": function(value) {
this.frozen = new Function(value);
},
"data-wgo-onunfrozen": function(value) {
this.unfrozen = new Function(value);
},
"data-wgo-layout": function(value) {
this.layout = eval("({"+value+"})");
},
"data-wgo-enablewheel": function(value) {
if(value.toLowerCase() == "false") this.enableWheel = false;
},
"data-wgo-lockscroll": function(value) {
if(value.toLowerCase() == "false") this.lockScroll = false;
},
"data-wgo-enablekeys": function(value) {
if(value.toLowerCase() == "false") this.enableKeys = false;
},
"data-wgo-rememberpath": function(value) {
if(value.toLowerCase() == "false") this.rememberPath = false;
},
"data-wgo-allowillegal": function(value) {
if(value.toLowerCase() != "false") this.allowIllegalMoves = true;
},
"data-wgo-move": function(value) {
var m = parseInt(value);
if(!isNaN(m)) this.move = m;
else this.move = eval("({"+value+"})");
},
"data-wgo-marklastmove": function(value) {
if(value.toLowerCase() == "false") this.markLastMove = false;
},
"data-wgo-diagram": function(value) {
if(value) {
if(value[0] == "(") this.sgf = value;
else this.sgfFile = value;
this.markLastMove = false;
this.enableKeys = false;
this.enableWheel = false;
this.layout = {top: [], right: [], left: [], bottom: []};
}
}
}
var player_from_tag = function(elem) {
var att, config, pl;
config = {};
for(var a = 0; a < elem.attributes.length; a++) {
att = elem.attributes[a];
if(BasicPlayer.attributes[att.name]) BasicPlayer.attributes[att.name].call(config, att.value, att.name);
}
pl = new BasicPlayer(elem, config);
elem._wgo_player = pl;
}
WGo.BasicPlayer = BasicPlayer;
window.addEventListener("load", function() {
var pl_elems = document.querySelectorAll("[data-wgo],[data-wgo-diagram]");
for(var i = 0; i < pl_elems.length; i++) {
player_from_tag(pl_elems[i]);
}
});
})(WGo);