mirror of https://github.com/sbt/sbt.git
commit
3bcc99a53a
|
|
@ -4,8 +4,6 @@ package core
|
|||
import java.io._
|
||||
import java.net.URL
|
||||
|
||||
import org.http4s.{EntityDecoder, Status, Uri}
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.io.Codec
|
||||
import scalaz._, Scalaz._
|
||||
|
|
@ -18,7 +16,7 @@ trait ArtifactDownloaderLogger {
|
|||
def downloadedArtifact(url: String, success: Boolean): Unit
|
||||
}
|
||||
|
||||
case class ArtifactDownloader(root: Uri, cache: File, logger: Option[ArtifactDownloaderLogger] = None) {
|
||||
case class ArtifactDownloader(root: String, cache: File, logger: Option[ArtifactDownloaderLogger] = None) {
|
||||
var bufferSize = 1024*1024
|
||||
|
||||
def artifact(module: Module,
|
||||
|
|
@ -52,7 +50,7 @@ case class ArtifactDownloader(root: Uri, cache: File, logger: Option[ArtifactDow
|
|||
// - what if someone is trying to write this file at the same time? (no locking of any kind yet)
|
||||
// - ...
|
||||
|
||||
val urlStr = (root resolve Uri(path = relPath.mkString("./", "/", ""))).renderString
|
||||
val urlStr = root + relPath.mkString("/")
|
||||
|
||||
Task {
|
||||
try {
|
||||
|
|
@ -107,8 +105,39 @@ trait RemoteLogger {
|
|||
def puttingInCache(f: File): Unit
|
||||
}
|
||||
|
||||
case class Remote(root: Uri, cache: Option[File] = None, logger: Option[RemoteLogger] = None) extends Repository {
|
||||
lazy val client = org.http4s.client.blaze.defaultClient
|
||||
object Remote {
|
||||
|
||||
def readFullySync(is: InputStream) = {
|
||||
val buffer = new ByteArrayOutputStream()
|
||||
val data = Array.ofDim[Byte](16384)
|
||||
|
||||
var nRead = is.read(data, 0, data.length)
|
||||
while (nRead != -1) {
|
||||
buffer.write(data, 0, nRead)
|
||||
nRead = is.read(data, 0, data.length)
|
||||
}
|
||||
|
||||
buffer.flush()
|
||||
buffer.toByteArray
|
||||
}
|
||||
|
||||
def readFully(is: => InputStream) =
|
||||
Task {
|
||||
\/.fromTryCatchNonFatal {
|
||||
val is0 = is
|
||||
val b =
|
||||
try readFullySync(is)
|
||||
finally is0.close()
|
||||
|
||||
new String(b, "UTF-8")
|
||||
} .leftMap(_.getMessage)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
case class Remote(root: String,
|
||||
cache: Option[File] = None,
|
||||
logger: Option[RemoteLogger] = None) extends Repository {
|
||||
|
||||
def find(module: Module,
|
||||
cachePolicy: CachePolicy): EitherT[Task, String, Project] = {
|
||||
|
|
@ -141,19 +170,13 @@ case class Remote(root: Uri, cache: Option[File] = None, logger: Option[RemoteLo
|
|||
}
|
||||
|
||||
def remote = {
|
||||
val uri = root resolve Uri(path = relPath.mkString("./", "/", ""))
|
||||
val log = Task(logger.foreach(_.downloading(uri.renderString)))
|
||||
val get = log.flatMap(_ => client(uri))
|
||||
val urlStr = root + relPath.mkString("/")
|
||||
val url = new URL(urlStr)
|
||||
|
||||
get.flatMap{ resp =>
|
||||
val success = resp.status == Status.Ok
|
||||
logger.foreach(_.downloaded(uri.renderString, success))
|
||||
def log = Task(logger.foreach(_.downloading(urlStr)))
|
||||
def get = Remote.readFully(url.openStream())
|
||||
|
||||
if (success)
|
||||
EntityDecoder.text.decode(resp).run.map(_.leftMap(_.sanitized))
|
||||
else
|
||||
Task.now(-\/(s"Unhandled or bad status ${resp.status.code} from repository (${resp.status.code} ${resp.status.reason})"))
|
||||
}
|
||||
log.flatMap(_ => get)
|
||||
}
|
||||
|
||||
def save(s: String) = {
|
||||
|
|
@ -199,14 +222,10 @@ case class Remote(root: Uri, cache: Option[File] = None, logger: Option[RemoteLo
|
|||
}
|
||||
|
||||
def remote = {
|
||||
val respTask = client(root resolve Uri(path = relPath.mkString("./", "/", "")))
|
||||
val urlStr = root + relPath.mkString("/")
|
||||
val url = new URL(urlStr)
|
||||
|
||||
respTask.flatMap{ resp =>
|
||||
if (resp.status == Status.Ok)
|
||||
EntityDecoder.text.decode(resp).run.map(_.leftMap(_.sanitized))
|
||||
else
|
||||
Task.now(-\/(s"Unhandled or bad status ${resp.status.code} from repository (${resp.status.code} ${resp.status.reason})"))
|
||||
}
|
||||
Remote.readFully(url.openStream())
|
||||
}
|
||||
|
||||
def save(s: String) = {
|
||||
|
|
|
|||
|
|
@ -2,9 +2,6 @@ package coursier.core
|
|||
|
||||
package object compatibility {
|
||||
|
||||
type DateTime = org.http4s.DateTime
|
||||
val DateTime: org.http4s.DateTime.type = org.http4s.DateTime
|
||||
|
||||
implicit class RichChar(val c: Char) extends AnyVal {
|
||||
def letterOrDigit = c.isLetterOrDigit
|
||||
def letter = c.isLetter
|
||||
|
|
|
|||
|
|
@ -1,15 +1,13 @@
|
|||
package coursier
|
||||
|
||||
import org.http4s.Http4s._
|
||||
|
||||
package object repository {
|
||||
|
||||
type Remote = core.Remote
|
||||
val Remote: core.Remote.type = core.Remote
|
||||
|
||||
val mavenCentral = Remote(uri("https://repo1.maven.org/maven2/"))
|
||||
val mavenCentral = Remote("https://repo1.maven.org/maven2/")
|
||||
|
||||
val sonatypeReleases = Remote(uri("https://oss.sonatype.org/content/repositories/releases/"))
|
||||
val sonatypeSnapshots = Remote(uri("https://oss.sonatype.org/content/repositories/snapshots/"))
|
||||
val sonatypeReleases = Remote("https://oss.sonatype.org/content/repositories/releases/")
|
||||
val sonatypeSnapshots = Remote("https://oss.sonatype.org/content/repositories/snapshots/")
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,14 @@ object Resolver {
|
|||
|
||||
/**
|
||||
* Try to find `module` among `repositories`.
|
||||
*
|
||||
* Look at `repositories` from the left, one-by-one, and stop at first success.
|
||||
* Else, return all errors, in the same order.
|
||||
*
|
||||
* The `module` field of the returned `Project` in case of success may not be
|
||||
* equal to `module`, in case the version of the latter is not a specific
|
||||
* version (e.g. version interval). Which version get chosen depends on
|
||||
* the repository implementation.
|
||||
*/
|
||||
def find(repositories: Seq[Repository],
|
||||
module: Module): EitherT[Task, List[String], (Repository, Project)] = {
|
||||
|
|
@ -126,6 +134,7 @@ object Resolver {
|
|||
.map(v => v -> Parse.versionConstraint(v))
|
||||
.partition(_._2.isEmpty)
|
||||
|
||||
// FIXME Report this in return type, not this way
|
||||
if (nonParsedConstraints.nonEmpty)
|
||||
Console.err.println(s"Ignoring unparsed versions: ${nonParsedConstraints.map(_._1)}")
|
||||
|
||||
|
|
@ -139,7 +148,7 @@ object Resolver {
|
|||
|
||||
/**
|
||||
* Merge several dependencies, solving version constraints of duplicated modules.
|
||||
* Returns the conflicted dependencies, and the (solved) others.
|
||||
* Returns the conflicted dependencies, and the (merged) others.
|
||||
*/
|
||||
def merge(dependencies: TraversableOnce[Dependency]): (Seq[Dependency], Seq[Dependency]) = {
|
||||
val m = dependencies
|
||||
|
|
@ -370,7 +379,7 @@ object Resolver {
|
|||
/**
|
||||
* State of a dependency resolution.
|
||||
*
|
||||
* Done if `isDone` is `true`.
|
||||
* Done if method `isDone` returns `true`.
|
||||
*
|
||||
* @param dependencies: current set of dependencies
|
||||
* @param conflicts: conflicting dependencies
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ object CoursierBuild extends Build {
|
|||
.settings(commonSettings ++ commonCoreSettings: _*)
|
||||
.settings(
|
||||
libraryDependencies ++= Seq(
|
||||
"org.http4s" %% "http4s-blazeclient" % "0.7.0",
|
||||
"org.scalaz" %% "scalaz-concurrent" % "7.1.2",
|
||||
"com.lihaoyi" %% "utest" % "0.3.0" % "test"
|
||||
) ++ {
|
||||
if (scalaVersion.value.startsWith("2.10.")) Seq()
|
||||
|
|
@ -118,8 +118,13 @@ object CoursierBuild extends Build {
|
|||
libraryDependencies ++= Seq(
|
||||
"com.github.japgolly.scalajs-react" %%% "core" % "0.9.0"
|
||||
),
|
||||
jsDependencies +=
|
||||
"org.webjars" % "react" % "0.12.2" / "react-with-addons.js" commonJSName "React"
|
||||
resolvers += "Webjars Bintray" at "https://dl.bintray.com/webjars/maven/",
|
||||
jsDependencies ++= Seq(
|
||||
("org.webjars.bower" % "bootstrap" % "3.3.4" intransitive()) / "bootstrap.min.js" commonJSName "Bootstrap",
|
||||
("org.webjars.bower" % "react" % "0.12.2" intransitive()) / "react-with-addons.js" commonJSName "React",
|
||||
("org.webjars.bower" % "bootstrap-treeview" % "1.2.0" intransitive()) / "bootstrap-treeview.min.js" commonJSName "Treeview",
|
||||
("org.webjars.bower" % "raphael" % "2.1.4" intransitive()) / "raphael-min.js" commonJSName "Raphael"
|
||||
)
|
||||
)
|
||||
.enablePlugins(ScalaJSPlugin)
|
||||
|
||||
|
|
|
|||
|
|
@ -23,20 +23,17 @@ function isMaster() {
|
|||
[ "$TRAVIS_BRANCH" = "master" ]
|
||||
}
|
||||
|
||||
# web sub-project doesn't compile in 2.10 (no scalajs-react)
|
||||
if echo "$TRAVIS_SCALA_VERSION" | grep -q "^2\.10"; then
|
||||
if isNotPr && isJdk7 && isMaster; then
|
||||
EXTRA_SBT_ARGS="core-jvm/publish core-js/publish cli/publish"
|
||||
else
|
||||
EXTRA_SBT_ARGS=""
|
||||
fi
|
||||
|
||||
sbt ++${TRAVIS_SCALA_VERSION} core-jvm/test core-js/test cli/test $EXTRA_SBT_ARGS
|
||||
SBT_COMMANDS="cli/compile"
|
||||
else
|
||||
if isNotPr && isJdk7 && isMaster; then
|
||||
EXTRA_SBT_ARGS="publish"
|
||||
else
|
||||
EXTRA_SBT_ARGS=""
|
||||
fi
|
||||
|
||||
sbt ++${TRAVIS_SCALA_VERSION} test $EXTRA_SBT_ARGS
|
||||
SBT_COMMANDS="compile"
|
||||
fi
|
||||
|
||||
SBT_COMMANDS="$SBT_COMMANDS core-jvm/test core-js/test"
|
||||
|
||||
if isNotPr && isJdk7 && isMaster; then
|
||||
SBT_COMMANDS="$SBT_COMMANDS core-jvm/publish core-js/publish cli/publish"
|
||||
fi
|
||||
|
||||
sbt ++${TRAVIS_SCALA_VERSION} $SBT_COMMANDS
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css">
|
||||
<!-- FIXME Get from WebJar -->
|
||||
<link rel="stylesheet" href="css/bootstrap-treeview.css">
|
||||
|
||||
<style>
|
||||
|
|
@ -106,6 +107,8 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
|
||||
|
||||
<!-- during dev -->
|
||||
<script type="text/javascript" src="../web-jsdeps.js"></script>
|
||||
<script type="text/javascript" src="../web-fastopt.js"></script>
|
||||
|
|
@ -113,20 +116,13 @@
|
|||
<script type="text/javascript" src="web-jsdeps.js"></script>
|
||||
<script type="text/javascript" src="web-fastopt.js"></script>
|
||||
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js"></script>
|
||||
|
||||
<script src="js/bootstrap-treeview.js"></script>
|
||||
|
||||
<script type="text/javascript" src="js/raphael.js"></script>
|
||||
<script type="text/javascript" src="js/dracula_graph.js"></script>
|
||||
<script type="text/javascript" src="js/dracula_graffle.js"></script>
|
||||
<script type="text/javascript" src="https://raw.githubusercontent.com/strathausen/dracula/26010eb3b0df037cf507886897e20c02c4ec041b/lib/dracula_graph.js"></script>
|
||||
<script type="text/javascript" src="https://raw.githubusercontent.com/strathausen/dracula/26010eb3b0df037cf507886897e20c02c4ec041b/lib/dracula_graffle.js"></script>
|
||||
|
||||
<script>
|
||||
// From http://www.bootply.com/VQqzOawoYc
|
||||
// Javascript to enable link to tab
|
||||
var hash = document.location.hash;
|
||||
console.log(hash);
|
||||
if (hash) {
|
||||
console.log('match');
|
||||
// $('.nav-tabs a[href='+hash+']').tab('show');
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,128 +0,0 @@
|
|||
/**
|
||||
* Originally grabbed from the official RaphaelJS Documentation
|
||||
* http://raphaeljs.com/graffle.html
|
||||
* Adopted (arrows) and commented by Philipp Strathausen http://blog.ameisenbar.de
|
||||
* Licenced under the MIT licence.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
* connect two shapes
|
||||
* parameters:
|
||||
* source shape [or connection for redrawing],
|
||||
* target shape,
|
||||
* style with { fg : linecolor, bg : background color, directed: boolean }
|
||||
* returns:
|
||||
* connection { draw = function() }
|
||||
*/
|
||||
Raphael.fn.connection = function Connection(obj1, obj2, style) {
|
||||
var selfRef = this;
|
||||
/* create and return new connection */
|
||||
var edge = {/*
|
||||
from : obj1,
|
||||
to : obj2,
|
||||
style : style,*/
|
||||
draw : function() {
|
||||
/* get bounding boxes of target and source */
|
||||
var bb1 = obj1.getBBox();
|
||||
var bb2 = obj2.getBBox();
|
||||
var off1 = 0;
|
||||
var off2 = 0;
|
||||
/* coordinates for potential connection coordinates from/to the objects */
|
||||
var p = [
|
||||
/* NORTH 1 */
|
||||
{ x: bb1.x + bb1.width / 2, y: bb1.y - off1 },
|
||||
/* SOUTH 1 */
|
||||
{ x: bb1.x + bb1.width / 2, y: bb1.y + bb1.height + off1 },
|
||||
/* WEST */
|
||||
{ x: bb1.x - off1, y: bb1.y + bb1.height / 2 },
|
||||
/* EAST 1 */
|
||||
{ x: bb1.x + bb1.width + off1, y: bb1.y + bb1.height / 2 },
|
||||
/* NORTH 2 */
|
||||
{ x: bb2.x + bb2.width / 2, y: bb2.y - off2 },
|
||||
/* SOUTH 2 */
|
||||
{ x: bb2.x + bb2.width / 2, y: bb2.y + bb2.height + off2 },
|
||||
/* WEST 2 */
|
||||
{ x: bb2.x - off2, y: bb2.y + bb2.height / 2 },
|
||||
/* EAST 2 */
|
||||
{ x: bb2.x + bb2.width + off2, y: bb2.y + bb2.height / 2 }
|
||||
];
|
||||
|
||||
/* distances between objects and according coordinates connection */
|
||||
var d = {}, dis = [], dx, dy;
|
||||
|
||||
/*
|
||||
* find out the best connection coordinates by trying all possible ways
|
||||
*/
|
||||
/* loop the first object's connection coordinates */
|
||||
for (var i = 0; i < 4; i++) {
|
||||
/* loop the seond object's connection coordinates */
|
||||
for (var j = 4; j < 8; j++) {
|
||||
dx = Math.abs(p[i].x - p[j].x);
|
||||
dy = Math.abs(p[i].y - p[j].y);
|
||||
if ((i === j - 4) || (((i !== 3 && j !== 6) || p[i].x < p[j].x) &&
|
||||
((i !== 2 && j !== 7) || p[i].x > p[j].x) &&
|
||||
((i !== 0 && j !== 5) || p[i].y > p[j].y) &&
|
||||
((i !== 1 && j !== 4) || p[i].y < p[j].y)))
|
||||
{
|
||||
dis.push(dx + dy);
|
||||
d[dis[dis.length - 1].toFixed(3)] = [i, j];
|
||||
}
|
||||
}
|
||||
}
|
||||
var res = dis.length === 0 ? [0, 4] : d[Math.min.apply(Math, dis).toFixed(3)];
|
||||
/* bezier path */
|
||||
var x1 = p[res[0]].x,
|
||||
y1 = p[res[0]].y,
|
||||
x4 = p[res[1]].x,
|
||||
y4 = p[res[1]].y;
|
||||
dx = Math.max(Math.abs(x1 - x4) / 2, 10);
|
||||
dy = Math.max(Math.abs(y1 - y4) / 2, 10);
|
||||
var x2 = [ x1, x1, x1 - dx, x1 + dx ][res[0]].toFixed(3),
|
||||
y2 = [ y1 - dy, y1 + dy, y1, y1 ][res[0]].toFixed(3),
|
||||
x3 = [ 0, 0, 0, 0, x4, x4, x4 - dx, x4 + dx ][res[1]].toFixed(3),
|
||||
y3 = [ 0, 0, 0, 0, y1 + dy, y1 - dy, y4, y4 ][res[1]].toFixed(3);
|
||||
/* assemble path and arrow */
|
||||
var path = [ "M" + x1.toFixed(3), y1.toFixed(3),
|
||||
"C" + x2, y2, x3, y3, x4.toFixed(3), y4.toFixed(3) ].join(",");
|
||||
/* arrow */
|
||||
if(style && style.directed) {
|
||||
// magnitude, length of the last path vector
|
||||
var mag = Math.sqrt((y4 - y3) * (y4 - y3) + (x4 - x3) * (x4 - x3));
|
||||
// vector normalisation to specified length
|
||||
var norm = function(x,l){return (-x*(l||5)/mag);};
|
||||
// calculate array coordinates (two lines orthogonal to the path vector)
|
||||
var arr = [
|
||||
{ x:(norm(x4-x3)+norm(y4-y3)+x4).toFixed(3),
|
||||
y:(norm(y4-y3)+norm(x4-x3)+y4).toFixed(3) },
|
||||
{ x:(norm(x4-x3)-norm(y4-y3)+x4).toFixed(3),
|
||||
y:(norm(y4-y3)-norm(x4-x3)+y4).toFixed(3) }
|
||||
];
|
||||
path = path + ",M" + arr[0].x + "," + arr[0].y + ",L" + x4 + "," +
|
||||
y4 + ",L" + arr[1].x + "," + arr[1].y;
|
||||
}
|
||||
/* function to be used for moving existent path(s), e.g. animate() or attr() */
|
||||
var move = "attr";
|
||||
/* applying path(s) */
|
||||
edge.fg && edge.fg[move]({path:path}) ||
|
||||
(edge.fg = selfRef.path(path)
|
||||
.attr({ stroke: style && style.stroke || "#000", fill: "none" })
|
||||
.toBack());
|
||||
edge.bg && edge.bg[move]({path:path}) ||
|
||||
style && style.fill && (edge.bg = style.fill.split &&
|
||||
selfRef.path(path)
|
||||
.attr({ stroke: style.fill.split("|")[0], fill: "none",
|
||||
"stroke-width": style.fill.split("|")[1] || 3 }).toBack());
|
||||
/* setting label */
|
||||
style && style.label &&
|
||||
(edge.label && edge.label.attr({x:(x1+x4)/2, y:(y1+y4)/2}) ||
|
||||
(edge.label = selfRef.text((x1+x4)/2, (y1+y4)/2, style.label)
|
||||
.attr({fill: "#000", "font-size": style["font-size"] || "12px"})));
|
||||
style && style.label && style["label-style"] && edge.label &&
|
||||
edge.label.attr(style["label-style"]);
|
||||
style && style.callback && style.callback(edge);
|
||||
}
|
||||
};
|
||||
edge.draw();
|
||||
return edge;
|
||||
};
|
||||
|
|
@ -1,685 +0,0 @@
|
|||
/*
|
||||
* Dracula Graph Layout and Drawing Framework 0.0.3alpha
|
||||
* (c) 2010 Johann Philipp Strathausen <strathausen@gmail.com>
|
||||
* http://strathausen.eu
|
||||
*
|
||||
* Contributions by Jake Stothard <stothardj@gmail.com>.
|
||||
*
|
||||
* based on the Graph JavaScript framework, version 0.0.1
|
||||
* (c) 2006 Aslak Hellesoy <aslak.hellesoy@gmail.com>
|
||||
* (c) 2006 Dave Hoover <dave.hoover@gmail.com>
|
||||
*
|
||||
* Ported from Graph::Layouter::Spring in
|
||||
* http://search.cpan.org/~pasky/Graph-Layderer-0.02/
|
||||
* The algorithm is based on a spring-style layouter of a Java-based social
|
||||
* network tracker PieSpy written by Paul Mutton <paul@jibble.org>.
|
||||
*
|
||||
* This code is freely distributable under the MIT license. Commercial use is
|
||||
* hereby granted without any cost or restriction.
|
||||
*
|
||||
* Links:
|
||||
*
|
||||
* Graph Dracula JavaScript Framework:
|
||||
* http://graphdracula.net
|
||||
*
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Dracula
|
||||
*/
|
||||
var Dracula = function() {
|
||||
this.nodes = {};
|
||||
this.edges = [];
|
||||
this.snapshots = []; // previous graph states TODO to be implemented
|
||||
};
|
||||
var Graph = Dracula;
|
||||
Dracula.prototype = {
|
||||
/*
|
||||
* add a node
|
||||
* @id the node's ID (string or number)
|
||||
* @content (optional, dictionary) can contain any information that is
|
||||
* being interpreted by the layout algorithm or the graph
|
||||
* representation
|
||||
*/
|
||||
addNode: function(id, content) {
|
||||
/* testing if node is already existing in the graph */
|
||||
if(this.nodes[id] === undefined) {
|
||||
this.nodes[id] = new Dracula.Node(id, content);
|
||||
}
|
||||
return this.nodes[id];
|
||||
},
|
||||
|
||||
addEdge: function(source, target, style) {
|
||||
var s = this.addNode(source);
|
||||
var t = this.addNode(target);
|
||||
var edge = new Dracula.Edge({ source: s, target: t, style: style });
|
||||
s.edges.push(edge);
|
||||
this.edges.push(edge);
|
||||
// NOTE: Even directed edges are added to both nodes.
|
||||
t.edges.push(edge);
|
||||
return edge;
|
||||
},
|
||||
|
||||
/* TODO to be implemented
|
||||
* Preserve a copy of the graph state (nodes, positions, ...)
|
||||
* @comment a comment describing the state
|
||||
*/
|
||||
snapShot: function(comment) {
|
||||
// FIXME
|
||||
//var graph = new Dracula();
|
||||
//graph.nodes = jQuery.extend(true, {}, this.nodes);
|
||||
//graph.edges = jQuery.extend(true, {}, this.edges);
|
||||
//this.snapshots.push({comment: comment, graph: graph});
|
||||
},
|
||||
removeNode: function(id) {
|
||||
if (this.nodes[id] && this.node[id].shape) {
|
||||
this.nodes[id].shape.remove();
|
||||
delete this.nodes[id];
|
||||
}
|
||||
for(var i = 0; i < this.edges.length; i++) {
|
||||
if (this.edges[i].source.id == id || this.edges[i].target.id == id) {
|
||||
this.edges[i].connection.fg.remove();
|
||||
this.edges[i].connection.label.remove();
|
||||
this.edges.splice(i, 1);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Edge
|
||||
*/
|
||||
Dracula.Edge = function DraculaEdge(opts) {
|
||||
this.source = opts.source;
|
||||
this.target = opts.target;
|
||||
if (opts.style) {
|
||||
this.style = jQuery.extend(true, {}, Dracula.Edge.style, opts.style);
|
||||
} else {
|
||||
this.style = jQuery.extend(true, {}, Dracula.Edge.style);
|
||||
}
|
||||
};
|
||||
Dracula.Edge.style = {
|
||||
directed: false
|
||||
};
|
||||
Dracula.Edge.prototype = {
|
||||
weight: 0,
|
||||
hide: function hideEdge() {
|
||||
this.connection.fg.hide();
|
||||
this.connection.bg && this.bg.connection.hide();
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Node
|
||||
*/
|
||||
Dracula.Node = function DraculaNode(id, node){
|
||||
var i;
|
||||
node = node || {};
|
||||
node.id = id;
|
||||
node.edges = [];
|
||||
node.hide = function() {
|
||||
var i;
|
||||
this.hidden = true;
|
||||
this.shape && this.shape.hide(); /* FIXME this is representation specific code and should be elsewhere */
|
||||
for(i in this.edges)
|
||||
(this.edges[i].source.id == id || this.edges[i].target == id) && this.edges[i].hide && this.edges[i].hide();
|
||||
};
|
||||
node.show = function() {
|
||||
var i;
|
||||
this.hidden = false;
|
||||
this.shape && this.shape.show();
|
||||
for(i in this.edges)
|
||||
(this.edges[i].source.id == id || this.edges[i].target == id) &&
|
||||
this.edges[i].show && this.edges[i].show();
|
||||
};
|
||||
return node;
|
||||
};
|
||||
Dracula.Node.prototype = {
|
||||
};
|
||||
|
||||
/*
|
||||
* Renderer Base Class
|
||||
*/
|
||||
Dracula.Renderer = {};
|
||||
|
||||
/*
|
||||
* Renderer implementation using RaphaelJS
|
||||
*/
|
||||
Dracula.Renderer.Raphael = function(element, graph, width, height) {
|
||||
this.width = width || 400;
|
||||
this.height = height || 400;
|
||||
this.r = Raphael(element, this.width, this.height);
|
||||
this.radius = 40; /* max dimension of a node */
|
||||
this.graph = graph;
|
||||
this.mouse_in = false;
|
||||
|
||||
/* TODO default node rendering function */
|
||||
if(!this.graph.render) {
|
||||
this.graph.render = function() {
|
||||
return;
|
||||
};
|
||||
}
|
||||
this.draw();
|
||||
};
|
||||
|
||||
|
||||
/* Moved this default node renderer function out of the main prototype code
|
||||
* so it can be override by default */
|
||||
Dracula.Renderer.defaultRenderFunc = function(r, node) {
|
||||
/* the default node drawing */
|
||||
var color = Raphael.getColor();
|
||||
var ellipse = r.ellipse(0, 0, 30, 20).attr({
|
||||
fill: node.fill || color,
|
||||
stroke: node.stroke || color,
|
||||
"stroke-width": 2
|
||||
});
|
||||
/* set DOM node ID */
|
||||
ellipse.node.id = node.id || node.label;
|
||||
if(node.class)ellipse.node.classList.add(node.class);
|
||||
shape = r.set().push(ellipse).push(r.text(0, 30, node.label || node.id));
|
||||
return shape;
|
||||
};
|
||||
|
||||
|
||||
Dracula.Renderer.Raphael.prototype = {
|
||||
translate: function(point) {
|
||||
return [
|
||||
(point[0] - this.graph.layoutMinX) * this.factorX + this.radius,
|
||||
(point[1] - this.graph.layoutMinY) * this.factorY + this.radius
|
||||
];
|
||||
},
|
||||
|
||||
rotate: function(point, length, angle) {
|
||||
var dx = length * Math.cos(angle);
|
||||
var dy = length * Math.sin(angle);
|
||||
return [point[0]+dx, point[1]+dy];
|
||||
},
|
||||
|
||||
draw: function() {
|
||||
var i;
|
||||
this.factorX = (this.width - 2 * this.radius) / (this.graph.layoutMaxX - this.graph.layoutMinX);
|
||||
this.factorY = (this.height - 2 * this.radius) / (this.graph.layoutMaxY - this.graph.layoutMinY);
|
||||
for (i in this.graph.nodes) {
|
||||
this.drawNode(this.graph.nodes[i]);
|
||||
}
|
||||
for (i = 0; i < this.graph.edges.length; i++) {
|
||||
this.drawEdge(this.graph.edges[i]);
|
||||
}
|
||||
},
|
||||
|
||||
drawNode: function(node) {
|
||||
var point = this.translate([node.layoutPosX, node.layoutPosY]);
|
||||
node.point = point;
|
||||
var r = this.r;
|
||||
var graph = this.graph;
|
||||
|
||||
/* if node has already been drawn, move the nodes */
|
||||
if(node.shape) {
|
||||
var oBBox = node.shape.getBBox();
|
||||
var opoint = {
|
||||
x: oBBox.x + oBBox.width / 2,
|
||||
y: oBBox.y + oBBox.height / 2
|
||||
};
|
||||
node.shape.translate(
|
||||
Math.round(point[0] - opoint.x), Math.round(point[1] - opoint.y)
|
||||
);
|
||||
|
||||
this.r.safari();
|
||||
return node;
|
||||
}/* else, draw new nodes */
|
||||
|
||||
var shape;
|
||||
|
||||
/* if a node renderer function is provided by the user, then use it
|
||||
or the default render function instead */
|
||||
if(!node.render) {
|
||||
node.render = Dracula.Renderer.defaultRenderFunc;
|
||||
}
|
||||
/* or check for an ajax representation of the nodes */
|
||||
if(node.shapes) {
|
||||
// TODO ajax representation evaluation
|
||||
}
|
||||
|
||||
var selfRef = this;
|
||||
shape = node.render(this.r, node).hide();
|
||||
|
||||
shape.attr({"fill-opacity": 0.6});
|
||||
/* re-reference to the node an element belongs to, needed for dragging all elements of a node */
|
||||
shape.items.forEach(function(item) {
|
||||
item.set = shape;
|
||||
item.node.style.cursor = "move";
|
||||
});
|
||||
shape.drag(
|
||||
function dragMove(dx, dy, x, y) {
|
||||
dx = this.set.ox;
|
||||
dy = this.set.oy;
|
||||
var bBox = this.set.getBBox();
|
||||
var newX = x - dx + (bBox.x + bBox.width / 2);
|
||||
var newY = y - dy + (bBox.y + bBox.height / 2);
|
||||
var clientX =
|
||||
x - (newX < 20 ? newX - 20 : newX > r.width - 20 ? newX - r.width + 20 : 0);
|
||||
var clientY =
|
||||
y - (newY < 20 ? newY - 20 : newY > r.height - 20 ? newY - r.height + 20 : 0);
|
||||
this.set.translate(clientX - Math.round(dx), clientY - Math.round(dy));
|
||||
for (var i in selfRef.graph.edges) {
|
||||
selfRef.graph.edges[i] &&
|
||||
selfRef.graph.edges[i].connection && selfRef.graph.edges[i].connection.draw();
|
||||
}
|
||||
r.safari();
|
||||
this.set.ox = clientX;
|
||||
this.set.oy = clientY;
|
||||
},
|
||||
function dragEnter(x, y) {
|
||||
this.set.ox = x;
|
||||
this.set.oy = y;
|
||||
this.animate({ 'fill-opacity': 0.2 }, 500);
|
||||
},
|
||||
function dragOut() {
|
||||
this.animate({ 'fill-opacity': 0.6 }, 500);
|
||||
}
|
||||
);
|
||||
|
||||
var box = shape.getBBox();
|
||||
shape.translate(
|
||||
Math.round(point[0] - (box.x + box.width / 2)),
|
||||
Math.round(point[1] - (box.y + box.height / 2))
|
||||
);
|
||||
node.hidden || shape.show();
|
||||
node.shape = shape;
|
||||
},
|
||||
drawEdge: function(edge) {
|
||||
/* if this edge already exists the other way around and is undirected */
|
||||
if(edge.backedge)
|
||||
return;
|
||||
if(edge.source.hidden || edge.target.hidden) {
|
||||
edge.connection && edge.connection.fg.hide();
|
||||
edge.connection.bg && edge.connection.bg.hide();
|
||||
return;
|
||||
}
|
||||
/* if edge already has been drawn, only refresh the edge */
|
||||
if(!edge.connection) {
|
||||
edge.style && edge.style.callback && edge.style.callback(edge); // TODO move this somewhere else
|
||||
edge.connection = this.r.connection(edge.source.shape, edge.target.shape, edge.style);
|
||||
return;
|
||||
}
|
||||
//FIXME showing doesn't work well
|
||||
edge.connection.fg.show();
|
||||
edge.connection.bg && edge.connection.bg.show();
|
||||
edge.connection.draw();
|
||||
}
|
||||
};
|
||||
Dracula.Layout = {};
|
||||
Dracula.Layout.Spring = function(graph) {
|
||||
this.graph = graph;
|
||||
this.iterations = 500;
|
||||
this.maxRepulsiveForceDistance = 6;
|
||||
this.k = 2;
|
||||
this.c = 0.01;
|
||||
this.maxVertexMovement = 0.5;
|
||||
this.layout();
|
||||
};
|
||||
Dracula.Layout.Spring.prototype = {
|
||||
layout: function() {
|
||||
this.layoutPrepare();
|
||||
for (var i = 0; i < this.iterations; i++) {
|
||||
this.layoutIteration();
|
||||
}
|
||||
this.layoutCalcBounds();
|
||||
},
|
||||
|
||||
layoutPrepare: function() {
|
||||
var i;
|
||||
for (i in this.graph.nodes) {
|
||||
var node = this.graph.nodes[i];
|
||||
node.layoutPosX = 0;
|
||||
node.layoutPosY = 0;
|
||||
node.layoutForceX = 0;
|
||||
node.layoutForceY = 0;
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
layoutCalcBounds: function() {
|
||||
var minx = Infinity, maxx = -Infinity,
|
||||
miny = Infinity, maxy = -Infinity;
|
||||
var i;
|
||||
|
||||
for (i in this.graph.nodes) {
|
||||
var x = this.graph.nodes[i].layoutPosX;
|
||||
var y = this.graph.nodes[i].layoutPosY;
|
||||
|
||||
if(x > maxx) maxx = x;
|
||||
if(x < minx) minx = x;
|
||||
if(y > maxy) maxy = y;
|
||||
if(y < miny) miny = y;
|
||||
}
|
||||
|
||||
this.graph.layoutMinX = minx;
|
||||
this.graph.layoutMaxX = maxx;
|
||||
this.graph.layoutMinY = miny;
|
||||
this.graph.layoutMaxY = maxy;
|
||||
},
|
||||
|
||||
layoutIteration: function() {
|
||||
// Forces on nodes due to node-node repulsions
|
||||
|
||||
var prev = [];
|
||||
for(var c in this.graph.nodes) {
|
||||
var node1 = this.graph.nodes[c];
|
||||
for (var d in prev) {
|
||||
var node2 = this.graph.nodes[prev[d]];
|
||||
this.layoutRepulsive(node1, node2);
|
||||
|
||||
}
|
||||
prev.push(c);
|
||||
}
|
||||
|
||||
// Forces on nodes due to edge attractions
|
||||
for (var i = 0; i < this.graph.edges.length; i++) {
|
||||
var edge = this.graph.edges[i];
|
||||
this.layoutAttractive(edge);
|
||||
}
|
||||
|
||||
// Move by the given force
|
||||
for (i in this.graph.nodes) {
|
||||
var node = this.graph.nodes[i];
|
||||
var xmove = this.c * node.layoutForceX;
|
||||
var ymove = this.c * node.layoutForceY;
|
||||
|
||||
var max = this.maxVertexMovement;
|
||||
if(xmove > max) xmove = max;
|
||||
if(xmove < -max) xmove = -max;
|
||||
if(ymove > max) ymove = max;
|
||||
if(ymove < -max) ymove = -max;
|
||||
|
||||
node.layoutPosX += xmove;
|
||||
node.layoutPosY += ymove;
|
||||
node.layoutForceX = 0;
|
||||
node.layoutForceY = 0;
|
||||
}
|
||||
},
|
||||
|
||||
layoutRepulsive: function(node1, node2) {
|
||||
if (typeof node1 == 'undefined' || typeof node2 == 'undefined')
|
||||
return;
|
||||
var dx = node2.layoutPosX - node1.layoutPosX;
|
||||
var dy = node2.layoutPosY - node1.layoutPosY;
|
||||
var d2 = dx * dx + dy * dy;
|
||||
if(d2 < 0.01) {
|
||||
dx = 0.1 * Math.random() + 0.1;
|
||||
dy = 0.1 * Math.random() + 0.1;
|
||||
d2 = dx * dx + dy * dy;
|
||||
}
|
||||
var d = Math.sqrt(d2);
|
||||
if(d < this.maxRepulsiveForceDistance) {
|
||||
var repulsiveForce = this.k * this.k / d;
|
||||
node2.layoutForceX += repulsiveForce * dx / d;
|
||||
node2.layoutForceY += repulsiveForce * dy / d;
|
||||
node1.layoutForceX -= repulsiveForce * dx / d;
|
||||
node1.layoutForceY -= repulsiveForce * dy / d;
|
||||
}
|
||||
},
|
||||
|
||||
layoutAttractive: function(edge) {
|
||||
var node1 = edge.source;
|
||||
var node2 = edge.target;
|
||||
|
||||
var dx = node2.layoutPosX - node1.layoutPosX;
|
||||
var dy = node2.layoutPosY - node1.layoutPosY;
|
||||
var d2 = dx * dx + dy * dy;
|
||||
if(d2 < 0.01) {
|
||||
dx = 0.1 * Math.random() + 0.1;
|
||||
dy = 0.1 * Math.random() + 0.1;
|
||||
d2 = dx * dx + dy * dy;
|
||||
}
|
||||
var d = Math.sqrt(d2);
|
||||
if(d > this.maxRepulsiveForceDistance) {
|
||||
d = this.maxRepulsiveForceDistance;
|
||||
d2 = d * d;
|
||||
}
|
||||
var attractiveForce = (d2 - this.k * this.k) / this.k;
|
||||
if(edge.attraction === undefined) edge.attraction = 1;
|
||||
attractiveForce *= Math.log(edge.attraction) * 0.5 + 1;
|
||||
|
||||
node2.layoutForceX -= attractiveForce * dx / d;
|
||||
node2.layoutForceY -= attractiveForce * dy / d;
|
||||
node1.layoutForceX += attractiveForce * dx / d;
|
||||
node1.layoutForceY += attractiveForce * dy / d;
|
||||
}
|
||||
};
|
||||
|
||||
Dracula.Layout.Ordered = function(graph, order) {
|
||||
this.graph = graph;
|
||||
this.order = order;
|
||||
this.layout();
|
||||
};
|
||||
Dracula.Layout.Ordered.prototype = {
|
||||
layout: function() {
|
||||
this.layoutPrepare();
|
||||
this.layoutCalcBounds();
|
||||
},
|
||||
|
||||
layoutPrepare: function(order) {
|
||||
var node, i;
|
||||
for (i in this.graph.nodes) {
|
||||
node = this.graph.nodes[i];
|
||||
node.layoutPosX = 0;
|
||||
node.layoutPosY = 0;
|
||||
}
|
||||
var counter = 0;
|
||||
for (i in this.order) {
|
||||
node = this.order[i];
|
||||
node.layoutPosX = counter;
|
||||
node.layoutPosY = Math.random();
|
||||
counter++;
|
||||
}
|
||||
},
|
||||
|
||||
layoutCalcBounds: function() {
|
||||
var minx = Infinity, maxx = -Infinity,
|
||||
miny = Infinity, maxy = -Infinity;
|
||||
var i;
|
||||
|
||||
for (i in this.graph.nodes) {
|
||||
var x = this.graph.nodes[i].layoutPosX;
|
||||
var y = this.graph.nodes[i].layoutPosY;
|
||||
|
||||
if(x > maxx) maxx = x;
|
||||
if(x < minx) minx = x;
|
||||
if(y > maxy) maxy = y;
|
||||
if(y < miny) miny = y;
|
||||
}
|
||||
|
||||
this.graph.layoutMinX = minx;
|
||||
this.graph.layoutMaxX = maxx;
|
||||
|
||||
this.graph.layoutMinY = miny;
|
||||
this.graph.layoutMaxY = maxy;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Dracula.Layout.OrderedTree = function(graph, order) {
|
||||
this.graph = graph;
|
||||
this.order = order;
|
||||
this.layout();
|
||||
};
|
||||
|
||||
/*
|
||||
* OrderedTree is like Ordered but assumes there is one root
|
||||
* This way we can give non random positions to nodes on the Y-axis
|
||||
* it assumes the ordered nodes are of a perfect binary tree
|
||||
*/
|
||||
Dracula.Layout.OrderedTree.prototype = {
|
||||
layout: function() {
|
||||
this.layoutPrepare();
|
||||
this.layoutCalcBounds();
|
||||
},
|
||||
|
||||
layoutPrepare: function(order) {
|
||||
var node, i;
|
||||
for (i in this.graph.nodes) {
|
||||
node = this.graph.nodes[i];
|
||||
node.layoutPosX = 0;
|
||||
node.layoutPosY = 0;
|
||||
}
|
||||
//to reverse the order of rendering, we need to find out the
|
||||
//absolute number of levels we have. simple log math applies.
|
||||
var numNodes = this.order.length;
|
||||
var totalLevels = Math.floor(Math.log(numNodes) / Math.log(2));
|
||||
|
||||
var counter = 1;
|
||||
for (i in this.order) {
|
||||
node = this.order[i];
|
||||
//rank aka x coordinate
|
||||
var rank = Math.floor(Math.log(counter) / Math.log(2));
|
||||
//file relative to top
|
||||
var file = counter - Math.pow(rank, 2);
|
||||
|
||||
node.layoutPosX = totalLevels - rank;
|
||||
node.layoutPosY = file;
|
||||
counter++;
|
||||
}
|
||||
},
|
||||
|
||||
layoutCalcBounds: function() {
|
||||
var minx = Infinity, maxx = -Infinity,
|
||||
miny = Infinity, maxy = -Infinity;
|
||||
var i;
|
||||
|
||||
for (i in this.graph.nodes) {
|
||||
var x = this.graph.nodes[i].layoutPosX;
|
||||
var y = this.graph.nodes[i].layoutPosY;
|
||||
|
||||
if(x > maxx) maxx = x;
|
||||
if(x < minx) minx = x;
|
||||
if(y > maxy) maxy = y;
|
||||
if(y < miny) miny = y;
|
||||
}
|
||||
|
||||
this.graph.layoutMinX = minx;
|
||||
this.graph.layoutMaxX = maxx;
|
||||
|
||||
this.graph.layoutMinY = miny;
|
||||
this.graph.layoutMaxY = maxy;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Dracula.Layout.TournamentTree = function(graph, order) {
|
||||
this.graph = graph;
|
||||
this.order = order;
|
||||
this.layout();
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* TournamentTree looks more like a binary tree
|
||||
*/
|
||||
Dracula.Layout.TournamentTree.prototype = {
|
||||
layout: function() {
|
||||
this.layoutPrepare();
|
||||
this.layoutCalcBounds();
|
||||
},
|
||||
|
||||
layoutPrepare: function(order) {
|
||||
var node, i;
|
||||
for (i in this.graph.nodes) {
|
||||
node = this.graph.nodes[i];
|
||||
node.layoutPosX = 0;
|
||||
node.layoutPosY = 0;
|
||||
}
|
||||
//to reverse the order of rendering, we need to find out the
|
||||
//absolute number of levels we have. simple log math applies.
|
||||
var numNodes = this.order.length;
|
||||
var totalLevels = Math.floor(Math.log(numNodes) / Math.log(2));
|
||||
|
||||
var counter = 1;
|
||||
for (i in this.order) {
|
||||
node = this.order[i];
|
||||
var depth = Math.floor(Math.log(counter) / Math.log(2));
|
||||
var xpos = counter - Math.pow(depth, 2);
|
||||
var offset = Math.pow(2, totalLevels - depth);
|
||||
var final_x = offset + (counter - Math.pow(2, depth)) *
|
||||
Math.pow(2, (totalLevels - depth) + 1);
|
||||
node.layoutPosX = final_x;
|
||||
node.layoutPosY = depth;
|
||||
counter++;
|
||||
}
|
||||
},
|
||||
|
||||
layoutCalcBounds: function() {
|
||||
var minx = Infinity, maxx = -Infinity,
|
||||
miny = Infinity, maxy = -Infinity;
|
||||
var i;
|
||||
|
||||
for (i in this.graph.nodes) {
|
||||
var x = this.graph.nodes[i].layoutPosX;
|
||||
var y = this.graph.nodes[i].layoutPosY;
|
||||
|
||||
if(x > maxx) maxx = x;
|
||||
if(x < minx) minx = x;
|
||||
if(y > maxy) maxy = y;
|
||||
if(y < miny) miny = y;
|
||||
}
|
||||
|
||||
this.graph.layoutMinX = minx;
|
||||
this.graph.layoutMaxX = maxx;
|
||||
|
||||
this.graph.layoutMinY = miny;
|
||||
this.graph.layoutMaxY = maxy;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Raphael Tooltip Plugin
|
||||
* - attaches an element as a tooltip to another element
|
||||
*
|
||||
* Usage example, adding a rectangle as a tooltip to a circle:
|
||||
*
|
||||
* paper.circle(100,100,10).tooltip(paper.rect(0,0,20,30));
|
||||
*
|
||||
* If you want to use more shapes, you'll have to put them into a set.
|
||||
*
|
||||
*/
|
||||
Raphael.el.tooltip = function (tp) {
|
||||
this.tp = tp;
|
||||
this.tp.o = {x: 0, y: 0};
|
||||
this.tp.hide();
|
||||
this.hover(
|
||||
function(event){
|
||||
this.mousemove(function(event){
|
||||
this.tp.translate(event.clientX -
|
||||
this.tp.o.x,event.clientY - this.tp.o.y);
|
||||
this.tp.o = {x: event.clientX, y: event.clientY};
|
||||
});
|
||||
this.tp.show().toFront();
|
||||
},
|
||||
function(event){
|
||||
this.tp.hide();
|
||||
this.unmousemove();
|
||||
});
|
||||
return this;
|
||||
};
|
||||
|
||||
/* For IE */
|
||||
if (!Array.prototype.forEach)
|
||||
{
|
||||
Array.prototype.forEach = function(fun /*, thisp*/)
|
||||
{
|
||||
var len = this.length;
|
||||
if (typeof fun != "function")
|
||||
throw new TypeError();
|
||||
|
||||
var thisp = arguments[1];
|
||||
for (var i = 0; i < len; i++)
|
||||
{
|
||||
if (i in this)
|
||||
fun.call(thisp, this[i], i, this);
|
||||
}
|
||||
};
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,8 +0,0 @@
|
|||
/*!
|
||||
* routie - a tiny hash router
|
||||
* v0.3.2
|
||||
* http://projects.jga.me/routie
|
||||
* copyright Greg Allen 2013
|
||||
* MIT License
|
||||
*/
|
||||
(function(n){var e=[],t={},r="routie",o=n[r],i=function(n,e){this.name=e,this.path=n,this.keys=[],this.fns=[],this.params={},this.regex=a(this.path,this.keys,!1,!1)};i.prototype.addHandler=function(n){this.fns.push(n)},i.prototype.removeHandler=function(n){for(var e=0,t=this.fns.length;t>e;e++){var r=this.fns[e];if(n==r)return this.fns.splice(e,1),void 0}},i.prototype.run=function(n){for(var e=0,t=this.fns.length;t>e;e++)this.fns[e].apply(this,n)},i.prototype.match=function(n,e){var t=this.regex.exec(n);if(!t)return!1;for(var r=1,o=t.length;o>r;++r){var i=this.keys[r-1],a="string"==typeof t[r]?decodeURIComponent(t[r]):t[r];i&&(this.params[i.name]=a),e.push(a)}return!0},i.prototype.toURL=function(n){var e=this.path;for(var t in n)e=e.replace("/:"+t,"/"+n[t]);if(e=e.replace(/\/:.*\?/g,"/").replace(/\?/g,""),-1!=e.indexOf(":"))throw Error("missing parameters for url: "+e);return e};var a=function(n,e,t,r){return n instanceof RegExp?n:(n instanceof Array&&(n="("+n.join("|")+")"),n=n.concat(r?"":"/?").replace(/\/\(/g,"(?:/").replace(/\+/g,"__plus__").replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?/g,function(n,t,r,o,i,a){return e.push({name:o,optional:!!a}),t=t||"",""+(a?"":t)+"(?:"+(a?t:"")+(r||"")+(i||r&&"([^/.]+?)"||"([^/]+?)")+")"+(a||"")}).replace(/([\/.])/g,"\\$1").replace(/__plus__/g,"(.+)").replace(/\*/g,"(.*)"),RegExp("^"+n+"$",t?"":"i"))},s=function(n,r){var o=n.split(" "),a=2==o.length?o[0]:null;n=2==o.length?o[1]:o[0],t[n]||(t[n]=new i(n,a),e.push(t[n])),t[n].addHandler(r)},h=function(n,e){if("function"==typeof e)s(n,e),h.reload();else if("object"==typeof n){for(var t in n)s(t,n[t]);h.reload()}else e===void 0&&h.navigate(n)};h.lookup=function(n,t){for(var r=0,o=e.length;o>r;r++){var i=e[r];if(i.name==n)return i.toURL(t)}},h.remove=function(n,e){var r=t[n];r&&r.removeHandler(e)},h.removeAll=function(){t={},e=[]},h.navigate=function(n,e){e=e||{};var t=e.silent||!1;t&&l(),setTimeout(function(){window.location.hash=n,t&&setTimeout(function(){p()},1)},1)},h.noConflict=function(){return n[r]=o,h};var f=function(){return window.location.hash.substring(1)},c=function(n,e){var t=[];return e.match(n,t)?(e.run(t),!0):!1},u=h.reload=function(){for(var n=f(),t=0,r=e.length;r>t;t++){var o=e[t];if(c(n,o))return}},p=function(){n.addEventListener?n.addEventListener("hashchange",u,!1):n.attachEvent("onhashchange",u)},l=function(){n.removeEventListener?n.removeEventListener("hashchange",u):n.detachEvent("onhashchange",u)};p(),n[r]=h})(window);
|
||||
Loading…
Reference in New Issue