From d058db496aa1ea129a8daa472b5d50a249b00098 Mon Sep 17 00:00:00 2001 From: Justin Blank Date: Wed, 20 Feb 2013 15:59:19 -0500 Subject: [PATCH] Added support for downloads using a[download]. In browsers that support the new download attribute for links, you can now download edited files with no server side component. --- player/js/all.compressed.js | 204 +++++++++++---------- player/js/player.js | 343 +++++++++++++++++++----------------- player/js/util.js | 50 +++--- 3 files changed, 314 insertions(+), 283 deletions(-) diff --git a/player/js/all.compressed.js b/player/js/all.compressed.js index 38ea480..fb29a13 100644 --- a/player/js/all.compressed.js +++ b/player/js/all.compressed.js @@ -144,6 +144,10 @@ var _2=(ua.match(/.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/)||[])[1]; eidogo.browser={ua:ua,ver:_2,ie:/msie/.test(ua)&&!/opera/.test(ua),moz:/mozilla/.test(ua)&&!/(compatible|webkit)/.test(ua),safari3:/webkit/.test(ua)&&parseInt(_2,10)>=420}; eidogo.util={byId:function(id){ return document.getElementById(id); +},downloadElement:function(){ +return "download" in document.createElement("a"); +},dataUri:function(){ +return; },makeQueryString:function(_4){ var qs=""; if(_4&&typeof _4=="object"){ @@ -1175,7 +1179,8 @@ return 0; }}; (function(){ -var t=eidogo.i18n,_2=eidogo.util.byId,_3=eidogo.util.ajax,_4=eidogo.util.addEvent,_5=eidogo.util.onClick,_6=eidogo.util.getElClickXY,_7=eidogo.util.stopEvent,_8=eidogo.util.addClass,_9=eidogo.util.removeClass,_a=eidogo.util.show,_b=eidogo.util.hide,_c=eidogo.browser.moz,_d=eidogo.util.getPlayerPath(); +var t=eidogo.i18n,_2=eidogo.util.byId,_3=eidogo.util.ajax,_4=eidogo.util.addEvent,_5=eidogo.util.onClick,_6=eidogo.util.getElClickXY,_7=eidogo.util.stopEvent,_8=eidogo.util.addClass,_9=eidogo.util.removeClass,_a=eidogo.util.show,_b=eidogo.util.hide,_c=eidogo.browser.moz,_d=eidogo.util.downloadElement(); +playerPath=eidogo.util.getPlayerPath(); eidogo.players=eidogo.players||{}; eidogo.delegate=function(_e,fn){ var _10=eidogo.players[_e]; @@ -2742,7 +2747,6 @@ this.dom.comments.innerHTML+=_13f.replace(/^(\n|\r|\t|\s)+/,"").replace(/\n/g,"< cls=cls||"comment-status"; this.dom.comments.innerHTML="
"+_142+"
"+this.dom.comments.innerHTML; },downloadSgf:function(evt){ -_7(evt); if(this.downloadUrl){ if(this.unsavedChanges){ alert(t["unsaved changes"]); @@ -2750,20 +2754,28 @@ return; } location.href=this.downloadUrl+this.gameName; }else{ +if(_d){ +var blob=new Blob([this.cursor.getGameRoot().toSgf()],{"type":"text/plain"}); +var _146=document.getElementsByClassName("option-download")[0]; +_146.href=window.URL.createObjectURL(blob); +_146.download="eidogogame.sgf"; +return true; +}else{ if(_c){ location.href="data:text/plain,"+encodeURIComponent(this.cursor.getGameRoot().toSgf()); } } +} },save:function(evt){ _7(evt); -var _146=function(req){ +var _148=function(req){ this.hook("saved",[req.responseText]); }; -var _148=function(req){ +var _14a=function(req){ this.croak(t["error retrieving"]); }; var sgf=this.cursor.getGameRoot().toSgf(); -_3("POST",this.saveUrl,{sgf:sgf},_146,_148,this,30000); +_3("POST",this.saveUrl,{sgf:sgf},_148,_14a,this,30000); },constructDom:function(){ this.dom.player=document.createElement("div"); this.dom.player.className="eidogo-player"+(this.theme?" theme-"+this.theme:""); @@ -2771,21 +2783,21 @@ this.dom.player.id="player-"+this.uniq; this.dom.container.innerHTML=""; eidogo.util.show(this.dom.container); this.dom.container.appendChild(this.dom.player); -var _14b="
"+t["variations"]+":
"+t["done"]+"
"+t["done"]+"
"+t["close search"]+"

 "+t["matches found"]+" Showing -

"+t["white"]+" "+t["black"]+" "+t["result"]+" "+t["date"]+"
"+(this.saveUrl?""+t["save to server"]+"":"")+" "+(this.downloadUrl||_c?""+t["download sgf"]+"":"")+"
Show variations on board
Mark current move
"; -_14b=_14b.replace(/ id='([^']+)'/g," id='$1-"+this.uniq+"'"); -this.dom.player.innerHTML=_14b; +var _14d="
"+t["variations"]+":
"+t["done"]+"
"+t["done"]+"
"+t["close search"]+"

 "+t["matches found"]+" Showing -

"+t["white"]+" "+t["black"]+" "+t["result"]+" "+t["date"]+"
"+(this.saveUrl?""+t["save to server"]+"":"")+" "+(this.downloadUrl||_d||_c?""+t["download sgf"]+"":"")+"
Show variations on board
Mark current move
"; +_14d=_14d.replace(/ id='([^']+)'/g," id='$1-"+this.uniq+"'"); +this.dom.player.innerHTML=_14d; var re=/ id='([^']+)-\d+'/g; -var _14d; -var id; var _14f; -while(_14d=re.exec(_14b)){ -id=_14d[0].replace(/'/g,"").replace(/ id=/,""); -_14f=""; -_14d[1].split("-").forEach(function(word,i){ +var id; +var _151; +while(_14f=re.exec(_14d)){ +id=_14f[0].replace(/'/g,"").replace(/ id=/,""); +_151=""; +_14f[1].split("-").forEach(function(word,i){ word=i?word.charAt(0).toUpperCase()+word.substring(1):word; -_14f+=word; +_151+=word; }); -this.dom[_14f]=_2(id); +this.dom[_151]=_2(id); } [["moveNumber","setPermalink"],["controlFirst","first"],["controlBack","back"],["controlForward","forward"],["controlLast","last"],["controlPass","pass"],["scoreEst","fetchScoreEstimate"],["searchButton","searchRegion"],["searchResults","loadSearchResult"],["searchClose","closeSearch"],["optionDownload","downloadSgf"],["optionSave","save"],["commentsEditDone","finishEditComment"],["gameInfoEditDone","finishEditGameInfo"],["navTree","navTreeClick"]].forEach(function(eh){ if(this.dom[eh[0]]){ @@ -2801,61 +2813,61 @@ _b(this.dom.navSliderThumb); return; } this.dom.navSlider.style.cursor="pointer"; -var _154=false; -var _155=null; +var _156=false; +var _157=null; _4(this.dom.navSlider,"mousedown",function(e){ -_154=true; +_156=true; _7(e); },this,true); _4(document,"mousemove",function(e){ -if(!_154){ +if(!_156){ return; } var xy=_6(e,this.dom.navSlider); -clearTimeout(_155); -_155=setTimeout(function(){ +clearTimeout(_157); +_157=setTimeout(function(){ this.updateNavSlider(xy[0]); }.bind(this),10); _7(e); },this,true); _4(document,"mouseup",function(e){ -if(!_154){ +if(!_156){ return true; } -_154=false; +_156=false; var xy=_6(e,this.dom.navSlider); this.updateNavSlider(xy[0]); return true; },this,true); -},updateNavSlider:function(_15b){ -var _15c=this.dom.navSlider.offsetWidth-this.dom.navSliderThumb.offsetHeight; -var _15d=this.totalMoves; -var _15e=!!_15b; -_15b=_15b||(this.moveNumber/_15d*_15c); -_15b=_15b>_15c?_15c:_15b; -_15b=_15b<0?0:_15b; -var _15f=parseInt(_15b/_15c*_15d,10); -if(_15e){ +},updateNavSlider:function(_15d){ +var _15e=this.dom.navSlider.offsetWidth-this.dom.navSliderThumb.offsetHeight; +var _15f=this.totalMoves; +var _160=!!_15d; +_15d=_15d||(this.moveNumber/_15f*_15e); +_15d=_15d>_15e?_15e:_15d; +_15d=_15d<0?0:_15d; +var _161=parseInt(_15d/_15e*_15f,10); +if(_160){ this.nowLoading(); -var _160=_15f-this.cursor.getMoveNumber(); -for(var i=0;i0){ +var _162=_161-this.cursor.getMoveNumber(); +for(var i=0;i0){ this.variation(null,true); }else{ -if(_160<0){ +if(_162<0){ this.cursor.previous(); } } } -if(_160<0){ -this.board.revert(Math.abs(_160)); +if(_162<0){ +this.board.revert(Math.abs(_162)); } this.doneLoading(); this.refresh(); } -_15b=parseInt(_15f/_15d*_15c,10)||0; -this.dom.navSliderThumb.style.left=_15b+"px"; -},updateNavTree:function(_162){ +_15d=parseInt(_161/_15f*_15e,10)||0; +this.dom.navSliderThumb.style.left=_15d+"px"; +},updateNavTree:function(_164){ if(!this.prefs.showNavTree){ return; } @@ -2863,7 +2875,7 @@ if(this.updatedNavTree){ this.showNavTreeCurrent(); return; } -if(!_162){ +if(!_164){ if(this.navTreeTimeout){ clearTimeout(this.navTreeTimeout); } @@ -2873,27 +2885,27 @@ this.updateNavTree(true); return; } this.updatedNavTree=true; -var _163=[],_164=this.cursor.getGameRoot(); -path=[_164.getPosition()],cur=new eidogo.GameCursor(),maxx=0; -var _166=function(node,_168,_169){ -var y=_169,x=_168; -var n=node,_16d=1; +var _165=[],_166=this.cursor.getGameRoot(); +path=[_166.getPosition()],cur=new eidogo.GameCursor(),maxx=0; +var _168=function(node,_16a,_16b){ +var y=_16b,x=_16a; +var n=node,_16f=1; while(n&&n._children.length==1){ -_16d++; +_16f++; n=n._children[0]; } -while(_163[y]&&_163[y].slice(x,x+_16d+1).some(function(el){ +while(_165[y]&&_165[y].slice(x,x+_16f+1).some(function(el){ return (typeof el!="undefined"); })){ y++; } do{ -if(!_163[y]){ -_163[y]=[]; +if(!_165[y]){ +_165[y]=[]; } cur.node=node; -node._pathStr=path.join("-")+"-"+(x-_168); -_163[y][x]=node; +node._pathStr=path.join("-")+"-"+(x-_16a); +_165[y][x]=node; if(x>maxx){ maxx=x; } @@ -2905,34 +2917,34 @@ node=node._children[0]; }while(node); for(var i=0;i"],node,td,cur=new eidogo.GameCursor(),x,y,_175,_176=1,LINE=2; +_168(_166,0,0); +var html=[""],node,td,cur=new eidogo.GameCursor(),x,y,_177,_178=1,LINE=2; for(x=0;x0;y--){ -if(!_163[y][x]){ -if(typeof _163[y][x+1]=="object"){ -_163[y][x]=_176; -_175=true; +_177=false; +for(y=_165.length-1;y>0;y--){ +if(!_165[y][x]){ +if(typeof _165[y][x+1]=="object"){ +_165[y][x]=_178; +_177=true; }else{ -if(_175){ -_163[y][x]=LINE; +if(_177){ +_165[y][x]=LINE; } } }else{ -_175=false; +_177=false; } } } -for(y=0;y<_163.length;y++){ +for(y=0;y<_165.length;y++){ html.push(""); -for(x=0;x<_163[y].length;x++){ -node=_163[y][x]; -if(node==_176){ +for(x=0;x<_165[y].length;x++){ +node=_165[y][x]; +if(node==_178){ td="
"; }else{ if(node==LINE){ @@ -2957,17 +2969,17 @@ setTimeout(function(){ this.showNavTreeCurrent(); }.bind(this),0); },showNavTreeCurrent:function(){ -var id="navtree-node-"+this.cursor.getPath().join("-"),_179=_2(id); -if(!_179){ +var id="navtree-node-"+this.cursor.getPath().join("-"),_17b=_2(id); +if(!_17b){ return; } if(this.prevNavTreeCurrent){ this.prevNavTreeCurrent.className=this.prevNavTreeCurrentClass; } -this.prevNavTreeCurrent=_179; -this.prevNavTreeCurrentClass=_179.className; -_179.className="current"; -var w=_179.offsetWidth,h=_179.offsetHeight,xy=eidogo.util.getElXY(_179),_17d=eidogo.util.getElXY(this.dom.navTree),l=xy[0]-_17d[0],t=xy[1]-_17d[1],ntc=this.dom.navTreeContainer,maxl=ntc.scrollLeft,maxr=maxl+ntc.offsetWidth-100; +this.prevNavTreeCurrent=_17b; +this.prevNavTreeCurrentClass=_17b.className; +_17b.className="current"; +var w=_17b.offsetWidth,h=_17b.offsetHeight,xy=eidogo.util.getElXY(_17b),_17f=eidogo.util.getElXY(this.dom.navTree),l=xy[0]-_17f[0],t=xy[1]-_17f[1],ntc=this.dom.navTreeContainer,maxl=ntc.scrollLeft,maxr=maxl+ntc.offsetWidth-100; maxt=ntc.scrollTop,maxb=maxt+ntc.offsetHeight-30; if(lmaxb){ ntc.scrollTop+=((t+h)-maxb); } },navTreeClick:function(e){ -var _183=e.target||e.srcElement; -if(!_183||!_183.id){ +var _185=e.target||e.srcElement; +if(!_185||!_185.id){ return; } -var path=_183.id.replace(/^navtree-node-/,"").split("-"); +var path=_185.id.replace(/^navtree-node-/,"").split("-"); this.goTo(path,true); _7(e); },resetLastLabels:function(){ this.labelLastNumber=1; this.labelLastLetter="A"; -},getGameDescription:function(_185){ +},getGameDescription:function(_187){ var root=this.cursor.getGameRoot(); if(!root){ return; } -var desc=(_185?"":root.GN||this.gameName); +var desc=(_187?"":root.GN||this.gameName); if(root.PW&&root.PB){ var wr=root.WR?" "+root.WR:""; var br=root.BR?" "+root.BR:""; desc+=(desc.length?" - ":"")+root.PW+wr+" vs "+root.PB+br; } return desc; -},sgfCoordToPoint:function(_18a){ -if(!_18a||_18a=="tt"){ +},sgfCoordToPoint:function(_18c){ +if(!_18c||_18c=="tt"){ return {x:null,y:null}; } -var _18b={a:0,b:1,c:2,d:3,e:4,f:5,g:6,h:7,i:8,j:9,k:10,l:11,m:12,n:13,o:14,p:15,q:16,r:17,s:18}; -return {x:_18b[_18a.charAt(0)],y:_18b[_18a.charAt(1)]}; +var _18d={a:0,b:1,c:2,d:3,e:4,f:5,g:6,h:7,i:8,j:9,k:10,l:11,m:12,n:13,o:14,p:15,q:16,r:17,s:18}; +return {x:_18d[_18c.charAt(0)],y:_18d[_18c.charAt(1)]}; },pointToSgfCoord:function(pt){ if(!pt||(this.board&&!this.boundsCheck(pt.x,pt.y,[0,this.board.boardSize-1]))){ return null; } var pts={0:"a",1:"b",2:"c",3:"d",4:"e",5:"f",6:"g",7:"h",8:"i",9:"j",10:"k",11:"l",12:"m",13:"n",14:"o",15:"p",16:"q",17:"r",18:"s"}; return pts[pt.x]+pts[pt.y]; -},expandCompressedPoints:function(_18e){ -var _18f; +},expandCompressedPoints:function(_190){ +var _191; var ul,lr; var x,y; -var _194=[]; +var _196=[]; var hits=[]; -for(var i=0;i<_18e.length;i++){ -_18f=_18e[i].split(/:/); -if(_18f.length>1){ -ul=this.sgfCoordToPoint(_18f[0]); -lr=this.sgfCoordToPoint(_18f[1]); +for(var i=0;i<_190.length;i++){ +_191=_190[i].split(/:/); +if(_191.length>1){ +ul=this.sgfCoordToPoint(_191[0]); +lr=this.sgfCoordToPoint(_191[1]); for(x=ul.x;x<=lr.x;x++){ for(y=ul.y;y<=lr.y;y++){ -_194.push(this.pointToSgfCoord({x:x,y:y})); +_196.push(this.pointToSgfCoord({x:x,y:y})); } } hits.push(i); } } -_18e=_18e.concat(_194); -return _18e; +_190=_190.concat(_196); +return _190; },setPermalink:function(){ if(!this.permalinkable){ return true; diff --git a/player/js/player.js b/player/js/player.js index 436bbe5..8e9a10b 100644 --- a/player/js/player.js +++ b/player/js/player.js @@ -22,6 +22,7 @@ var t = eidogo.i18n, show = eidogo.util.show, hide = eidogo.util.hide, isMoz = eidogo.browser.moz, + downloadElement = eidogo.util.downloadElement(); playerPath = eidogo.util.getPlayerPath(); // Keep track of all the player instances we've created @@ -42,51 +43,51 @@ eidogo.Player = function() { this.init.apply(this, arguments); } eidogo.Player.prototype = { - + /** * Inits settings that are persistent among games * @constructor * @param {Object} cfg A hash of configuration values */ init: function(cfg) { - + cfg = cfg || {}; - + // play, add_b, add_w, region, tr, sq, cr, label, number, score(?) this.mode = cfg.mode ? cfg.mode : "play"; - + // for references to all our DOM objects -- see constructDom() this.dom = {}; this.dom.container = (typeof cfg.container == "string" ? byId(cfg.container) : cfg.container); - + if (!this.dom.container) { alert(t['dom error']); return; } - + // unique id, so we can have more than one player on a page this.uniq = (new Date()).getTime(); - + // store for later eidogo.players[this.uniq] = this; - + // URL path to SGF files this.sgfPath = cfg.sgfPath; - + // pattern and game info search this.searchUrl = cfg.searchUrl; this.showingSearch = false; - + // save to file this.saveUrl = cfg.saveUrl; - + // url to handle downloads this.downloadUrl = cfg.downloadUrl; - + // score est this.scoreEstUrl = cfg.scoreEstUrl; - + // Allow outside scripts to hook into Player events. Format: // hookName: hookHandler // Available hooks: @@ -95,9 +96,9 @@ eidogo.Player.prototype = { // - setPermalink // - searchRegion this.hooks = cfg.hooks || {}; - + this.permalinkable = !!this.hooks.setPermalink; - + // handlers for the various types of GameNode properties this.propertyHandlers = { W: this.playMove, @@ -133,7 +134,7 @@ eidogo.Player.prototype = { WL: this.showTime, OW: this.showTime }; - + this.infoLabels = { GN: t['game'], PW: t['white'], @@ -161,7 +162,7 @@ eidogo.Player.prototype = { AP: t['created with'] // FF, GM, TM }; - + this.months = [ t['january'], t['february'], @@ -176,16 +177,16 @@ eidogo.Player.prototype = { t['november'], t['december'] ]; - + // UI theme this.theme = cfg.theme; - + // initialize per-game settings this.reset(cfg); - + // custom renderer? this.renderer = cfg.renderer || "html"; - + // crop settings this.cropParams = null; this.shrinkToFit = cfg.shrinkToFit; @@ -197,23 +198,23 @@ eidogo.Player.prototype = { this.cropParams.top = cfg.cropTop; this.cropParams.padding = cfg.cropPadding || 1; } - + // set up the elements we'll use this.constructDom(); - + // player-wide events if (cfg.enableShortcuts) { addEvent(document, isMoz ? "keypress" : "keydown", this.handleKeypress, this, true); } addEvent(document, "mouseup", this.handleDocMouseUp, this, true); - + if (cfg.sgf || cfg.sgfUrl || (cfg.sgfPath && cfg.gameName)) { this.loadSgf(cfg); } - + this.hook("initDone"); }, - + /** * Delegate to a hook handler. 'this' will be bound to the Player * instance @@ -223,30 +224,30 @@ eidogo.Player.prototype = { return this.hooks[hook].bind(this)(params); } }, - + /** * Resets settings that can change per game **/ reset: function(cfg) { this.gameName = ""; - + // Multiple games can be contained in collectionRoot. We default // to the first (collectionRoot._children[0]) - // See http://www.red-bean.com/sgf/sgf4.html + // See http://www.red-bean.com/sgf/sgf4.html this.collectionRoot = new eidogo.GameNode(); this.cursor = new eidogo.GameCursor(); - + // used for Ajaxy dynamic branch loading this.progressiveLoad = cfg.progressiveLoad ? true : false; this.progressiveLoads = null; this.progressiveUrl = null; this.progressiveMode = cfg.progressiveLoad && cfg.progressiveMode || "id"; - + // gnugo/computer opponent this.opponentUrl = null; this.opponentColor = null; this.opponentLevel = null; - + // these are populated after load this.board = null; this.rules = null; @@ -256,7 +257,7 @@ eidogo.Player.prototype = { this.variations = null; this.timeB = ""; this.timeW = ""; - + // region selection state this.regionTop = null; this.regionLeft = null; @@ -264,35 +265,35 @@ eidogo.Player.prototype = { this.regionHeight = null; this.regionBegun = null; this.regionClickSelect = null; - + // mouse clicking/dragging state this.mouseDown = null; this.mouseDownX = null; this.mouseDownY = null; this.mouseDownClickX = null; this.mouseDownClickY = null; - + // for the letter and number tools this.labelLastLetter = null; this.labelLastNumber = null; this.resetLastLabels(); - + // so we know when permalinks and downloads are unreliable this.unsavedChanges = false; - + // to know when to update the nav tree this.updatedNavTree = false; this.navTreeTimeout = null; - + // whether we're currently searching or editing this.searching = false; this.editingText = false; this.goingBack = false; - + // problem-solving mode: respond when the user plays a move this.problemMode = cfg.problemMode; this.problemColor = cfg.problemColor; - + // user-changeable preferences this.prefs = {}; this.prefs.markCurrent = typeof cfg.markCurrent != "undefined" ? @@ -310,67 +311,67 @@ eidogo.Player.prototype = { this.prefs.showNavTree = !this.progressiveLoad && typeof cfg.showNavTree != "undefined" ? !!cfg.showNavTree : false; }, - + /** * Load an SGF file or start from a blank board **/ loadSgf: function(cfg, completeFn) { cfg = cfg || {}; - + this.nowLoading(); - + this.reset(cfg); - + // URL path to SGF files this.sgfPath = cfg.sgfPath || this.sgfPath; - + // Load the first node of the first node by default this.loadPath = cfg.loadPath && cfg.loadPath.length > 1 ? cfg.loadPath : [0, 0]; - + // game name (= file name) of the game to load this.gameName = cfg.gameName || ""; - + // For calling completeFn asynchronously var noCb = false; - + if (typeof cfg.sgf == "string") { - + // raw SGF data var sgf = new eidogo.SgfParser(cfg.sgf); this.load(sgf.root); - + } else if (typeof cfg.sgf == "object") { - + // already-parsed JSON game tree this.load(cfg.sgf); - + } else if (cfg.progressiveLoad && cfg.progressiveUrl) { - + this.progressiveLoads = 0; this.progressiveUrl = cfg.progressiveUrl; this.fetchProgressiveData(completeFn); noCb = true; - + } else if (typeof cfg.sgfUrl == "string" || this.gameName) { - + // the URL can be provided as a single sgfUrl or as sgfPath + gameName if (!cfg.sgfUrl) { cfg.sgfUrl = this.sgfPath + this.gameName + ".sgf"; } - + // load data from a URL this.remoteLoad(cfg.sgfUrl, null, false, null, completeFn); noCb = true; - + if (cfg.progressiveLoad) { this.progressiveLoads = 0; this.progressiveUrl = cfg.progressiveUrl || cfg.sgfUrl.replace(/\?.+$/, ""); } - + } else { - + // start from scratch var boardSize = cfg.boardSize || "19"; var komiMap = {19: 6.5, 13: 4.5, 9: 3.5, 7: 2.5}; @@ -378,7 +379,7 @@ eidogo.Player.prototype = { SZ: boardSize, KM: cfg.komi || komiMap[boardSize] || 6.5, _children: []}]}; - + // AI opponent (e.g. GNU Go) if (cfg.opponentUrl) { this.gameName = "gnugo"; @@ -421,14 +422,14 @@ eidogo.Player.prototype = { } } } - + this.load(blankGame); } if (!noCb && typeof completeFn == "function") { completeFn(); } }, - + /** * Loads game data into a given target. If no target is given, creates * a new gameRoot and initializes the game. @@ -451,7 +452,7 @@ eidogo.Player.prototype = { this.initGame(target._children[gameIndex || 0]); newGame = true; } - + if (this.loadPath.length) { this.goTo(this.loadPath, newGame); if (!this.progressiveLoad) { @@ -460,7 +461,7 @@ eidogo.Player.prototype = { } else { this.refresh(); } - + // find out which color to play as for problem mode if (newGame && this.problemMode) { if (!this.problemColor) @@ -480,9 +481,9 @@ eidogo.Player.prototype = { **/ remoteLoad: function(url, target, useSgfPath, loadPath, completeFn) { useSgfPath = useSgfPath == "undefined" ? true : useSgfPath; - + completeFn = (typeof completeFn == "function") ? completeFn : null; - + if (useSgfPath) { if (!target) { this.gameName = url; @@ -490,11 +491,11 @@ eidogo.Player.prototype = { // if we're using sgfPath, assume url does not include .sgf extension url = this.sgfPath + url + ".sgf"; } - + if (loadPath) { this.loadPath = loadPath; } - + var success = function(req) { var data = req.responseText.replace(/^( |\t|\r|\n)*/, ""); // infer the kind of file we got @@ -515,11 +516,11 @@ eidogo.Player.prototype = { this.croak(t['invalid data']); } } - + var failure = function(req) { this.croak(t['error retrieving']); } - + ajax('get', url, null, success, failure, this, 30000); }, @@ -560,14 +561,14 @@ eidogo.Player.prototype = { this.selectTool(this.mode == "view" ? "view" : "play"); this.hook("initGame"); }, - + /** * Shows or hides interface elements as configured **/ handleDisplayPrefs: function() { (this.prefs.showGameInfo || this.prefs.showPlayerInfo ? show : hide)(this.dom.info); (this.prefs.showGameInfo ? show : hide)(this.dom.infoGame); - (this.prefs.showPlayerInfo ? show : hide)(this.dom.infoPlayers); + (this.prefs.showPlayerInfo ? show : hide)(this.dom.infoPlayers); (this.prefs.showTools ? show : hide)(this.dom.toolsContainer); if (!this.showingSearch) { (this.prefs.showComments ? show : hide)(this.dom.comments); @@ -595,7 +596,7 @@ eidogo.Player.prototype = { } } }, - + /** * Calculates the crop area to use based on the widest distance between * stones and markers in the given game. We're conservative with respect @@ -666,9 +667,9 @@ eidogo.Player.prototype = { size: root.SZ, level: this.opponentLevel }; - ajax('post', this.opponentUrl, params, success, failure, this, 45000); + ajax('post', this.opponentUrl, params, success, failure, this, 45000); }, - + /** * Use GNU Go to estimate the score. * Thanks to Sorin Gherman for the idea and for getting this started! @@ -699,7 +700,7 @@ eidogo.Player.prototype = { }; ajax('post', this.scoreEstUrl, params, success, failure, this, 45000); }, - + /** * Respond to a move made in problem-solving mode **/ @@ -716,20 +717,20 @@ eidogo.Player.prototype = { } }.bind(this), 200); }, - + /** * Navigates to a location within the game. Takes progressive loading * into account. **/ goTo: function(path, fromStart) { fromStart = typeof fromStart != "undefined" ? fromStart : true; - + if (fromStart && path.length > 1 && path[0] != this.cursor.getGameRoot().getPosition()) this.updatedNavTree = false; - + if (fromStart) this.resetCursor(true); - + // Move number var steps = parseInt(path, 10); if (!(path instanceof Array) && !isNaN(steps)) { @@ -739,7 +740,7 @@ eidogo.Player.prototype = { this.refresh(); return; } - + // Not a path? if (!(path instanceof Array) || !path.length) { alert(t['bad path'] + " " + path); @@ -748,7 +749,7 @@ eidogo.Player.prototype = { var position; var vars; - + // Path of moves (SGF coords) if (isNaN(parseInt(path[0], 10))) { if (!this.cursor.node._parent) @@ -770,7 +771,7 @@ eidogo.Player.prototype = { this.refresh(); return; } - + // Path of branch indexes and final move number var first = true; while (path.length) { @@ -802,7 +803,7 @@ eidogo.Player.prototype = { } this.refresh(noRender); }, - + /** * Resets the current color as appropriate **/ @@ -859,19 +860,19 @@ eidogo.Player.prototype = { setTimeout(function() { me.execNode.call(me, noRender); }, 10); return; } - + if (!this.cursor.node) return; - + if (!noRender) { this.dom.comments.innerHTML = ""; this.board.clearMarkers(); this.moveNumber = this.cursor.getMoveNumber(); } - + if (this.moveNumber < 1) { this.resetCurrentColor(); } - + // execute handlers for the appropriate properties var props = this.cursor.node.getProperties(); for (var propName in props) { @@ -882,7 +883,7 @@ eidogo.Player.prototype = { ); } } - + if (noRender) { this.board.commit(); } else { @@ -896,18 +897,18 @@ eidogo.Player.prototype = { this.board.commit(); this.board.render(); } - - // progressive loading? + + // progressive loading? if (!ignoreProgressive && this.progressiveUrl) this.fetchProgressiveData(); - + // play a reponse in problem-solving mode, unless we just navigated backwards if (this.problemMode && this.currentColor && this.currentColor != this.problemColor && !this.goingBack) this.playProblemResponse(noRender); - + this.goingBack = false; }, - + fetchProgressiveData: function(completeFn) { var loadNode = this.cursor.node || null; if (loadNode && loadNode._cached) return; @@ -940,7 +941,7 @@ eidogo.Player.prototype = { this.remoteLoad(url, loadNode, false, null, completeFnWrap); } }, - + fetchProgressiveContinuations: function(completeFn) { this.nowLoading(); this.progressiveLoads++; @@ -991,7 +992,7 @@ eidogo.Player.prototype = { contBranch.C += "
" + "
" + cont.label + "
" + "
" + - "
" + cont.count + "
" + + "
" + cont.count + "
" + "
"; contBranch._children.push(contNode); } @@ -1017,7 +1018,7 @@ eidogo.Player.prototype = { findVariations: function() { this.variations = this.getVariations(); }, - + getVariations: function() { var vars = [], kids = this.cursor.node._children; @@ -1115,11 +1116,11 @@ eidogo.Player.prototype = { **/ handleBoardMouseUp: function(x, y, e) { if (this.domLoading) return; - + this.mouseDown = false; - + var coord = this.pointToSgfCoord({x: x, y: y}); - + // click on a variation? if (this.mode == "view" || this.mode == "play") { for (var i = 0; i < this.variations.length; i++) { @@ -1131,7 +1132,7 @@ eidogo.Player.prototype = { } } } - + if (this.mode == "view") { // Jump to any moved played at the clicked coordinate var root = this.cursor.getGameRoot(), @@ -1149,7 +1150,7 @@ eidogo.Player.prototype = { } return; } - + if (this.mode == "play") { // can't click there? if (!this.rules.check({x: x, y: y}, this.currentColor)) { @@ -1241,7 +1242,7 @@ eidogo.Player.prototype = { if (deleted) this.prependComment(t['position deleted']); } }, - + /** * If there are no properties left in a node, ask whether to delete it **/ @@ -1267,7 +1268,7 @@ eidogo.Player.prototype = { } return false; }, - + /** * This prevents region selection from getting stuck in drag mode **/ @@ -1282,7 +1283,7 @@ eidogo.Player.prototype = { } return true; }, - + /** * Check whether a point falls within a given region (left, top, right, * bottom) @@ -1324,14 +1325,14 @@ eidogo.Player.prototype = { var bounds = this.getRegionBounds(); this.board.renderer.showRegion(bounds); }, - + /** * Tell the board renderer to hide the search region **/ hideRegion: function() { this.board.renderer.hideRegion(); }, - + /** * Converts a board region array to a string suitable for searching **/ @@ -1341,7 +1342,7 @@ eidogo.Player.prototype = { .replace(new RegExp(this.board.BLACK, "g"), "x") .replace(new RegExp(this.board.WHITE, "g"), "o"); }, - + /** * Set up a board position to represent a search pattern, then start * the search @@ -1380,14 +1381,14 @@ eidogo.Player.prototype = { this.cursor.node.pushProperty(c, this.pointToSgfCoord({x:l+x, y:t+y})); } } - + this.refresh(); this.regionLeft = l; this.regionTop = t; this.regionRight = l + x; this.regionBottom = t + y; - + // highlight the selected search region by dimming surroundings var b = this.getRegionBounds(); var r = [b[1], b[0], b[1]+b[2], b[0]+b[3]-1]; @@ -1398,10 +1399,10 @@ eidogo.Player.prototype = { } } } - + this.searchRegion(o); }, - + /** * Call out to our external handler to perform a pattern search. Also * prevent meaningless or overly-simple searches. @@ -1409,21 +1410,21 @@ eidogo.Player.prototype = { searchRegion: function(offset) { if (this.searching) return; this.searching = true; - + if (!this.searchUrl) { show(this.dom.comments); hide(this.dom.searchContainer); this.prependComment(t['no search url']); return; } - + var offset = parseInt(offset, 10) || 0; var algo = this.dom.searchAlgo.value; var bounds = this.getRegionBounds(); var region = this.board.getRegion(bounds[0], bounds[1], bounds[2], bounds[3]); var pattern = this.convertRegionPattern(region); - + // check for empty or meaningless searches var empty = /^\.*$/.test(pattern); var oneW = /^\.*o\.*$/.test(pattern); @@ -1435,7 +1436,7 @@ eidogo.Player.prototype = { this.prependComment(t['two stones']); return; } - + // make sure corner search regions touch two adjacent edges of the board var edges = []; if (bounds[0] == 0) edges.push('n'); @@ -1453,10 +1454,10 @@ eidogo.Player.prototype = { this.prependComment(t['two edges']); return; } - + var quadrant = (edges.contains('n') ? "n" : "s"); quadrant += (edges.contains('w') ? "w" : "e"); - + this.showComments(""); this.gameName = "search"; @@ -1524,18 +1525,18 @@ eidogo.Player.prototype = { o: offset, t: (new Date()).getTime() }; - + this.progressiveLoad = false; this.progressiveUrl = null; this.prefs.markNext = false; this.prefs.showPlayerInfo = true; - + this.hook("searchRegion", params); - + this.nowLoading(); - ajax('get', this.searchUrl, params, success, failure, this, 45000); + ajax('get', this.searchUrl, params, success, failure, this, 45000); }, - + /** * Load a particular search result. This gets called via the HTML * output by the external search handler. @@ -1567,7 +1568,7 @@ eidogo.Player.prototype = { }.bind(this)); stopEvent(e); }, - + /** * Close the search pane **/ @@ -1651,7 +1652,7 @@ eidogo.Player.prototype = { var charCode = e.keyCode || e.charCode; if (!charCode || e.ctrlKey || e.altKey || e.metaKey) return true; var charKey = String.fromCharCode(charCode).toLowerCase(); - + // Variations can be selected by pressing the appropriate alphanumberic // character that is either 1) its variation number; or 2) its // marker label. @@ -1673,13 +1674,13 @@ eidogo.Player.prototype = { } varMoves.push(varMove); } - + // tool shortcuts - if (charCode == 112 || charCode == 27) { + if (charCode == 112 || charCode == 27) { // f1 or esc this.selectTool("play"); } - + var stop = true; switch (charCode) { case 39: // right @@ -1794,7 +1795,7 @@ eidogo.Player.prototype = { this.mode = tool; this.dom.toolsSelect.value = tool; }, - + startEditComment: function() { this.closeSearch(); var div = this.dom.commentsEdit; @@ -1807,9 +1808,9 @@ eidogo.Player.prototype = { show(this.dom.commentsEditDone); this.dom.commentsEditTa.value = this.cursor.node.C || ""; this.dom.commentsEditTa.focus(); - this.editingText = true; + this.editingText = true; }, - + finishEditComment: function() { this.editingText = false; var oldC = this.cursor.node.C; @@ -1828,7 +1829,7 @@ eidogo.Player.prototype = { this.refresh(); if (deleted) this.prependComment(t['position deleted']); }, - + startEditGameInfo: function() { this.closeSearch(); var div = this.dom.gameInfoEdit; @@ -1854,7 +1855,7 @@ eidogo.Player.prototype = { }, 0); this.editingText = true; }, - + finishEditGameInfo: function() { this.editingText = false; hide(this.dom.shade); @@ -1883,13 +1884,13 @@ eidogo.Player.prototype = { this.dom.moveNumber.innerHTML = (this.moveNumber ? (t['move'] + " " + this.moveNumber) : (this.permalinkable ? "permalink" : "")); - + // captures this.dom.whiteCaptures.innerHTML = t['captures'] + ": " + this.board.captures.W + ""; this.dom.blackCaptures.innerHTML = t['captures'] + ": " + this.board.captures.B + ""; - + // time this.dom.whiteTime.innerHTML = t['time left'] + ": " + (this.timeW ? this.timeW : "--") + ""; @@ -1897,7 +1898,7 @@ eidogo.Player.prototype = { (this.timeB ? this.timeB : "--") + ""; removeClass(this.dom.controlPass, "pass-on"); - + // variations? this.dom.variations.innerHTML = ""; for (var i = 0; i < this.variations.length; i++) { @@ -1938,7 +1939,7 @@ eidogo.Player.prototype = { this.dom.variations.innerHTML = "
" + t['no variations'] + "
"; } - + // nav buttons if (this.cursor.hasNext()) { addClass(this.dom.controlForward, "forward-on"); @@ -1961,13 +1962,13 @@ eidogo.Player.prototype = { if (info.length && this.theme != "problem") this.prependComment(info, "comment-info"); } - + // nav slider & nav tree if (!this.progressiveLoad) this.updateNavSlider(); if (this.prefs.showNavTree) this.updateNavTree(); - + // multiple games per sgf var node = this.cursor.node, pos, html, js; if (node._parent && !node._parent._parent && node._parent._children.length > 1) { @@ -2101,24 +2102,34 @@ eidogo.Player.prototype = { this.dom.comments.innerHTML = "
" + content + "
" + this.dom.comments.innerHTML; }, - + /** * Redirect to a download handler or attempt to display data inline **/ downloadSgf: function(evt) { - stopEvent(evt); + // stopEvent(evt); if (this.downloadUrl) { if (this.unsavedChanges) { alert(t['unsaved changes']); return; } location.href = this.downloadUrl + this.gameName; + + } else if (downloadElement) { + var blob = new Blob([this.cursor.getGameRoot().toSgf()], + {'type' : 'text/plain'}); + + var downloadLink = document.getElementsByClassName("option-download")[0]; + downloadLink.href = window.URL.createObjectURL(blob); + // possibly should unload this + downloadLink.download = "eidogogame.sgf"; + return true; } else if (isMoz) { location.href = "data:text/plain," + encodeURIComponent(this.cursor.getGameRoot().toSgf()); } }, - + /** * Send SGF data to a file-saving handler **/ @@ -2140,7 +2151,7 @@ eidogo.Player.prototype = { * appropriate event handlers. **/ constructDom: function() { - + this.dom.player = document.createElement('div'); this.dom.player.className = "eidogo-player" + (this.theme ? " theme-" + this.theme : ""); @@ -2148,7 +2159,7 @@ eidogo.Player.prototype = { this.dom.container.innerHTML = ""; eidogo.util.show(this.dom.container); this.dom.container.appendChild(this.dom.player); - + var domHtml = "\
\
\ @@ -2241,7 +2252,7 @@ eidogo.Player.prototype = {
\
\ " + (this.saveUrl ? "" + t['save to server'] + "" : "") + "\ - " + (this.downloadUrl || isMoz ? "" + t['download sgf'] + "" : "") + "\ + " + (this.downloadUrl || downloadElement || isMoz ? "" + t['download sgf'] + "" : "") + "\
\
\
\ @@ -2251,13 +2262,13 @@ eidogo.Player.prototype = { \
\ "; - + // unique ids for each element so we can have multiple Player // instances on a page domHtml = domHtml.replace(/ id='([^']+)'/g, " id='$1-" + this.uniq + "'"); - + this.dom.player.innerHTML = domHtml; - + // grab all the dom elements for later use var re = / id='([^']+)-\d+'/g; var match; @@ -2273,7 +2284,7 @@ eidogo.Player.prototype = { }); this.dom[jsName] = byId(id); } - + // dom element handler [['moveNumber', 'setPermalink'], ['controlFirst', 'first'], @@ -2293,29 +2304,29 @@ eidogo.Player.prototype = { ].forEach(function(eh) { if (this.dom[eh[0]]) onClick(this.dom[eh[0]], this[eh[1]], this); }.bind(this)); - + addEvent(this.dom.toolsSelect, 'change', function(e) { this.selectTool.apply(this, [(e.target || e.srcElement).value]); }, this, true); }, - + enableNavSlider: function() { // don't use slider for progressively-loaded games if (this.progressiveLoad) { - hide(this.dom.navSliderThumb); + hide(this.dom.navSliderThumb); return; } - + this.dom.navSlider.style.cursor = "pointer"; var sliding = false; var timeout = null; - + addEvent(this.dom.navSlider, "mousedown", function(e) { sliding = true; stopEvent(e); }, this, true); - + addEvent(document, "mousemove", function(e) { if (!sliding) return; var xy = getElClickXY(e, this.dom.navSlider); @@ -2325,7 +2336,7 @@ eidogo.Player.prototype = { }.bind(this), 10); stopEvent(e); }, this, true); - + addEvent(document, "mouseup", function(e) { if (!sliding) return true; sliding = false; @@ -2334,7 +2345,7 @@ eidogo.Player.prototype = { return true; }, this, true); }, - + updateNavSlider: function(offset) { var width = this.dom.navSlider.offsetWidth - this.dom.navSliderThumb.offsetHeight; var steps = this.totalMoves; @@ -2361,12 +2372,12 @@ eidogo.Player.prototype = { this.doneLoading(); this.refresh(); } - + // snap to move interval offset = parseInt(moveOffset / steps * width, 10) || 0; this.dom.navSliderThumb.style.left = offset + "px"; }, - + /** * Construct a navigation tree from scratch, assuming it hasn't been done * already and no unsaved additions have been made. @@ -2420,7 +2431,7 @@ eidogo.Player.prototype = { })) { y++; } - do { + do { if (!navGrid[y]) navGrid[y] = []; cur.node = node; @@ -2457,7 +2468,7 @@ eidogo.Player.prototype = { } else { showLine = false; } - } + } } for (y = 0; y < navGrid.length; y++) { html.push("
"); @@ -2491,7 +2502,7 @@ eidogo.Player.prototype = { this.showNavTreeCurrent(); }.bind(this), 0); }, - + showNavTreeCurrent: function() { var id = "navtree-node-" + this.cursor.getPath().join("-"), current = byId(id); @@ -2523,7 +2534,7 @@ eidogo.Player.prototype = { if (t + h > maxb) ntc.scrollTop += ((t + h) - maxb); }, - + navTreeClick: function(e) { var target = e.target || e.srcElement; if (!target || !target.id) return; @@ -2536,7 +2547,7 @@ eidogo.Player.prototype = { this.labelLastNumber = 1; this.labelLastLetter = "A"; }, - + getGameDescription: function(excludeGameName) { var root = this.cursor.getGameRoot(); if (!root) return; @@ -2620,7 +2631,7 @@ eidogo.Player.prototype = { this.domLoading.innerHTML = msg; this.dom.player.appendChild(this.domLoading); }, - + doneLoading: function() { if (this.domLoading && this.domLoading != null && this.domLoading.parentNode) { this.domLoading.parentNode.removeChild(this.domLoading); @@ -2642,5 +2653,5 @@ eidogo.Player.prototype = { } } }; - + })(); diff --git a/player/js/util.js b/player/js/util.js index edc2e89..335583a 100644 --- a/player/js/util.js +++ b/player/js/util.js @@ -6,10 +6,10 @@ * * General-purpose utility functions. */ - + (function() { -// browser detection +// browser detection var ua = navigator.userAgent.toLowerCase(); var uav = (ua.match(/.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/) || [])[1]; eidogo.browser = {ua: ua, ver: uav, ie: /msie/.test(ua) && !/opera/.test(ua), @@ -21,7 +21,15 @@ eidogo.util = { byId: function(id) { return document.getElementById(id); }, - + + downloadElement: function () { + return 'download' in document.createElement('a'); + }, + + dataUri: function () { + return; + }, + makeQueryString: function(params) { var qs = ""; if (params && typeof params == "object") { @@ -41,7 +49,7 @@ eidogo.util = { } return qs; }, - + // Adapted from jQuery ajax: function(method, url, params, successFn, failureFn, scope, timeout) { method = method.toUpperCase(); @@ -85,7 +93,7 @@ eidogo.util = { xhr = null; } } - var ival = setInterval(handleReadyState, 13); + var ival = setInterval(handleReadyState, 13); if (timeout) setTimeout(function() { if (xhr) { @@ -97,7 +105,7 @@ eidogo.util = { xhr.send(qs); return xhr; }, - + // written by Dean Edwards, 2005 // with input from Tino Zijdel, Matthias Miller, Diego Perini // http://dean.edwards.name/weblog/2005/10/add-event/ @@ -142,7 +150,7 @@ eidogo.util = { } return returnValue; }, - + addEvent: function(el, eventType, handler, arg, override) { if (!el) return; if (override) { @@ -156,11 +164,11 @@ eidogo.util = { } eidogo.util.addEventHelper(el, eventType, handler); }, - + onClick: function(el, handler, scope) { eidogo.util.addEvent(el, "click", handler, scope, true); }, - + getElClickXY: function(e, el, noScroll) { var doc = el.ownerDocument; // for IE @@ -173,7 +181,7 @@ eidogo.util = { var elXY = eidogo.util.getElXY(el, noScroll); return [e.pageX - elXY[0], e.pageY - elXY[1]]; }, - + stopEvent: function(e) { if (!e) return; if (e.stopPropagation) { @@ -187,13 +195,13 @@ eidogo.util = { e.returnValue = false; } }, - + getTarget: function(ev) { var t = ev.target || ev.srcElement; return (t && t.nodeName && t.nodeName.toUpperCase() == "#TEXT") ? t.parentNode : t; }, - + addClass: function(el, cls) { if (!cls) return; var ca = cls.split(/\s+/); @@ -221,7 +229,7 @@ eidogo.util = { } return false; }, - + show: function(el, display) { display = display || "block"; if (typeof el == "string") { @@ -230,7 +238,7 @@ eidogo.util = { if (!el) return; el.style.display = display; }, - + hide: function(el) { if (typeof el == "string") { el = eidogo.util.byId(el); @@ -238,7 +246,7 @@ eidogo.util = { if (!el) return; el.style.display = "none"; }, - + getElXY: function(el, noScroll) { var node = el, elX = 0, elY = 0, parent = el.parentNode, sx = 0, sy = 0, doc = el.ownerDocument; @@ -262,15 +270,15 @@ eidogo.util = { } return [elX, elY, sx, sy]; }, - + getElX: function(el) { return this.getElXY(el)[0]; }, - + getElY: function(el) { return this.getElXY(el)[1]; }, - + addStyleSheet: function(href) { if (document.createStyleSheet) { document.createStyleSheet(href); @@ -282,7 +290,7 @@ eidogo.util = { document.getElementsByTagName("head")[0].appendChild(link); } }, - + getPlayerPath: function() { var scripts = document.getElementsByTagName('script'); var scriptPath; @@ -294,13 +302,13 @@ eidogo.util = { } return scriptPath; }, - + numProperties: function(obj) { var count = 0; for (var i in obj) count++; return count; } - + }; })(); \ No newline at end of file