function StreamUtilities() { //this is a stupid hack, used here because P4JsApi and p4 handle forms differently var useP4JsApi = false; var p4ServerConnection; var threadCount = 0; try { //this will fail if there is no ClientKit (e.g. if we're in P4V using P4JsApi console.log(ClientKit.system.currentDir()); } catch(err) { p4ServerConnection = P4JsApi; useP4JsApi = true; } var mGitRepoStorageHandler; //this method does not get called by the P4V client, because the connection is already established this.setP4Server = function(port, user, client ) { var connectObj = {'port':port,'user':user,'client':client}; p4ServerConnection = ClientKit.p4.createP4(connectObj) mGitRepoStorageHandler = new GitRepoStorageHandler(user, this); useP4JsApi = false; //this bit makes ClientKit emulate P4JsApi p4ServerConnection.p4 = function(/*command, callback*/) { var command = arguments[0]; var callback; var form; if( arguments[1] ) { if( typeof(arguments[1]) == 'function' ) callback = arguments[1]; else form = arguments[1]; } if( arguments[2] && typeof(arguments[2]) == 'function' ) callback = arguments[2]; var results; //var temp = false; //if( callback ) // temp = true; //console.log('command: ' + command + ' callback: ' + temp); if( callback ) results = p4ServerConnection.run(command, callback); else if( form ) results = p4ServerConnection.run(command, form); else results = p4ServerConnection.run(command); return results; } } this.getPort = function() { return p4ServerConnection.getPort(); } this.canAccessLocalHarddrive = function() { return !useP4JsApi; } this.currentDepot; this.loadStreams = function(depot, callback) { this.currentDepot = depot; p4ServerConnection.p4('streams -F Stream~=//' + depot, callback); } this.GetTextWidth = function(text, element, font) { var textTester = document.getElementById( "TextTester" ); if( font ) textTester.style.font = font; else textTester.style.font = element.font; textTester.innerText = text;//P4JsApi.encodeForHTML(text); return textTester.clientWidth + 1; } this.ElideTextToWidth = function( text, size, element, font ) { var width = this.GetTextWidth( text, element, font ); if( width < size ) return text;//doesn't need to be elided //take off the last letter of the text and check if it fits. If it does, //take off three more letters and add '...'. If it doesn't fit, lather, rinse, repeat. while( width > size ) { var buf = ''; for( var i = 0; i < text.length - 1; i++ ) buf += text.charAt(i); text = buf; width = this.GetTextWidth( text, element ); } var buf = ''; for( var i = 0; i < text.length - 3; i++ ) buf += text.charAt(i); return buf + '...'; } //Given two vectors, returns a vector that represents a normalized (unit) vector //perpendicular to the given vectors this.GetNormalizedPerpendicularVector = function(vectorA, vectorB) { //get the unit vector var x = vectorB.X - vectorA.X; var y = vectorB.Y - vectorA.Y; var distance = Math.sqrt(x * x + y * y); x /= distance; y /= distance; //get the perpendicular vector of the unit vector var storeY = y; y = -x; x = storeY; return new this.Vector(x, y); } this.Vector = function(x, y) { this.X = x; this.Y = y; } this.Rectangle = function(x,y,width,height) { this.x = x; this.y = y; this.width = width; this.height = height; } this.StreamColor = function(r, g, b, a) { this.r = r; this.g = g; this.b = b; this.a = a; this.rgb = 'rgb(' + r + ',' + g + ',' + b + ')'; this.rgba = 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')'; } this.Point = function(x, y) { this.x = x; this.y = y; } //this is a enum, to use it do something like StreamType.mainLine //TODO: need to check out other ways of doing enums in javascript this.StreamType = { "mainLine" : 0, "development" : 1, // down "staging" : 2, // up "release" : 3, // up one more "experimental" : 4, "gitRepo" : 5, "gitBranch" : 6, "typeCount" : 7 // DNE }; this.typeFromString = function(typeString) { switch(typeString) { case 'mainline': return this.StreamType.mainLine; case 'development': return this.StreamType.development; case 'staging': return this.StreamType.staging; case 'release': return this.StreamType.release; case 'experimental': return this.StreamType.experimental; case 'git repo': return this.StreamType.gitRepo; case 'git branch': return this.StreamType.gitBranch; case 'typeCount': return this.StreamType.typeCount; default: return this.StreamType.typeCount; } } this.typeToString = function( type ) { switch (type) { case this.StreamType.mainLine: return "mainline"; case this.StreamType.development: return "development"; case this.StreamType.staging: return "staging"; case this.StreamType.release: return "release"; case this.StreamType.gitRepo: return "git repo"; case this.StreamType.gitBranch: return "git branch"; default: return "unk"; } } this.depotFromDepotPath = function( depotPath ) { var buf = depotPath.replace("//", ""); var tokens = buf.split("/"); return tokens[0]; } this.streamFromDepotPath = function( depotPath ) { var buf = depotPath.replace("//", ""); var tokens = buf.split("/"); if( tokens.length > 1 ) return '//' + tokens[0] + '/' + tokens[1]; return null; } this.clearCanvas = function(canvas) { var context = canvas.getContext( '2d' ); context.clearRect( 0, 0, canvas.width, canvas.height ); } this.Direction = { "up" : 1, "down" : -1, "unknown" : 0 }; this.ArrowDirection = { "top" : 0, "bottom" : 1, "none" : 2 }; this.LinkThickness = { "thin" : 0, "thick" : 1, "none" : 2 }; this.LinkType = { "Default" : 0, "Parallel" : 1 }; this.CurveDirection = { "bottomToTop" : 0, "topToBottom" : 1, "nShaped" : 2, "uShaped" : 3 }; this.AddSelectOption = function(selectObj, text, value, isSelected) { if (selectObj != null && selectObj.options != null) { selectObj.options[selectObj.options.length] = new Option(text, value, false, isSelected); } } this.CreateRoundedRectangle = function(x, y, width, height, radius, context) { if( radius * 2 > width ) radius = width / 2; context.beginPath(); context.lineTo(x,y+height-radius);//start at lower left corner - the radius context.quadraticCurveTo(x,y+height,x+radius,y+height);//curve around to bottom context.lineTo(x+width-radius,y+height);//draw bottom line context.quadraticCurveTo(x+width,y+height,x+width,y+height-radius);//curve around to right side context.lineTo(x+width,y+radius);//draw right side context.quadraticCurveTo(x+width,y,x+width-radius,y);//curve around to top context.lineTo(x+radius,y);//draw top context.quadraticCurveTo(x,y,x,y+radius);//curve around to left context.closePath();//draws the left side } this.GetGradientFill = function(color, context, canvas, vertical) { var linearGradient = context.createLinearGradient(0,0,0,canvas.height); if( vertical ) linearGradient = context.createLinearGradient(0,0,canvas.width,0); linearGradient.addColorStop(0, 'white'); linearGradient.addColorStop(0.7, color); linearGradient.addColorStop(0.7, color); linearGradient.addColorStop(1, 'white'); return linearGradient; } this.DrawColorBezier = function(linkCanvas, color, bottomToTop, arrowDirection, linkThickness, zIndex, parentCanvas, horizontalLayout) { linkCanvas.style.zIndex = zIndex; var strokeNudge = 4; if( linkThickness == this.LinkThickness.thick ) strokeNudge = 16; else if( linkThickness == this.LinkThickness.none ) strokeNudge = 0; var arrowHeight = 0; if( arrowDirection == this.ArrowDirection.bottom || arrowDirection == this.ArrowDirection.top ) arrowHeight = parentCanvas.height/4; var lineWidth = strokeNudge / 2; var linkContext = linkCanvas.getContext( '2d' ); linkCanvas.style.left = linkCanvas.offsetLeft - strokeNudge; linkCanvas.width = linkCanvas.width + strokeNudge*2; if( bottomToTop ) { linkContext.beginPath(); if( !horizontalLayout ) { if(arrowDirection == this.ArrowDirection.bottom) { linkContext.moveTo(strokeNudge, linkCanvas.height - arrowHeight); linkContext.bezierCurveTo(strokeNudge, linkCanvas.height/2, linkCanvas.width - strokeNudge, linkCanvas.height/2, linkCanvas.width - strokeNudge, 0); linkContext.bezierCurveTo(linkCanvas.width - strokeNudge, linkCanvas.height/2, strokeNudge, linkCanvas.height/2, strokeNudge, linkCanvas.height - arrowHeight); } else if(arrowDirection == this.ArrowDirection.top) { linkContext.moveTo(strokeNudge, linkCanvas.height); linkContext.bezierCurveTo(strokeNudge, linkCanvas.height/2, linkCanvas.width - strokeNudge, linkCanvas.height/2, linkCanvas.width - strokeNudge, arrowHeight); linkContext.bezierCurveTo(linkCanvas.width - strokeNudge, linkCanvas.height/2, strokeNudge, linkCanvas.height/2, strokeNudge, linkCanvas.height); } else { linkContext.moveTo(strokeNudge, linkCanvas.height); linkContext.bezierCurveTo(strokeNudge, linkCanvas.height/2, linkCanvas.width - strokeNudge, linkCanvas.height/2, linkCanvas.width - strokeNudge, 0); linkContext.bezierCurveTo(linkCanvas.width - strokeNudge, linkCanvas.height/2, strokeNudge, linkCanvas.height/2, strokeNudge, linkCanvas.height - arrowHeight); } } else { var sn = strokeNudge; var rh = 16;//linkCanvas.height / 16;//sn * 4; var cw = 24;//linkCanvas.width / 8; var initX = 12; var h = linkCanvas.height; var w = linkCanvas.width; var terminalY = 4; if( w < 100 )//TODO: this is going to bite me one day; the idea is to have the merge connection be shorter than the promote connection. If the width changes, this'll break...I need a constant... terminalY = 1; linkContext.moveTo(0, h - sn); linkContext.lineTo(initX, h - sn); linkContext.quadraticCurveTo(w - 3 * cw, terminalY, w - sn, terminalY); linkContext.lineTo(w - sn, 0); //now reverse... linkContext.lineTo(w - sn, terminalY); linkContext.quadraticCurveTo(w - 3 * cw, terminalY, initX, h - sn); linkContext.lineTo(0, h - sn); } linkContext.closePath(); } else { linkContext.beginPath(); if( !horizontalLayout ) { if(arrowDirection == this.ArrowDirection.bottom) { linkContext.moveTo(strokeNudge, 0); linkContext.bezierCurveTo(strokeNudge, linkCanvas.height/2, linkCanvas.width - strokeNudge, linkCanvas.height/2, linkCanvas.width - strokeNudge, linkCanvas.height - arrowHeight); linkContext.bezierCurveTo(linkCanvas.width - strokeNudge, linkCanvas.height/2, strokeNudge, linkCanvas.height/2, strokeNudge, 0); } else if(arrowDirection == this.ArrowDirection.top) { linkContext.moveTo(strokeNudge, arrowHeight); linkContext.bezierCurveTo(strokeNudge, linkCanvas.height/2, linkCanvas.width - strokeNudge, linkCanvas.height/2, linkCanvas.width - strokeNudge, linkCanvas.height); linkContext.bezierCurveTo(linkCanvas.width - strokeNudge, linkCanvas.height/2, strokeNudge, linkCanvas.height/2, strokeNudge, arrowHeight); } else { linkContext.moveTo(strokeNudge, 0); linkContext.bezierCurveTo(strokeNudge, linkCanvas.height/2, linkCanvas.width - strokeNudge, linkCanvas.height/2, linkCanvas.width - strokeNudge, linkCanvas.height); linkContext.bezierCurveTo(linkCanvas.width - strokeNudge, linkCanvas.height/2, strokeNudge, linkCanvas.height/2, strokeNudge, 0); } } else { var sn = strokeNudge; var rh = 16;//linkCanvas.height / 16;//sn * 4; var cw = 24;//linkCanvas.width / 8; var initX = 12; var h = linkCanvas.height; var w = linkCanvas.width; //console.log('What\'s the width? ' + w); var terminalY = 4; if( w < 100 ){//TODO: this is going to bite me one day; the idea is to have the merge connection be shorter than the promote connection. If the width changes, this'll break...I need a constant... terminalY = 1; //cw = 12; } linkContext.moveTo(0, sn); linkContext.lineTo(initX, sn); linkContext.quadraticCurveTo(w - 3 * cw, h - terminalY, w - sn, h - terminalY); linkContext.lineTo(w - sn, h); //now reverse... linkContext.lineTo(w - sn, h - terminalY); linkContext.quadraticCurveTo(w - 3 * cw, h - terminalY, initX, sn); linkContext.lineTo(0, sn); } linkContext.closePath(); } linkContext.strokeStyle = color.rgba; linkContext.lineWidth = lineWidth; //console.log('linkContext.lineWidth = ' + linkContext.lineWidth + ' x,y,height,width: ' + linkCanvas.style.left + ', ' + linkCanvas.style.top + ', ' + linkCanvas.height + ', ' + linkCanvas.width); linkContext.stroke(); //now draw the arrows... var arrowPoint; linkContext.beginPath(); if( arrowDirection == this.ArrowDirection.bottom ) { if( bottomToTop ) { linkContext.moveTo(strokeNudge/2, linkCanvas.height - arrowHeight); linkContext.lineTo(strokeNudge*2 - strokeNudge/2, linkCanvas.height - arrowHeight); linkContext.lineTo(strokeNudge, linkCanvas.height); arrowPoint = this.Point(parseInt(linkCanvas.style.left) + strokeNudge, parseInt(linkCanvas.style.top) + linkCanvas.height); linkContext.lineTo(strokeNudge/2, linkCanvas.height - arrowHeight); } else { linkContext.moveTo(linkCanvas.width - (strokeNudge + strokeNudge/2), linkCanvas.height - arrowHeight); linkContext.lineTo(linkCanvas.width - strokeNudge/2, linkCanvas.height - arrowHeight); linkContext.lineTo(linkCanvas.width - strokeNudge, linkCanvas.height); arrowPoint = this.Point(parseInt(linkCanvas.style.left) + (linkCanvas.width - strokeNudge), parseInt(linkCanvas.style.top) + linkCanvas.height); linkContext.lineTo(linkCanvas.width - (strokeNudge + strokeNudge/2), linkCanvas.height - arrowHeight); } } else if( arrowDirection == this.ArrowDirection.top ) { if( !bottomToTop ) { linkContext.moveTo(strokeNudge/2, arrowHeight); linkContext.lineTo(strokeNudge*2 - strokeNudge/2, arrowHeight); linkContext.lineTo(strokeNudge, 0); arrowPoint = this.Point(parseInt(linkCanvas.style.left) + strokeNudge, parseInt(linkCanvas.style.top)); linkContext.lineTo(strokeNudge/2, arrowHeight); } else { linkContext.moveTo(linkCanvas.width - (strokeNudge + strokeNudge/2), arrowHeight); linkContext.lineTo(linkCanvas.width - strokeNudge/2, arrowHeight); linkContext.lineTo(linkCanvas.width - strokeNudge, 0); arrowPoint = this.Point(parseInt(linkCanvas.style.left) + (linkCanvas.width - strokeNudge), parseInt(linkCanvas.style.top)); linkContext.lineTo(linkCanvas.width - (strokeNudge + strokeNudge/2), arrowHeight); } } linkContext.closePath(); linkContext.fillStyle = color.rgba; linkContext.fill(); return arrowPoint; } this.DrawHorizontalColorBezier = function(linkCanvas, color, curveDirection, linkThickness, zIndex, arrowDirection) { //console.log('DrawColorBezier stream: ' + this.stream + ' arrowDirection: ' + arrowDirection); linkCanvas.style.zIndex = zIndex; var strokeNudge = 4; if( linkThickness == this.LinkThickness.thick ) strokeNudge = 16; else if( linkThickness == this.LinkThickness.none ) strokeNudge = 0; var arrowHeight = 0; if( arrowDirection == this.ArrowDirection.bottom || arrowDirection == this.ArrowDirection.top ) arrowHeight = linkCanvas.height/14; var lineWidth = strokeNudge / 2; var linkContext = linkCanvas.getContext( '2d' ); linkCanvas.style.left = linkCanvas.offsetLeft - strokeNudge; linkCanvas.width = linkCanvas.width + strokeNudge*2; linkContext.beginPath(); var halfHeight = linkCanvas.height/2; if( curveDirection == this.CurveDirection.bottomToTop ) { linkContext.moveTo(strokeNudge, linkCanvas.height); linkContext.bezierCurveTo(strokeNudge, halfHeight, linkCanvas.width/2, halfHeight, linkCanvas.width - strokeNudge, 0); linkContext.bezierCurveTo( (linkCanvas.width)/2, halfHeight, strokeNudge, halfHeight, strokeNudge, linkCanvas.height - arrowHeight); } else if( curveDirection == this.CurveDirection.topToBottom ) { linkContext.moveTo(strokeNudge, 0); linkContext.bezierCurveTo(strokeNudge, halfHeight, linkCanvas.width/2,halfHeight, linkCanvas.width - strokeNudge, linkCanvas.height); linkContext.bezierCurveTo(linkCanvas.width/2, halfHeight, strokeNudge, halfHeight, strokeNudge, 0); } else if( curveDirection == this.CurveDirection.nShaped ) { linkContext.moveTo(strokeNudge, linkCanvas.height); linkContext.bezierCurveTo(0, 0, linkCanvas.width, 0, linkCanvas.width - strokeNudge, linkCanvas.height); linkContext.bezierCurveTo(linkCanvas.width, 0, 0, 0, strokeNudge, linkCanvas.height); } else if( curveDirection == this.CurveDirection.uShaped ) { linkContext.moveTo(strokeNudge, arrowHeight); linkContext.bezierCurveTo(arrowHeight, halfHeight, linkCanvas.width, halfHeight, linkCanvas.width - strokeNudge, 0); linkContext.bezierCurveTo(linkCanvas.width, halfHeight, 0, halfHeight, strokeNudge, arrowHeight); } //linkCanvas.style.background = 'rgba(155, 155, 155, .5)' //now draw the arrows... var arrowPoint; var bottomToTop = curveDirection == this.CurveDirection.nShaped; if( arrowDirection == this.ArrowDirection.bottom ) { if( bottomToTop ) { linkContext.moveTo(strokeNudge/2, linkCanvas.height - arrowHeight); linkContext.lineTo(strokeNudge*2 - strokeNudge/2, linkCanvas.height - arrowHeight); linkContext.lineTo(strokeNudge, linkCanvas.height); arrowPoint = this.Point(parseInt(linkCanvas.style.left) + strokeNudge, parseInt(linkCanvas.style.top) + linkCanvas.height); linkContext.lineTo(strokeNudge/2, linkCanvas.height - arrowHeight); } else { linkContext.moveTo(linkCanvas.width - (strokeNudge + strokeNudge/2), linkCanvas.height - arrowHeight); linkContext.lineTo(linkCanvas.width - strokeNudge/2, linkCanvas.height - arrowHeight); linkContext.lineTo(linkCanvas.width - strokeNudge, linkCanvas.height); arrowPoint = this.Point(parseInt(linkCanvas.style.left) + (linkCanvas.width - strokeNudge), parseInt(linkCanvas.style.top) + linkCanvas.height); linkContext.lineTo(linkCanvas.width - (strokeNudge + strokeNudge/2), linkCanvas.height - arrowHeight); } } else if( arrowDirection == this.ArrowDirection.top ) { if( !bottomToTop ) { var divFactor = 1.2; linkContext.moveTo(strokeNudge/divFactor, arrowHeight); linkContext.lineTo(strokeNudge*2 - strokeNudge/divFactor, arrowHeight); linkContext.lineTo(strokeNudge, arrowHeight/2); arrowPoint = this.Point(parseInt(linkCanvas.style.left) + strokeNudge, parseInt(linkCanvas.style.top)); linkContext.lineTo(strokeNudge/divFactor, arrowHeight); } else { linkContext.moveTo(linkCanvas.width - (strokeNudge + strokeNudge/2), arrowHeight); linkContext.lineTo(linkCanvas.width - strokeNudge/2, arrowHeight); linkContext.lineTo(linkCanvas.width - strokeNudge, 0); arrowPoint = this.Point(parseInt(linkCanvas.style.left) + (linkCanvas.width - strokeNudge), parseInt(linkCanvas.style.top)); linkContext.lineTo(linkCanvas.width - (strokeNudge + strokeNudge/2), arrowHeight); } } linkContext.closePath(); linkContext.strokeStyle = color.rgba; linkContext.lineWidth = lineWidth; //console.log('linkContext.lineWidth = ' + linkContext.lineWidth + ' x,y,height,width: ' + linkCanvas.style.left + ', ' + linkCanvas.style.top + ', ' + linkCanvas.height + ', ' + linkCanvas.width); linkContext.stroke(); } this.tractorBeam = function(topPoint, bottomPoint, beamUp) { if( !topPoint || !bottomPoint ) { if( !topPoint ) console.log('tractorBeam failed because the top point specified does not exist'); else console.log('tractorBeam failed because the bottom point specified does not exist'); return; } var cowImage = document.createElement( "img" ); cowImage.style.position = "absolute"; $(cowImage).hide(); document.body.appendChild( cowImage ); var tractorBeamCanvas = document.createElement( "canvas" ); tractorBeamCanvas.style.position = "absolute"; document.body.appendChild( tractorBeamCanvas ); //tractorBeamCanvas.style.background = 'red'; tractorBeamCanvas.style.top = topPoint.Y; var leanRight = topPoint.X > bottomPoint.X; //cowImage.style.background = 'rgba(255, 0, 0, 1)'; cowImage.width = 61; cowImage.height = 43; var halfACowWidth = cowImage.width/2; var topY = topPoint.Y - cowImage.height; if( beamUp ) { cowImage.style.left = bottomPoint.X - halfACowWidth; cowImage.style.top = bottomPoint.Y; } else { cowImage.style.left = topPoint.X - halfACowWidth; cowImage.style.top = topPoint.Y; } cowImage.src = "css/images/cow.gif"; tractorBeamCanvas.width = leanRight ? (topPoint.X + halfACowWidth) - (bottomPoint.X - halfACowWidth) : (bottomPoint.X + cowImage.width/2) - (topPoint.X - cowImage.width/2); tractorBeamCanvas.style.left = leanRight ? bottomPoint.X - halfACowWidth : topPoint.X - halfACowWidth; //draw the tractor beam var tractorContext = tractorBeamCanvas.getContext( '2d' ); tractorContext.beginPath(); var topWidth = 6; var bezRadius = 10; tractorBeamCanvas.height = bottomPoint.Y - topY; if( leanRight ) { tractorContext.moveTo((tractorBeamCanvas.width - halfACowWidth) - topWidth, bezRadius); tractorContext.quadraticCurveTo(tractorBeamCanvas.width - halfACowWidth, 0, (tractorBeamCanvas.width - halfACowWidth) + topWidth, bezRadius); tractorContext.lineTo(2*halfACowWidth, tractorBeamCanvas.height - 2*bezRadius); tractorContext.bezierCurveTo(2*halfACowWidth, tractorBeamCanvas.height, 0, tractorBeamCanvas.height, 0, tractorBeamCanvas.height - 2*bezRadius); tractorContext.lineTo((tractorBeamCanvas.width - halfACowWidth) - topWidth, bezRadius); } else { tractorContext.moveTo(halfACowWidth - topWidth, bezRadius); tractorContext.quadraticCurveTo(halfACowWidth, 0, halfACowWidth + topWidth, bezRadius); tractorContext.lineTo(tractorBeamCanvas.width, tractorBeamCanvas.height - 2*bezRadius); tractorContext.bezierCurveTo(tractorBeamCanvas.width, tractorBeamCanvas.height, tractorBeamCanvas.width - cowImage.width, tractorBeamCanvas.height, tractorBeamCanvas.width - cowImage.width, tractorBeamCanvas.height - 2*bezRadius); tractorContext.lineTo(halfACowWidth - topWidth, bezRadius); } tractorContext.closePath(); tractorContext.fillStyle = 'lightgoldenrodyellow'; tractorContext.fill(); tractorBeamCanvas.setAttribute("class","pulse1"); //and it goes like this: //the tractor beam descends from the top point //the cow beams up/down //the cow fades away //the tractor beam ascends, then disappears //tractor beam descends changeElementHeight(tractorBeamCanvas, 0, 0); changeElementHeight(tractorBeamCanvas, bottomPoint.Y - topY, 1000); //cow ascends or descends and fades setInterval(function(){ $(cowImage).show('fade', 1000); }, 1000); var moveVector; if( beamUp ) moveVector = new this.Vector(topPoint.X - bottomPoint.X, topPoint.Y - bottomPoint.Y); else moveVector = new this.Vector(bottomPoint.X - topPoint.X, bottomPoint.Y - topPoint.Y); setTimeout(function(){moveAndHideElement(cowImage, moveVector, 3000);}, 1000); //tractor beam ascends setTimeout(function(){ changeElementHeight(tractorBeamCanvas, 0, 1000); }, 5000); setTimeout(function(){ hideAndRemoveElement(tractorBeamCanvas, 10); }, 7000); } function moveAndHideElement(element, vector, animTime) { $(element).animate( { left: '+=' + vector.X + 'px', top: '+=' + vector.Y + 'px', }, animTime, function(){ hideAndRemoveElement(element, 500); }); } function hideAndRemoveElement(element, animTime) { $(element).hide('fade', animTime, function(){element.parentElement.removeChild(element);}); } function changeElementHeight(element, height, animTime) { $(element).animate( { height: height + 'px', }, animTime); } this.moveElementRight = function(element, units, animTime) { $(element).animate({ left: '+=' + units + 'px', }, animTime ); } this.showChangeDetails = function(changelist, x, y) { if( !changelist ) return; document.getElementById("changeChange").innerText = "Change: " + changelist.Change; document.getElementById("changeClient").innerText = "Client: " + changelist.Client; document.getElementById("changeDate").innerText = "Date: " + new Date(parseInt(changelist.Date)*1000); document.getElementById("changeDescription").innerText = "Description: " + changelist.Description; document.getElementById("changeStatus").innerText = "Status: " + changelist.Status; document.getElementById("changeType").innerText = "Type: " + changelist.Type; document.getElementById("changeUser").innerText = "User: " + changelist.User; document.getElementById("changePath").innerText = "Path: " + changelist.path; if( changelist.history && changelist.history.length > 1 )//ignore the first entry in history, it's this changelist itself { $('#integrationSelect').show(); $('#changeHistory').show(); var selectBox = document.getElementById('integrationSelect'); selectBox.options.length = 0; for (var i = 1; i < changelist.history.length; i++) this.AddSelectOption(/**/selectBox, changelist.history[i].Change, changelist.history[i], false); } else { $('#integrationSelect').hide(); $('#changeHistory').hide(); } if( x && y ) $( "#changeDetails" ).dialog( "option", "position", [x, y] ); mouseEvt = null; $( "#changeDetails" ).dialog( "open" ); } this.showChangeSelector = function(changelists, x, y, utilities) { var selectBox = document.getElementById('changeSelect'); selectBox.options.length = 0; //$(selectBox).change( null ).change(); for (var i = 0; i < changelists.length; i++) this.AddSelectOption(selectBox, changelists[i].Change, changelists[i], false); $("#changeSelect").change( function () { $("#changeSelect option:selected").each(function () { var clNum = parseInt($(this).text()); for( var i = 0; i < changelists.length; i++ ) { if( changelists[i].Change == clNum ) { utilities.showChangeDetails(changelists[i], x, y); //showChangeDetails(changelists[i], x, y, utilities); return; } } }); }).change(); if( x, y ) $( "#changeSelector" ).dialog( "option", "position", [x, y] ); $( '#changeSelector' ).dialog( 'open' ); } this.isNonConformingRelationship = function(parentNode, childNode) { //if( layout == 'ancestor' ) // return false; if( !parentNode || !childNode ) return false; if( parentNode.isMainline() && childNode.isMainline() ) return true; if( parentNode.isMainline() ) return false; if( parentNode.isFirmType() && !childNode.isFirmType() ) return true; if( !parentNode.isFirmType() && childNode.isFirmType() ) return true; return false; } this.createGitRepo = function(parentStream, repoData) { //first we have to know if git actually exists on this machine... var gitLocation; if( this.gitP4Installed() ) gitLocation = ''; else if (localStorage.gitLocation ) gitLocation = localStorage.gitLocation + '/'; else throw('git does not appear to be installed on this machine'); var gitCommand = gitLocation + 'git'; //based upon the KB article at: http://kb.perforce.com/article/1417/git-p4 // 1) Create a directory for the perforce client workspace root // 2) Create a P4CONFIG file var configFile = getConfigFile(); var configContents = 'P4CLIENT=' + repoData.workspaceName + '\nP4USER=' + p4ServerConnection.getUser() + '\nP4PORT=' + p4ServerConnection.getPort(); var wsDirCreated = ClientKit.system.createFile(repoData.workspaceFolder + '/' + configFile, configContents); // 3) Create a perforce workspace this.assembleAndSaveWorkspace(repoData.workspaceName, repoData.workspaceFolder, parentStream.mStream) // 4) Create a directory for the git repo // 5) Copy the P4Config file to the git repo directory var gitDirCreated = ClientKit.system.createFile(repoData.gitRepoFolder + '/' + configFile, configContents); var gitRepo = new GitRepo(repoData, p4ServerConnection.getUser(), parentStream.mStream, this.StreamType.gitRepo, 'stream', 'Branched from ' + parentStream.mStream, 'not currently used', true, true, parentStream.mFirmerThanParent, this.depotFromDepotPath(parentStream.mStream), p4ServerConnection.getPort()); mGitRepoStorageHandler.addRepo(gitRepo); //make sure we're in the correct directory ClientKit.system.setCurrentDir(repoData.gitRepoFolder); // 6) Import Perforce files and history into Git var cloneResults = ClientKit.system.startProcess(gitCommand,['p4', 'clone', parentStream.mStream, '.']); } this.branchGitRepo = function(parentRepo, name) { var gitRepo = this.createGitBranchObj(parentRepo, name); //Save the metadata (before we create the actual branch) mGitRepoStorageHandler.addRepo(gitRepo); //make sure we're in the correct directory ClientKit.system.setCurrentDir(parentRepo.mGitRepoFolder); //then do the branch var branchResults = ClientKit.system.startProcess('git',['branch', name]); //console.log(branchResults); } this.createGitBranchObj = function(parentRepo, name) { var repoData = {}; repoData.gitRepoName = name; repoData.gitRepoFolder = parentRepo.mGitRepoFolder; repoData.workspaceName = parentRepo.mWorkspaceName; repoData.workspaceFolder = parentRepo.mP4WorkspaceFolder; var parentType = 'gitRepo'; if( parentRepo.mType == this.StreamType.gitBranch ) parentType = 'gitBranch'; var gitRepo = new GitRepo(repoData, p4ServerConnection.getUser(), parentRepo.mStream, this.StreamType.gitBranch, parentType, 'Branched from ' + parentRepo.mStream, 'not currently used', true, true, parentRepo.mFirmerThanParent, parentRepo.mDepot, p4ServerConnection.getPort()); return gitRepo; } function getConfigFile() { var environmentVariables = ClientKit.system.startProcess('p4', ['set']); var lines = environmentVariables.split(/\r\n|\r|\n/); for( var i = 0; i < lines.length; i++ ) { var buf = lines[i]; if( buf.search('P4CONFIG=') == 0 ) { var s = buf.split(' '); buf = s[0]; buf = buf.substring(9, buf.length); if( buf != 'noconfig' ) return buf; } } return null; } this.p4ConfigInstalled = function() { if( getConfigFile() ) return true; return false; } this.gitP4Installed = function() { //TODO: this uses the 'which' command; what are the chances that it's cross platform? var msg = ClientKit.system.startProcess('which', ['-a', 'git']); //console.log(msg); if( msg.length < 1 ) { alert('Cannot find the Git executable. Set up your environment path to include it.'); return false; } msg = ClientKit.system.startProcess('which', ['-a', 'git-p4']); if( msg.length < 1 ) { alert('Cannot find the git-p4 plugin. Set up your environment path to include it.'); return false; } msg = ClientKit.system.startProcess('which', ['-a', 'p4']); if( msg.length < 1 ) { alert('Cannot find the p4 executable. Set up your environment path to include it.'); return false; } if( !this.p4ConfigInstalled() ) { alert('Your system must have a file name set for the P4CONFIG environmental variable, or the Git-P4 plugin won\'t work.'); return false; } return true; } this.selectDirectory = function() { return ClientKit.system.selectDirectory(); } this.selectFile = function(caption, dir) { return ClientKit.system.selectFile(caption, dir); } this.selectFiles = function(caption, dir) { return ClientKit.system.selectFiles(caption, dir); } this.assembleAndSaveWorkspace = function(clientName, root, stream) { if( useP4JsApi ) { var clientBuf = p4ServerConnection.p4('client -S ' + stream + ' -o ' + clientName).data[0]; clientBuf['Client'] = clientName; clientBuf['Root'] = root; clientBuf.View = ''; //this bit reassembles the paths; Paths come from the server in single paths, like Path0, Path1...PathN. Sadly, to send them back you have to put them in one tab-delimited string... //...even though we haven't touched them. No, that's not weird at all... for (var i = 0; clientBuf["View" + i] != null; ++i) { var view = clientBuf["View" + i]; clientBuf.View += '\t' + view + '\n'; delete clientBuf["View" + i]; } p4ServerConnection.p4("client -i", clientBuf); } else { var cmd = 'client -S ' + stream + ' -o ' + clientName; var results = p4ServerConnection.run(cmd); var clientSpec = results.data[0]; //console.log(clientSpec); //then change whatever properties ("form fields") you want clientSpec.Description = "This is my new client Description for a git-p4 client. \n\n [set (via JavaScript!) at: " + (new Date) + "]"; //**NOTE**: it gets mad if any line that isn't the first line of the "field" doesn't start with a space //so, for example, if you have a line-ending in the Description field, be sure the next line starts with a space //(those of you paying close attention will notice I've done exactly that above in my new Description, //albeit manually... you could do something like <newValueString>.replace("\n","\n ") if you want to be certain) //now we gotta turn this dang object into a string, but not just any ol' string! "Really?" you ask... //No! a specifically formatted string! Meaning we cant use JSON.stringify(clientSpec) or clientSpec.toString() //instead, we'll just start with an empty string, then loop through each property in the object, and add it on. var clientSpecString = ""; for (var key in clientSpec) if (clientSpec.hasOwnProperty(key)) { //what we're doing is basically making it look like the form would as a .txt file in an editor //**NOTE**: the "View" field gets separated into "View0", "View1", "View2", etc. properties, so //we have to hack it so that "View0" becomes "View" and the rest become empty spaces (see previous NOTE), do //beware that in other forms you might encounter other fields that take more than one line and //thus get will be split like this, so you'll have to check their form objects for those cases, or, //perhaps more ideally, write some code that checks each property to see if it's a "split" one. Have fun! if /*it's not one of the "View" properties"*/ (key.indexOf("View")==-1) clientSpecString += (key + ": " + clientSpec[key] + "\n"); else if /*it's the first of the "View" properties"*/ (key=="View0") clientSpecString += ("View: " + clientSpec[key] + "\n"); else /*it's a subsequent "View" property"*/ clientSpecString += (" " + clientSpec[key] + "\n"); } //now you can make the p4.run call (be sure to include the "-i" arg) and it'll send the data you just stored var serverResponse = p4ServerConnection.run('client -i', clientSpecString); } } this.deleteGitRepo = function(gitRepo) { //console.log(stream); if( gitRepo.mType === this.StreamType.gitRepo ) { ClientKit.system.setCurrentDir(gitRepo.gitRepoFolder); //p4ServerConnection.startProcess('rm', ['-r', '.git']);//this kills the git repo. Probably not a good idea, especially since it might be my repo we're talking about. } else if( gitRepo.mType === this.StreamType.gitBranch ) { ClientKit.system.startProcess('git', ['branch', '-D', gitRepo.mName]); } mGitRepoStorageHandler.deleteRepo(gitRepo); } this.getGitRepos = function() { if( this.canAccessLocalHarddrive() ) return mGitRepoStorageHandler.getGitDepotRepos(); return []; } this.getGitBranches = function(directory) { ClientKit.system.setCurrentDir(directory); var branches = ClientKit.system.startProcess('git', ['branch']); if( branches.indexOf('fatal:') === 0 ) return []; //branches = branches.replace('* ', ''); //branches = branches.replace(/ /g, '@'); branches = branches.replace(/(\r\n|\n|\r)/gm, ' ');//this kills all the line breaks branches = branches.split(' '); var allbranches = []; for( var i = 0; i < branches.length; i++ ) if( branches[i] !== '' && branches[i] !== '*' ) allbranches.push(branches[i]); //console.log('directory: ' + directory + ' branches: ' + allbranches); return allbranches; } this.saveStreamDescription = function(streamPathString, desc) { if( useP4JsApi ) { var streambuf = p4ServerConnection.p4('stream -o ' + streamPathString).data[0]; streambuf['Description'] = desc; //if( useP4JsApi ) { streambuf.Paths = ''; //this bit reassembles the paths; Paths come from the server in single paths, like Path0, Path1...PathN. Sadly, to send them back you have to put them in one tab-delimited string... //...even though we haven't touched them. No, that's not weird at all... for (var i = 0; streambuf["Paths" + i] != null; ++i) { var path = streambuf["Paths" + i]; streambuf.Paths += '\t' + path + '\n'; delete streambuf["Paths" + i]; } } //var temp = p4ServerConnection.p4("stream", ["-i", streambuf]); //console.log('stream:'); //console.log(temp); } else { var streamSpec = p4ServerConnection.run("stream",['-o', streamPathString]).data[0]; //then change whatever properties ("form fields") you want streamSpec.Description = desc; //**NOTE**: it gets mad if any line that isn't the first line of the "field" doesn't start with a space //so, for example, if you have a line-ending in the Description field, be sure the next line starts with a space //(those of you paying close attention will notice I've done exactly that above in my new Description, //albeit manually... you could do something like <newValueString>.replace("\n","\n ") if you want to be certain) //now we gotta turn this dang object into a string, but not just any ol' string! "Really?" you ask... //No! a specifically formatted string! Meaning we cant use JSON.stringify(streamSpec) or streamSpec.toString() //instead, we'll just start with an empty string, then loop through each property in the object, and add it on. var streamSpecString = ""; for (var key in streamSpec) if (streamSpec.hasOwnProperty(key)) { var kl = key.length; var digits = 1; var endString = key.substring(kl-1,kl); var endNum = null; while (parseInt(endString)==endString) { endNum = parseInt(endString); digits++; endString = key.substring(kl-digits, kl) } if (key.indexOf("extra")==0 || key.indexOf("firmer")==0) continue;//console.log("skipping: " + key); else if (endNum==null) streamSpecString += (key + ": " + streamSpec[key] + "\n"); else if (endNum==0) streamSpecString += (key.substring(0,kl-(digits-1)) + ": " + streamSpec[key] + "\n"); else streamSpecString += (" " + streamSpec[key] + "\n"); } //then, instead of passing that string as one of the arguments when you make the p4.run() call, //you have to set that string as the input using p4.setInput() //p4ServerConnection.setInput(streamSpecString); //now you can make the p4.run call (be sure to include the "-i" arg) and it'll send the data you just stored var serverResponse = p4ServerConnection.run("stream -i", streamSpecString); //console.log('stream:'); //console.log(serverResponse); //**NOTE**: serverResponse.data[0] (the response object) doesn't have a "message" property with a meaningful value //instead, the singular property it has is what you'd expect to be the value (eg: "Stream save") and its value is "" //**ALSO NOTE**: the input you set with setInput does not get cleared, I can't really imagine this being a problem, //as long as you always set the input you want to use before p4.run, but thought it was worth pointing out. //Or, if you're extra paranoid, you could do the following, just to be safe: //p4ServerConnection.setInput(null); } } this.getUser = function() { return p4ServerConnection.getUser(); } this.getDepots = function(callback) { var results = p4ServerConnection.p4('depots'/*, callback*/); callback(results); } this.getUsers = function(callback) { var results = p4ServerConnection.p4('users'/*, callback*/); callback(results); } this.loadMergePromoteData = function(stream, callback) { //console.log('loadMergePromoteData for streams'); if( stream instanceof Stream ) p4ServerConnection.p4('istat -a -f ' + stream.mStream, callback); else if( stream instanceof GitRepo && ( stream.mParentType == 'gitRepo' || stream.mParentType == 'gitBranch') ) { //for GitRepos, we have to assemble the data to make it look like the istat data that comes back from the Perforce server for streams... //console.log( 'repo: ' + stream.mName + ' parent: ' + stream.mParentName ); var data = {}; data.stream = stream.mStream; data.integToParent = 'false'; data.integFromParent = 'false'; ClientKit.system.setCurrentDir(stream.mGitRepoFolder); var integToParentVal = ClientKit.system.startProcess('git', ['log', '--pretty=oneline', stream.mName, '^' + stream.mParentName, '--no-merges']); var integFromParentVal = ClientKit.system.startProcess('git', ['log', '--pretty=oneline', stream.mParentName, '^' + stream.mName, '--no-merges']); //console.log( 'stream: *' + stream.mName + '* git folder: ' + stream.mGitRepoFolder); //console.log ('integ from parent' + integFromParentVal) //console.log ('integ to parent' + integToParentVal); //todo: there are a lot of things other than good data that could be returned here. Find out what they are and account for them! if( integToParentVal.length > 0 ) data.integToParent = 'true'; if( integFromParentVal.length > 0 ) data.integFromParent = 'true'; var istat = {}; istat.size = 1; istat.data = []; istat.data.push(data); callback(istat) } //else // p4ServerConnection.p4('istat -a -f ' + stream.mStream, callback); } this.getSelectedItem = function() { if( useP4JsApi ) return p4ServerConnection.getSelection(); //this is a placeholder until I get a selection tree hooked up in P4ClientKit. For right now, //it creates a dummy file object and returns it. //console.log(p4);//temp, this will throw an error if p4 doesn't exist var temp = new Object(); temp.folder = new Object(); var listbox = document.getElementById('depotSelect'); temp.folder.depotPath = '//' + listbox.options[listbox.selectedIndex].innerText + '/'; return temp; } this.getStream = function(stream) { return p4ServerConnection.p4('stream -o ' + stream).data[0]; } this.getChanges = function(numberOfChanges, stream, mostRecent) { return p4ServerConnection.p4('changes -l -m ' + numberOfChanges + ' ' + stream + '/...' + mostRecent); } this.getChangeHistory = function( change, callback ) { p4ServerConnection.p4('changes -i @' + change + ',' + change, callback); } this.getChangeHistoryNoCallback = function( change ) { var results = p4ServerConnection.p4('changes -i @' + change + ',' + change); return results; } this.getStreamWorkspaces = function( stream, callback ) { p4ServerConnection.p4('workspaces -S ' + stream, callback); } this.getUserStreamWorkspaces = function(user, stream, callback ) { p4ServerConnection.p4('workspaces -u ' + user + ' -S ' + stream, callback); } this.getDepotChanges = function(depot, callback) { p4ServerConnection.p4('changes -l ' + '//' + depot + '/...', callback); } this.getDepotIntegrated = function( depot, callback ) { p4ServerConnection.p4('integrated ' + '//' + depot + '/...', callback); } this.groomChangelist = function(change) { //the APIs are inconsistent in returning eg .Change or .change /* change: "1236" changeType: "restricted" client: "myclient-badDev1" desc: "Driving thru hedge, dev1 to former bad dev " history: Array (0) path: "//SomeDepot/badDev1/..." status: "submitted" streamNode: Object time: "1300366674" user: "me" */ if( change.change ) change.Change = change.change; if( change.changeType ) change.Type = change.changeType; if( change.client ) change.Client = change.client; if( change.desc ) change.Description = change.desc; if( change.user ) change.User = change.user; if( change.time ) change.Date = change.time; if( change.status ) change.Status = change.status; change.Change = parseInt(change.Change); } this.openTerminal = function(directory) { //TODO: make this work in other OS's. var platform = navigator.platform; if( platform.indexOf('Mac') === 0 ) { //p4ServerConnection.startDetached('open', ['-a', 'terminal', directory], ''); ClientKit.system.startDetached("osascript",['-e','tell application "Terminal" to do script "cd '+directory+'"'],""); } else if( platform.indexOf('Win') === 0 ) p4ServerConnection.startDetached('start', ['cd', directory], '');//'start' launches a new command console in windows. Is this going to work, or does it need to be cmd? else if( platform.indexOf('Linux') === 0 ) alert('not implemented on Linux yet'); else alert('not implemented on ' + platform + ' yet'); } }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#9 | 8214 | David George |
Re-implemented gumdrop skin in stream graph. Made selection work in P4V. |
||
#8 | 8126 | David George |
Added test for stream selection before adding a git repo. Updated StreamUtilities to use refactored ClientKit.system commands. 'p4 set' now has a different output, updated getConfigFile to handle it. |
||
#7 | 8125 | David George | Added handling for change trajectories that drive through the hedges and live in the same row. | ||
#6 | 8124 | David George |
Updated some of the calls to ClientKit to sync up with it's refactoring. Commented out the diagnostic time-tracking stuff in TrajectoryModel. Refactored TrajectoryView so that it once again has a asynchronous progress bar. |
||
#5 | 8122 | David George | Fixed call to startProcess, it was moved in the refactoring of ClientKit. | ||
#4 | 8116 | David George | Interim checkin so Jaimen can test against ClientKit. | ||
#3 | 8110 | David George | This is a fairly unstable checkin that is a first attempt at getting the StreamGraph to work with the refactored ClientKit. | ||
#2 | 8084 | David George |
Began work on optimizing the changelist trajectory algorithm. By using lazy loading, it's now about 4x faster. Refactored TrajectoryView and TrajectoryModel so that the model doesn't know about the view (I know, should never have known about it in the first place. I figured out how to create a Listener pattern in JavaScript so that I could do this a little more elegantly). |
||
#1 | 8081 | David George |
Initial submit of JavaScript StreamGraph. Main functionality is: Change Trajectory (Change Flow), Timeline, and GitStreams. |