Merge pull request #56 from alexarchambault/develop

Last developments
This commit is contained in:
Alexandre Archambault 2015-07-04 18:16:07 +02:00
commit 6e6062e6b8
2 changed files with 299 additions and 58 deletions

View File

@ -59,8 +59,10 @@ object CoursierBuild extends Build {
organization := "com.github.alexarchambault", organization := "com.github.alexarchambault",
scalaVersion := "2.11.6", scalaVersion := "2.11.6",
crossScalaVersions := Seq("2.10.5", "2.11.6"), crossScalaVersions := Seq("2.10.5", "2.11.6"),
resolvers += "Scalaz Bintray Repo" at "http://dl.bintray.com/scalaz/releases", resolvers ++= Seq(
resolvers += Resolver.sonatypeRepo("snapshots") "Scalaz Bintray Repo" at "http://dl.bintray.com/scalaz/releases",
Resolver.sonatypeRepo("releases")
)
) ++ publishingSettings ) ++ publishingSettings
private lazy val commonCoreSettings = commonSettings ++ Seq[Setting[_]]( private lazy val commonCoreSettings = commonSettings ++ Seq[Setting[_]](
@ -124,7 +126,7 @@ object CoursierBuild extends Build {
.settings( .settings(
name := "coursier-cli", name := "coursier-cli",
libraryDependencies ++= Seq( libraryDependencies ++= Seq(
"com.github.alexarchambault" %% "case-app" % "0.3.0-SNAPSHOT", "com.github.alexarchambault" %% "case-app" % "0.3.0",
"ch.qos.logback" % "logback-classic" % "1.1.3" "ch.qos.logback" % "logback-classic" % "1.1.3"
) ++ { ) ++ {
if (scalaVersion.value startsWith "2.10.") if (scalaVersion.value startsWith "2.10.")

View File

@ -14,17 +14,22 @@ import scala.concurrent.Future
import scala.scalajs.js import scala.scalajs.js
import js.Dynamic.{ global => g } import js.Dynamic.{ global => g }
case class ResolutionOptions(followOptional: Boolean = false, case class ResolutionOptions(
keepTest: Boolean = false) followOptional: Boolean = false,
keepTest: Boolean = false
)
case class State(modules: Seq[Dependency], case class State(
repositories: Seq[MavenRepository], modules: Seq[Dependency],
options: ResolutionOptions, repositories: Seq[(String, MavenRepository)],
resolutionOpt: Option[Resolution], options: ResolutionOptions,
editModuleIdx: Int, resolutionOpt: Option[Resolution],
resolving: Boolean, editModuleIdx: Int,
reverseTree: Boolean, editRepoIdx: Int,
log: Seq[String]) resolving: Boolean,
reverseTree: Boolean,
log: Seq[String]
)
class Backend($: BackendScope[Unit, State]) { class Backend($: BackendScope[Unit, State]) {
def updateDepGraph(resolution: Resolution) = { def updateDepGraph(resolution: Resolution) = {
@ -39,12 +44,21 @@ class Backend($: BackendScope[Unit, State]) {
nodes += name nodes += name
} }
def repr(dep: Dependency) =
Seq(
dep.module.organization,
dep.module.name,
dep.scope.name
).mkString(":")
for { for {
(dep, parents) <- resolution.reverseDependencies.toList (dep, parents) <- resolution
from = s"${dep.module.organization}:${dep.module.name}:${dep.scope.name}" .reverseDependencies
.toList
from = repr(dep)
_ = addNode(from) _ = addNode(from)
parDep <- parents parDep <- parents
to = s"${parDep.module.organization}:${parDep.module.name}:${parDep.scope.name}" to = repr(parDep)
_ = addNode(to) _ = addNode(to)
} { } {
graph.addEdge(from, to) graph.addEdge(from, to)
@ -53,14 +67,21 @@ class Backend($: BackendScope[Unit, State]) {
val layouter = js.Dynamic.newInstance(g.Graph.Layout.Spring)(graph) val layouter = js.Dynamic.newInstance(g.Graph.Layout.Spring)(graph)
layouter.layout() layouter.layout()
val width = jQuery("#dependencies").width() val width = jQuery("#dependencies")
val height = math.max(jQuery("#dependencies").height().asInstanceOf[Int], 400) .width()
val height = jQuery("#dependencies")
.height()
.asInstanceOf[Int]
.max(400)
println(s"width: $width, height: $height") println(s"width: $width, height: $height")
jQuery("#depgraphcanvas").html("") //empty() jQuery("#depgraphcanvas")
.html("") // empty()
val renderer = js.Dynamic.newInstance(g.Graph.Renderer.Raphael)("depgraphcanvas", graph, width, height) val renderer = js.Dynamic.newInstance(g.Graph.Renderer.Raphael)(
"depgraphcanvas", graph, width, height
)
renderer.draw() renderer.draw()
println("Rendered canvas") println("Rendered canvas")
} }
@ -103,8 +124,14 @@ class Backend($: BackendScope[Unit, State]) {
else Seq("nodes" -> js.Array(deps.map(tree): _*)) else Seq("nodes" -> js.Array(deps.map(tree): _*))
}: _*) }: _*)
println(minDependencies.toList.map(tree).map(js.JSON.stringify(_))) println(
g.$(target).treeview(js.Dictionary("data" -> js.Array(minDependencies.toList.map(tree): _*))) minDependencies
.toList
.map(tree)
.map(js.JSON.stringify(_))
)
g.$(target)
.treeview(js.Dictionary("data" -> js.Array(minDependencies.toList.map(tree): _*)))
} }
def resolve(action: => Unit = ()) = { def resolve(action: => Unit = ()) = {
@ -130,21 +157,34 @@ class Backend($: BackendScope[Unit, State]) {
def task = { def task = {
val res = coursier.Resolution( val res = coursier.Resolution(
s.modules.toSet, s.modules.toSet,
filter = Some(dep => (s.options.followOptional || !dep.optional) && (s.options.keepTest || dep.scope != Scope.Test)) filter = Some(dep =>
(s.options.followOptional || !dep.optional) &&
(s.options.keepTest || dep.scope != Scope.Test)
)
) )
implicit val cachePolicy = CachePolicy.Default implicit val cachePolicy = CachePolicy.Default
res res
.process .process
.run(s.repositories.map(r => r.copy(logger = Some(logger))), 100) .run(s.repositories.map(item => item._2.copy(logger = Some(logger))), 100)
} }
// For reasons that are unclear to me, not delaying this when using the runNow execution context // For reasons that are unclear to me, not delaying this when using the runNow execution context
// somehow discards the $.modState above. (Not a major problem as queue is used by default.) // somehow discards the $.modState above. (Not a major problem as queue is used by default.)
Future(task)(scala.scalajs.concurrent.JSExecutionContext.Implicits.queue).flatMap(_.runF).foreach { res: Resolution => Future(task)(scala.scalajs.concurrent.JSExecutionContext.Implicits.queue).flatMap(_.runF).foreach { res: Resolution =>
$.modState{ s => updateDepGraph(res); updateTree(res, "#deptree", reverse = s.reverseTree); s.copy(resolutionOpt = Some(res), resolving = false)} $.modState{ s =>
g.$("#resResTab a:last").tab("show") updateDepGraph(res)
updateTree(res, "#deptree", reverse = s.reverseTree)
s.copy(
resolutionOpt = Some(res),
resolving = false
)
}
g.$("#resResTab a:last")
.tab("show")
} }
} }
def handleResolve(e: ReactEventI) = { def handleResolve(e: ReactEventI) = {
@ -173,14 +213,24 @@ class Backend($: BackendScope[Unit, State]) {
def removeModule(idx: Int)(e: ReactEventI) = { def removeModule(idx: Int)(e: ReactEventI) = {
e.preventDefault() e.preventDefault()
$.modState(s => s.copy(modules = s.modules.zipWithIndex.filter(_._2 != idx).map(_._1))) $.modState(s =>
s.copy(
modules = s.modules
.zipWithIndex
.filter(_._2 != idx)
.map(_._1)
)
)
} }
def updateModule(moduleIdx: Int, update: (Dependency, String) => Dependency)(e: ReactEventI) = { def updateModule(moduleIdx: Int, update: (Dependency, String) => Dependency)(e: ReactEventI) = {
if (moduleIdx >= 0) { if (moduleIdx >= 0) {
$.modState{ state => $.modState{ state =>
val dep = state.modules(moduleIdx) val dep = state.modules(moduleIdx)
state.copy(modules = state.modules.updated(moduleIdx, update(dep, e.target.value))) state.copy(
modules = state.modules
.updated(moduleIdx, update(dep, e.target.value))
)
} }
} }
} }
@ -190,20 +240,95 @@ class Backend($: BackendScope[Unit, State]) {
$.modState{ state => $.modState{ state =>
val modules = state.modules :+ Dependency(Module("", ""), "") val modules = state.modules :+ Dependency(Module("", ""), "")
println(s"Modules:\n${modules.mkString("\n")}") println(s"Modules:\n${modules.mkString("\n")}")
state.copy(modules = modules, editModuleIdx = modules.length - 1) state.copy(
modules = modules,
editModuleIdx = modules.length - 1
)
}
}
def editRepo(idx: Int)(e: ReactEventI) = {
e.preventDefault()
$.modState(_.copy(editRepoIdx = idx))
}
def removeRepo(idx: Int)(e: ReactEventI) = {
e.preventDefault()
$.modState(s =>
s.copy(
repositories = s.repositories
.zipWithIndex
.filter(_._2 != idx)
.map(_._1)
)
)
}
def moveRepo(idx: Int, up: Boolean)(e: ReactEventI) = {
e.preventDefault()
$.modState { s =>
val idx0 = if (up) idx - 1 else idx + 1
val n = s.repositories.length
if (idx >= 0 && idx0 >= 0 && idx < n && idx0 < n) {
val a = s.repositories(idx)
val b = s.repositories(idx0)
s.copy(
repositories = s.repositories
.updated(idx, b)
.updated(idx0, a)
)
} else
s
}
}
def updateRepo(repoIdx: Int, update: ((String, MavenRepository), String) => (String, MavenRepository))(e: ReactEventI) = {
if (repoIdx >= 0) {
$.modState{ state =>
val repo = state.repositories(repoIdx)
state.copy(
repositories = state.repositories
.updated(repoIdx, update(repo, e.target.value))
)
}
}
}
def addRepo(e: ReactEventI) = {
e.preventDefault()
$.modState{ state =>
val repositories = state.repositories :+ ("" -> MavenRepository(""))
println(s"Repositories:\n${repositories.mkString("\n")}")
state.copy(
repositories = repositories,
editRepoIdx = repositories.length - 1
)
} }
} }
def enablePopover(e: ReactEventI) = { def enablePopover(e: ReactEventI) = {
g.$("[data-toggle='popover']").popover() g.$("[data-toggle='popover']")
.popover()
} }
object options { object options {
def toggleOptional(e: ReactEventI) = { def toggleOptional(e: ReactEventI) = {
$.modState(s => s.copy(options = s.options.copy(followOptional = !s.options.followOptional))) $.modState(s =>
s.copy(
options = s.options
.copy(followOptional = !s.options.followOptional)
)
)
} }
def toggleTest(e: ReactEventI) = { def toggleTest(e: ReactEventI) = {
$.modState(s => s.copy(options = s.options.copy(keepTest = !s.options.keepTest))) $.modState(s =>
s.copy(
options = s.options
.copy(keepTest = !s.options.keepTest)
)
)
} }
} }
} }
@ -289,7 +414,16 @@ object App {
) )
), ),
<.tbody( <.tbody(
sortedDeps.map(dep => depItem(dep, res.projectCache.get(dep.moduleVersion).map(_._2.version).filter(_ != dep.version))) sortedDeps.map(dep =>
depItem(
dep,
res
.projectCache
.get(dep.moduleVersion)
.map(_._2.version)
.filter(_ != dep.version)
)
)
) )
) )
} }
@ -300,6 +434,8 @@ object App {
def ok = apply("ok") def ok = apply("ok")
def edit = apply("pencil") def edit = apply("pencil")
def remove = apply("remove") def remove = apply("remove")
def up = apply("arrow-up")
def down = apply("arrow-down")
} }
val moduleEditModal = ReactComponentB[((Module, String), Int, Backend)]("EditModule") val moduleEditModal = ReactComponentB[((Module, String), Int, Backend)]("EditModule")
@ -346,7 +482,7 @@ object App {
} }
.build .build
def dependenciesTable(name: String) = ReactComponentB[(Seq[Dependency], Int, Backend)](name) val modules = ReactComponentB[(Seq[Dependency], Int, Backend)]("Dependencies")
.render{ P => .render{ P =>
val (deps, editModuleIdx, backend) = P val (deps, editModuleIdx, backend) = P
@ -373,7 +509,8 @@ object App {
<.p( <.p(
<.button(^.`type` := "button", ^.`class` := "btn btn-default customButton", <.button(^.`type` := "button", ^.`class` := "btn btn-default customButton",
^.onClick ==> backend.addModule, ^.onClick ==> backend.addModule,
Attr("data-toggle") := "modal", Attr("data-target") := "#moduleEdit", Attr("data-toggle") := "modal",
Attr("data-target") := "#moduleEdit",
"Add" "Add"
) )
), ),
@ -388,39 +525,131 @@ object App {
) )
), ),
<.tbody( <.tbody(
deps.zipWithIndex.map((depItem _).tupled) deps.zipWithIndex
.map((depItem _).tupled)
) )
), ),
moduleEditModal((deps.lift(editModuleIdx).fold((Module("", ""), ""))(_.moduleVersion), editModuleIdx, backend)) moduleEditModal((
deps
.lift(editModuleIdx)
.fold(Module("", "") -> "")(_.moduleVersion),
editModuleIdx,
backend
))
) )
} }
.build .build
val modules = dependenciesTable("Dependencies") val repoEditModal = ReactComponentB[((String, MavenRepository), Int, Backend)]("EditRepo")
.render{ P =>
val repositories = ReactComponentB[Seq[MavenRepository]]("Repositories") val ((name, repo), repoIdx, backend) = P
.render{ repos => <.div(^.`class` := "modal fade", ^.id := "repoEdit", ^.role := "dialog", ^.aria.labelledby := "repoEditTitle",
def repoItem(repo: MavenRepository) = <.div(^.`class` := "modal-dialog", <.div(^.`class` := "modal-content",
<.tr( <.div(^.`class` := "modal-header",
<.td( <.button(^.`type` := "button", ^.`class` := "close", Attr("data-dismiss") := "modal", ^.aria.label := "Close",
<.a(^.href := repo.root, <.span(^.aria.hidden := "true", dangerouslySetInnerHtml("&times;"))
repo.root ),
<.h4(^.`class` := "modal-title", ^.id := "repoEditTitle", "Repository")
),
<.div(^.`class` := "modal-body",
<.form(
<.div(^.`class` := "form-group",
<.label(^.`for` := "inputName", "Name"),
<.input(^.`class` := "form-control", ^.id := "inputName", ^.placeholder := "Name",
^.onChange ==> backend.updateRepo(repoIdx, (item, value) => (value, item._2)),
^.value := name
)
),
<.div(^.`class` := "form-group",
<.label(^.`for` := "inputVersion", "Root"),
<.input(^.`class` := "form-control", ^.id := "inputVersion", ^.placeholder := "Root",
^.onChange ==> backend.updateRepo(repoIdx, (item, value) => (item._1, item._2.copy(root = value))),
^.value := repo.root
)
),
<.div(^.`class` := "modal-footer",
<.button(^.`type` := "submit", ^.`class` := "btn btn-primary", Attr("data-dismiss") := "modal", "Done")
)
) )
) )
))
)
}
.build
val repositories = ReactComponentB[(Seq[(String, MavenRepository)], Int, Backend)]("Repositories")
.render{ P =>
val (repos, editRepoIdx, backend) = P
def repoItem(item: (String, MavenRepository), idx: Int, isLast: Boolean) =
<.tr(
<.td(item._1),
<.td(item._2.root),
<.td(
<.a(Attr("data-toggle") := "modal", Attr("data-target") := "#repoEdit", ^.`class` := "icon-action",
^.onClick ==> backend.editRepo(idx),
icon.edit
)
),
<.td(
<.a(Attr("data-toggle") := "modal", Attr("data-target") := "#repoRemove", ^.`class` := "icon-action",
^.onClick ==> backend.removeRepo(idx),
icon.remove
)
),
<.td(
if (idx > 0)
Seq(<.a(Attr("data-toggle") := "modal", Attr("data-target") := "#repoUp", ^.`class` := "icon-action",
^.onClick ==> backend.moveRepo(idx, up = true),
icon.up
))
else
Seq()
),
<.td(
if (isLast)
Seq()
else
Seq(<.a(Attr("data-toggle") := "modal", Attr("data-target") := "#repoDown", ^.`class` := "icon-action",
^.onClick ==> backend.moveRepo(idx, up = false),
icon.down
))
)
) )
val sortedRepos = repos <.div(
.sortBy(repo => repo.root) <.p(
<.button(^.`type` := "button", ^.`class` := "btn btn-default customButton",
<.table(^.`class` := "table", ^.onClick ==> backend.addRepo,
<.thead( Attr("data-toggle") := "modal",
<.tr( Attr("data-target") := "#repoEdit",
<.th("Base URL") "Add"
) )
), ),
<.tbody( <.table(^.`class` := "table",
sortedRepos.map(repoItem) <.thead(
) <.tr(
<.th("Name"),
<.th("Root"),
<.th(""),
<.th(""),
<.th(""),
<.th("")
)
),
<.tbody(
repos.init.zipWithIndex
.map(t => repoItem(t._1, t._2, isLast = false)) ++
repos.lastOption.map(repoItem(_, repos.length - 1, isLast = true))
)
),
repoEditModal((
repos
.lift(editRepoIdx)
.getOrElse("" -> MavenRepository("")),
editRepoIdx,
backend
))
) )
} }
.build .build
@ -471,7 +700,17 @@ object App {
} }
.build .build
val initialState = State(Nil, Seq(Repository.mavenCentral), ResolutionOptions(), None, -1, resolving = false, reverseTree = false, log = Nil) val initialState = State(
Nil,
Seq("central" -> Repository.mavenCentral),
ResolutionOptions(),
None,
-1,
-1,
resolving = false,
reverseTree = false,
log = Nil
)
val app = ReactComponentB[Unit]("Coursier") val app = ReactComponentB[Unit]("Coursier")
.initialState(initialState) .initialState(initialState)
@ -501,7 +740,7 @@ object App {
modules((S.modules, S.editModuleIdx, B)) modules((S.modules, S.editModuleIdx, B))
), ),
<.div(^.role := "tabpanel", ^.`class` := "tab-pane", ^.id := "repositories", <.div(^.role := "tabpanel", ^.`class` := "tab-pane", ^.id := "repositories",
repositories(S.repositories) repositories((S.repositories, S.editRepoIdx, B))
), ),
<.div(^.role := "tabpanel", ^.`class` := "tab-pane", ^.id := "options", <.div(^.role := "tabpanel", ^.`class` := "tab-pane", ^.id := "options",
options((S.options, B)) options((S.options, B))