mirror of https://github.com/sbt/sbt.git
Switch to latest scalajs-react, split web module sources (#906)
This commit is contained in:
parent
dd7c8c19d4
commit
528c2adc2f
|
|
@ -153,6 +153,20 @@ To build a distributable binary
|
||||||
java -jar dist/coursier-cli.jar fetch --help
|
java -jar dist/coursier-cli.jar fetch --help
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Build the web demo
|
||||||
|
|
||||||
|
coursier is cross-compiled to scala-js, and can run in the browser. It has a [demo web site](https://coursier.github.io/coursier/#demo), that runs resolutions straight from your web browser.
|
||||||
|
|
||||||
|
Its sources are in the `web` module.
|
||||||
|
|
||||||
|
To build and test this demo site locally, you can do
|
||||||
|
```
|
||||||
|
$ sbt web/fastOptJS
|
||||||
|
$ open web/target/scala-2.12/classes/index.html
|
||||||
|
```
|
||||||
|
(on Linux, use `xdg-open` instead of `open`)
|
||||||
|
|
||||||
|
|
||||||
# Merging PRs on GitHub
|
# Merging PRs on GitHub
|
||||||
|
|
||||||
Use either "Create merge commit" or "Squash and merge".
|
Use either "Create merge commit" or "Squash and merge".
|
||||||
|
|
|
||||||
17
build.sbt
17
build.sbt
|
|
@ -196,7 +196,7 @@ lazy val web = project
|
||||||
shared,
|
shared,
|
||||||
dontPublish,
|
dontPublish,
|
||||||
libs ++= {
|
libs ++= {
|
||||||
if (scalaBinaryVersion.value == "2.11")
|
if (scalaBinaryVersion.value == "2.12")
|
||||||
Seq(
|
Seq(
|
||||||
CrossDeps.scalaJsJquery.value,
|
CrossDeps.scalaJsJquery.value,
|
||||||
CrossDeps.scalaJsReact.value
|
CrossDeps.scalaJsReact.value
|
||||||
|
|
@ -207,7 +207,7 @@ lazy val web = project
|
||||||
sourceDirectory := {
|
sourceDirectory := {
|
||||||
val dir = sourceDirectory.value
|
val dir = sourceDirectory.value
|
||||||
|
|
||||||
if (scalaBinaryVersion.value == "2.11")
|
if (scalaBinaryVersion.value == "2.12")
|
||||||
dir
|
dir
|
||||||
else
|
else
|
||||||
dir / "target" / "dummy"
|
dir / "target" / "dummy"
|
||||||
|
|
@ -222,7 +222,20 @@ lazy val web = project
|
||||||
WebDeps.react
|
WebDeps.react
|
||||||
.intransitive()
|
.intransitive()
|
||||||
./("react-with-addons.js")
|
./("react-with-addons.js")
|
||||||
|
.minified("react-with-addons.min.js")
|
||||||
.commonJSName("React"),
|
.commonJSName("React"),
|
||||||
|
WebDeps.react
|
||||||
|
.intransitive()
|
||||||
|
./("react-dom.js")
|
||||||
|
.minified("react-dom.min.js")
|
||||||
|
.dependsOn("react-with-addons.js")
|
||||||
|
.commonJSName("ReactDOM"),
|
||||||
|
WebDeps.react
|
||||||
|
.intransitive()
|
||||||
|
./("react-dom-server.js")
|
||||||
|
.minified("react-dom-server.min.js")
|
||||||
|
.dependsOn("react-dom.js")
|
||||||
|
.commonJSName("ReactDOMServer"),
|
||||||
WebDeps.bootstrapTreeView
|
WebDeps.bootstrapTreeView
|
||||||
.intransitive()
|
.intransitive()
|
||||||
./("bootstrap-treeview.min.js")
|
./("bootstrap-treeview.min.js")
|
||||||
|
|
|
||||||
|
|
@ -98,18 +98,30 @@ package object compatibility {
|
||||||
// FIXME Won't work in the browser
|
// FIXME Won't work in the browser
|
||||||
lazy val cheerio = g.require("cheerio")
|
lazy val cheerio = g.require("cheerio")
|
||||||
|
|
||||||
def listWebPageRawElements(page: String): Seq[String] = {
|
lazy val jqueryAvailable = !js.isUndefined(g.$)
|
||||||
|
|
||||||
val jquery = cheerio.load(page)
|
def listWebPageRawElements(page: String): Seq[String] = {
|
||||||
|
|
||||||
val links = new ListBuffer[String]
|
val links = new ListBuffer[String]
|
||||||
|
|
||||||
jquery("a").each({ self: js.Dynamic =>
|
// getting weird "maybe a wrong Dynamic method signature" errors when trying to factor that more
|
||||||
val href = jquery(self).attr("href")
|
|
||||||
if (!js.isUndefined(href))
|
if (jqueryAvailable)
|
||||||
links += href.asInstanceOf[String]
|
g.$("<div></div>").html(page).find("a").each({ self: js.Dynamic =>
|
||||||
()
|
val href = g.$(self).attr("href")
|
||||||
}: js.ThisFunction0[js.Dynamic, Unit])
|
if (!js.isUndefined(href))
|
||||||
|
links += href.asInstanceOf[String]
|
||||||
|
()
|
||||||
|
}: js.ThisFunction0[js.Dynamic, Unit])
|
||||||
|
else {
|
||||||
|
val jquery = cheerio.load(page)
|
||||||
|
jquery("a").each({ self: js.Dynamic =>
|
||||||
|
val href = jquery(self).attr("href")
|
||||||
|
if (!js.isUndefined(href))
|
||||||
|
links += href.asInstanceOf[String]
|
||||||
|
()
|
||||||
|
}: js.ThisFunction0[js.Dynamic, Unit])
|
||||||
|
}
|
||||||
|
|
||||||
links.result()
|
links.result()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,6 @@ object CrossDeps {
|
||||||
def scalazCore = setting("org.scalaz" %%% "scalaz-core" % SharedVersions.scalaz)
|
def scalazCore = setting("org.scalaz" %%% "scalaz-core" % SharedVersions.scalaz)
|
||||||
def scalaJsDom = setting("org.scala-js" %%% "scalajs-dom" % "0.9.6")
|
def scalaJsDom = setting("org.scala-js" %%% "scalajs-dom" % "0.9.6")
|
||||||
def utest = setting("com.lihaoyi" %%% "utest" % "0.6.4")
|
def utest = setting("com.lihaoyi" %%% "utest" % "0.6.4")
|
||||||
def scalaJsJquery = setting("be.doeraene" %%% "scalajs-jquery" % "0.9.3")
|
def scalaJsJquery = setting("be.doeraene" %%% "scalajs-jquery" % "0.9.4")
|
||||||
def scalaJsReact = setting("com.github.japgolly.scalajs-react" %%% "core" % "0.9.0")
|
def scalaJsReact = setting("com.github.japgolly.scalajs-react" %%% "core" % "1.3.1")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import sbt.Keys._
|
||||||
|
|
||||||
object WebDeps {
|
object WebDeps {
|
||||||
def bootstrap = "org.webjars.bower" % "bootstrap" % "3.3.4"
|
def bootstrap = "org.webjars.bower" % "bootstrap" % "3.3.4"
|
||||||
def react = "org.webjars.bower" % "react" % "0.12.2"
|
def react = "org.webjars.bower" % "react" % "15.6.1"
|
||||||
def bootstrapTreeView = "org.webjars.bower" % "bootstrap-treeview" % "1.2.0"
|
def bootstrapTreeView = "org.webjars.bower" % "bootstrap-treeview" % "1.2.0"
|
||||||
def raphael = "org.webjars.bower" % "raphael" % "2.1.4"
|
def raphael = "org.webjars.bower" % "raphael" % "2.1.4"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,501 @@
|
||||||
|
package coursier.web
|
||||||
|
|
||||||
|
import coursier.{Dependency, MavenRepository, Module, Resolution}
|
||||||
|
import coursier.maven.MavenSource
|
||||||
|
import japgolly.scalajs.react.vdom.{Attr, TagMod}
|
||||||
|
import japgolly.scalajs.react.vdom.HtmlAttrs.dangerouslySetInnerHtml
|
||||||
|
import japgolly.scalajs.react._
|
||||||
|
import japgolly.scalajs.react.vdom.html_<^._
|
||||||
|
|
||||||
|
import scala.scalajs.js
|
||||||
|
import js.Dynamic.{global => g}
|
||||||
|
|
||||||
|
object App {
|
||||||
|
|
||||||
|
lazy val arbor = g.arbor
|
||||||
|
|
||||||
|
val resultDependencies = ScalaComponent.builder[(Resolution, Backend)]("Result")
|
||||||
|
.render_P {
|
||||||
|
case (res, backend) =>
|
||||||
|
|
||||||
|
def infoLabel(label: String) =
|
||||||
|
<.span(^.`class` := "label label-info", label)
|
||||||
|
def errorPopOver(label: String, desc: String) =
|
||||||
|
popOver("danger", label, desc)
|
||||||
|
def infoPopOver(label: String, desc: String) =
|
||||||
|
popOver("info", label, desc)
|
||||||
|
def popOver(`type`: String, label: String, desc: String) =
|
||||||
|
<.button(^.`type` := "button", ^.`class` := s"btn btn-xs btn-${`type`}",
|
||||||
|
Attr("data-trigger") := "focus",
|
||||||
|
Attr("data-toggle") := "popover", Attr("data-placement") := "bottom",
|
||||||
|
Attr("data-content") := desc,
|
||||||
|
^.onClick ==> backend.enablePopover,
|
||||||
|
^.onMouseOver ==> backend.enablePopover,
|
||||||
|
label
|
||||||
|
)
|
||||||
|
|
||||||
|
def depItem(dep: Dependency, finalVersionOpt: Option[String]) = {
|
||||||
|
<.tr(
|
||||||
|
^.`class` := (if (res.errorCache.contains(dep.moduleVersion)) "danger" else ""),
|
||||||
|
<.td(dep.module.organization),
|
||||||
|
<.td(dep.module.name),
|
||||||
|
<.td(finalVersionOpt.fold(dep.version)(finalVersion => s"$finalVersion (for ${dep.version})")),
|
||||||
|
<.td(TagMod(
|
||||||
|
if (dep.configuration == "compile") TagMod() else TagMod(infoLabel(dep.configuration)),
|
||||||
|
if (dep.attributes.`type`.isEmpty || dep.attributes.`type` == "jar") TagMod() else TagMod(infoLabel(dep.attributes.`type`)),
|
||||||
|
if (dep.attributes.classifier.isEmpty) TagMod() else TagMod(infoLabel(dep.attributes.classifier)),
|
||||||
|
Some(dep.exclusions).filter(_.nonEmpty).map(excls => infoPopOver("Exclusions", excls.toList.sorted.map{case (org, name) => s"$org:$name"}.mkString("; "))).toSeq.toTagMod,
|
||||||
|
if (dep.optional) TagMod(infoLabel("optional")) else TagMod(),
|
||||||
|
res.errorCache.get(dep.moduleVersion).map(errs => errorPopOver("Error", errs.mkString("; "))).toSeq.toTagMod
|
||||||
|
)),
|
||||||
|
<.td(TagMod(
|
||||||
|
res.projectCache.get(dep.moduleVersion) match {
|
||||||
|
case Some((source: MavenSource, proj)) =>
|
||||||
|
// FIXME Maven specific, generalize with source.artifacts
|
||||||
|
val version0 = finalVersionOpt getOrElse dep.version
|
||||||
|
val relPath =
|
||||||
|
dep.module.organization.split('.').toSeq ++ Seq(
|
||||||
|
dep.module.name,
|
||||||
|
version0,
|
||||||
|
s"${dep.module.name}-$version0"
|
||||||
|
)
|
||||||
|
|
||||||
|
val root = source.root
|
||||||
|
|
||||||
|
TagMod(
|
||||||
|
<.a(^.href := s"$root${relPath.mkString("/")}.pom",
|
||||||
|
<.span(^.`class` := "label label-info", "POM")
|
||||||
|
),
|
||||||
|
<.a(^.href := s"$root${relPath.mkString("/")}.jar",
|
||||||
|
<.span(^.`class` := "label label-info", "JAR")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
case _ => TagMod()
|
||||||
|
}
|
||||||
|
))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val sortedDeps = res.minDependencies.toList
|
||||||
|
.sortBy { dep =>
|
||||||
|
val (org, name, _) = coursier.core.Module.unapply(dep.module).get
|
||||||
|
(org, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
<.table(^.`class` := "table",
|
||||||
|
<.thead(
|
||||||
|
<.tr(
|
||||||
|
<.th("Organization"),
|
||||||
|
<.th("Name"),
|
||||||
|
<.th("Version"),
|
||||||
|
<.th("Extra"),
|
||||||
|
<.th("Links")
|
||||||
|
)
|
||||||
|
),
|
||||||
|
<.tbody(
|
||||||
|
sortedDeps
|
||||||
|
.map(dep =>
|
||||||
|
depItem(
|
||||||
|
dep,
|
||||||
|
res
|
||||||
|
.projectCache
|
||||||
|
.get(dep.moduleVersion)
|
||||||
|
.map(_._2.version)
|
||||||
|
.filter(_ != dep.version)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.toTagMod
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.build
|
||||||
|
|
||||||
|
object icon {
|
||||||
|
def apply(id: String) = <.span(^.`class` := s"glyphicon glyphicon-$id", ^.aria.hidden := "true")
|
||||||
|
def ok = apply("ok")
|
||||||
|
def edit = apply("pencil")
|
||||||
|
def remove = apply("remove")
|
||||||
|
def up = apply("arrow-up")
|
||||||
|
def down = apply("arrow-down")
|
||||||
|
}
|
||||||
|
|
||||||
|
val moduleEditModal = ScalaComponent.builder[((Module, String), Int, Backend)]("EditModule")
|
||||||
|
.render_P {
|
||||||
|
case ((module, version), moduleIdx, backend) =>
|
||||||
|
<.div(^.`class` := "modal fade", ^.id := "moduleEdit", ^.role := "dialog", ^.aria.labelledBy := "moduleEditTitle",
|
||||||
|
<.div(^.`class` := "modal-dialog", <.div(^.`class` := "modal-content",
|
||||||
|
<.div(^.`class` := "modal-header",
|
||||||
|
<.button(^.`type` := "button", ^.`class` := "close", Attr("data-dismiss") := "modal", ^.aria.label := "Close",
|
||||||
|
<.span(^.aria.hidden := "true", dangerouslySetInnerHtml := "×")
|
||||||
|
),
|
||||||
|
<.h4(^.`class` := "modal-title", ^.id := "moduleEditTitle", "Dependency")
|
||||||
|
),
|
||||||
|
<.div(^.`class` := "modal-body",
|
||||||
|
<.form(
|
||||||
|
<.div(^.`class` := "form-group",
|
||||||
|
<.label(^.`for` := "inputOrganization", "Organization"),
|
||||||
|
<.input(^.`class` := "form-control", ^.id := "inputOrganization", ^.placeholder := "Organization",
|
||||||
|
^.onChange ==> backend.updateModule(moduleIdx, (dep, value) => dep.copy(module = dep.module.copy(organization = value))),
|
||||||
|
^.value := module.organization
|
||||||
|
)
|
||||||
|
),
|
||||||
|
<.div(^.`class` := "form-group",
|
||||||
|
<.label(^.`for` := "inputName", "Name"),
|
||||||
|
<.input(^.`class` := "form-control", ^.id := "inputName", ^.placeholder := "Name",
|
||||||
|
^.onChange ==> backend.updateModule(moduleIdx, (dep, value) => dep.copy(module = dep.module.copy(name = value))),
|
||||||
|
^.value := module.name
|
||||||
|
)
|
||||||
|
),
|
||||||
|
<.div(^.`class` := "form-group",
|
||||||
|
<.label(^.`for` := "inputVersion", "Version"),
|
||||||
|
<.input(^.`class` := "form-control", ^.id := "inputVersion", ^.placeholder := "Version",
|
||||||
|
^.onChange ==> backend.updateModule(moduleIdx, (dep, value) => dep.copy(version = value)),
|
||||||
|
^.value := version
|
||||||
|
)
|
||||||
|
),
|
||||||
|
<.div(^.`class` := "modal-footer",
|
||||||
|
<.button(^.`type` := "submit", ^.`class` := "btn btn-primary", Attr("data-dismiss") := "modal", "Done")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.build
|
||||||
|
|
||||||
|
val modules = ScalaComponent.builder[(Seq[Dependency], Int, Backend)]("Dependencies")
|
||||||
|
.render_P {
|
||||||
|
case (deps, editModuleIdx, backend) =>
|
||||||
|
|
||||||
|
def depItem(dep: Dependency, idx: Int) =
|
||||||
|
<.tr(
|
||||||
|
<.td(dep.module.organization),
|
||||||
|
<.td(dep.module.name),
|
||||||
|
<.td(dep.version),
|
||||||
|
<.td(
|
||||||
|
<.a(Attr("data-toggle") := "modal", Attr("data-target") := "#moduleEdit", ^.`class` := "icon-action",
|
||||||
|
^.onClick ==> backend.editModule(idx),
|
||||||
|
icon.edit
|
||||||
|
)
|
||||||
|
),
|
||||||
|
<.td(
|
||||||
|
<.a(Attr("data-toggle") := "modal", Attr("data-target") := "#moduleRemove", ^.`class` := "icon-action",
|
||||||
|
^.onClick ==> backend.removeModule(idx),
|
||||||
|
icon.remove
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
<.div(
|
||||||
|
<.p(
|
||||||
|
<.button(^.`type` := "button", ^.`class` := "btn btn-default customButton",
|
||||||
|
^.onClick ==> backend.addModule,
|
||||||
|
Attr("data-toggle") := "modal",
|
||||||
|
Attr("data-target") := "#moduleEdit",
|
||||||
|
"Add"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
<.table(^.`class` := "table",
|
||||||
|
<.thead(
|
||||||
|
<.tr(
|
||||||
|
<.th("Organization"),
|
||||||
|
<.th("Name"),
|
||||||
|
<.th("Version"),
|
||||||
|
<.th(""),
|
||||||
|
<.th("")
|
||||||
|
)
|
||||||
|
),
|
||||||
|
<.tbody(
|
||||||
|
deps
|
||||||
|
.zipWithIndex
|
||||||
|
.map((depItem _).tupled)
|
||||||
|
.toTagMod
|
||||||
|
)
|
||||||
|
),
|
||||||
|
moduleEditModal((
|
||||||
|
deps
|
||||||
|
.lift(editModuleIdx)
|
||||||
|
.fold(Module("", "") -> "")(_.moduleVersion),
|
||||||
|
editModuleIdx,
|
||||||
|
backend
|
||||||
|
))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.build
|
||||||
|
|
||||||
|
val repoEditModal = ScalaComponent.builder[((String, MavenRepository), Int, Backend)]("EditRepo")
|
||||||
|
.render_P {
|
||||||
|
case ((name, repo), repoIdx, backend) =>
|
||||||
|
<.div(^.`class` := "modal fade", ^.id := "repoEdit", ^.role := "dialog", ^.aria.labelledBy := "repoEditTitle",
|
||||||
|
<.div(^.`class` := "modal-dialog", <.div(^.`class` := "modal-content",
|
||||||
|
<.div(^.`class` := "modal-header",
|
||||||
|
<.button(^.`type` := "button", ^.`class` := "close", Attr("data-dismiss") := "modal", ^.aria.label := "Close",
|
||||||
|
<.span(^.aria.hidden := "true", dangerouslySetInnerHtml := "×")
|
||||||
|
),
|
||||||
|
<.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 = ScalaComponent.builder[(Seq[(String, MavenRepository)], Int, Backend)]("Repositories")
|
||||||
|
.render_P {
|
||||||
|
case (repos, editRepoIdx, backend) =>
|
||||||
|
|
||||||
|
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)
|
||||||
|
<.a(Attr("data-toggle") := "modal", Attr("data-target") := "#repoUp", ^.`class` := "icon-action",
|
||||||
|
^.onClick ==> backend.moveRepo(idx, up = true),
|
||||||
|
icon.up
|
||||||
|
)
|
||||||
|
else
|
||||||
|
TagMod()
|
||||||
|
),
|
||||||
|
<.td(
|
||||||
|
if (isLast)
|
||||||
|
TagMod()
|
||||||
|
else
|
||||||
|
<.a(Attr("data-toggle") := "modal", Attr("data-target") := "#repoDown", ^.`class` := "icon-action",
|
||||||
|
^.onClick ==> backend.moveRepo(idx, up = false),
|
||||||
|
icon.down
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
<.div(
|
||||||
|
<.p(
|
||||||
|
<.button(^.`type` := "button", ^.`class` := "btn btn-default customButton",
|
||||||
|
^.onClick ==> backend.addRepo,
|
||||||
|
Attr("data-toggle") := "modal",
|
||||||
|
Attr("data-target") := "#repoEdit",
|
||||||
|
"Add"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
<.table(^.`class` := "table",
|
||||||
|
<.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))).toTagMod
|
||||||
|
)
|
||||||
|
),
|
||||||
|
repoEditModal((
|
||||||
|
repos
|
||||||
|
.lift(editRepoIdx)
|
||||||
|
.getOrElse("" -> MavenRepository("")),
|
||||||
|
editRepoIdx,
|
||||||
|
backend
|
||||||
|
))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.build
|
||||||
|
|
||||||
|
val options = ScalaComponent.builder[(ResolutionOptions, Backend)]("ResolutionOptions")
|
||||||
|
.render_P {
|
||||||
|
case (options, backend) =>
|
||||||
|
<.div(
|
||||||
|
<.div(^.`class` := "checkbox",
|
||||||
|
<.label(
|
||||||
|
<.input.checkbox(
|
||||||
|
^.onChange ==> backend.options.toggleOptional,
|
||||||
|
if (options.followOptional) ^.checked := true else TagMod()
|
||||||
|
),
|
||||||
|
"Follow optional dependencies"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.build
|
||||||
|
|
||||||
|
val resolution = ScalaComponent.builder[(Option[Resolution], Backend)]("Resolution")
|
||||||
|
.render_P {
|
||||||
|
case (resOpt, backend) =>
|
||||||
|
resOpt match {
|
||||||
|
case Some(res) =>
|
||||||
|
<.div(
|
||||||
|
<.div(^.`class` := "page-header",
|
||||||
|
<.h1("Resolution")
|
||||||
|
),
|
||||||
|
resultDependencies((res, backend))
|
||||||
|
)
|
||||||
|
|
||||||
|
case None =>
|
||||||
|
<.div()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.build
|
||||||
|
|
||||||
|
val initialState = State(
|
||||||
|
List(
|
||||||
|
Dependency(Module("org.apache.spark", "spark-sql_2.11"), "2.2.1") // DEBUG
|
||||||
|
),
|
||||||
|
Seq("central" -> MavenRepository("https://repo1.maven.org/maven2/")),
|
||||||
|
ResolutionOptions(),
|
||||||
|
None,
|
||||||
|
-1,
|
||||||
|
-1,
|
||||||
|
resolving = false,
|
||||||
|
reverseTree = false,
|
||||||
|
log = Nil
|
||||||
|
)
|
||||||
|
|
||||||
|
val app = ScalaComponent.builder[Unit]("Coursier")
|
||||||
|
.initialState(initialState)
|
||||||
|
.backend(new Backend(_))
|
||||||
|
.render { scope =>
|
||||||
|
|
||||||
|
val S = scope.state
|
||||||
|
val backend = scope.backend
|
||||||
|
|
||||||
|
<.div(
|
||||||
|
<.div(^.role := "tabpanel",
|
||||||
|
<.ul(^.`class` := "nav nav-tabs", ^.role := "tablist",
|
||||||
|
<.li(^.role := "presentation", ^.`class` := "active",
|
||||||
|
<.a(^.href := "#dependencies", ^.aria.controls := "dependencies", ^.role := "tab", Attr("data-toggle") := "tab",
|
||||||
|
s"Dependencies (${S.modules.length})"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
<.li(^.role := "presentation",
|
||||||
|
<.a(^.href := "#repositories", ^.aria.controls := "repositories", ^.role := "tab", Attr("data-toggle") := "tab",
|
||||||
|
s"Repositories (${S.repositories.length})"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
<.li(^.role := "presentation",
|
||||||
|
<.a(^.href := "#options", ^.aria.controls := "options", ^.role := "tab", Attr("data-toggle") := "tab",
|
||||||
|
"Options"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
<.div(^.`class` := "tab-content",
|
||||||
|
<.div(^.role := "tabpanel", ^.`class` := "tab-pane active", ^.id := "dependencies",
|
||||||
|
modules((S.modules, S.editModuleIdx, backend))
|
||||||
|
),
|
||||||
|
<.div(^.role := "tabpanel", ^.`class` := "tab-pane", ^.id := "repositories",
|
||||||
|
repositories((S.repositories, S.editRepoIdx, backend))
|
||||||
|
),
|
||||||
|
<.div(^.role := "tabpanel", ^.`class` := "tab-pane", ^.id := "options",
|
||||||
|
options((S.options, backend))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
|
<.div(<.form(^.onSubmit ==> backend.handleResolve,
|
||||||
|
<.button(^.`type` := "submit", ^.id := "resolveButton", ^.`class` := "btn btn-lg btn-primary",
|
||||||
|
^.disabled := S.resolving,
|
||||||
|
if (S.resolving) "Resolving..." else "Resolve"
|
||||||
|
)
|
||||||
|
)),
|
||||||
|
|
||||||
|
|
||||||
|
<.div(^.role := "tabpanel", ^.id := "results",
|
||||||
|
<.ul(^.`class` := "nav nav-tabs", ^.role := "tablist", ^.id := "resTabs",
|
||||||
|
<.li(^.role := "presentation", ^.id := "resResTab",
|
||||||
|
<.a(^.href := "#resolution", ^.aria.controls := "resolution", ^.role := "tab", Attr("data-toggle") := "tab",
|
||||||
|
"Resolution"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
<.li(^.role := "presentation", ^.id := "resLogTab",
|
||||||
|
<.a(^.href := "#log", ^.aria.controls := "log", ^.role := "tab", Attr("data-toggle") := "tab",
|
||||||
|
"Log"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
<.li(^.role := "presentation",
|
||||||
|
<.a(^.href := "#depgraph", ^.aria.controls := "depgraph", ^.role := "tab", Attr("data-toggle") := "tab",
|
||||||
|
"Graph"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
<.li(^.role := "presentation",
|
||||||
|
<.a(^.href := "#deptreepanel", ^.aria.controls := "deptreepanel", ^.role := "tab", Attr("data-toggle") := "tab",
|
||||||
|
"Tree"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
<.div(^.`class` := "tab-content",
|
||||||
|
<.div(^.role := "tabpanel", ^.`class` := "tab-pane", ^.id := "resolution",
|
||||||
|
resolution((S.resolutionOpt, backend))
|
||||||
|
),
|
||||||
|
<.div(^.role := "tabpanel", ^.`class` := "tab-pane", ^.id := "log",
|
||||||
|
<.button(^.`type` := "button", ^.`class` := "btn btn-default",
|
||||||
|
^.onClick ==> backend.clearLog,
|
||||||
|
"Clear"
|
||||||
|
),
|
||||||
|
<.div(^.`class` := "well",
|
||||||
|
<.ul(^.`class` := "log",
|
||||||
|
S.log.map(e => <.li(e)).toTagMod
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
<.div(^.role := "tabpanel", ^.`class` := "tab-pane", ^.id := "depgraph",
|
||||||
|
<.button(^.`type` := "button", ^.`class` := "btn btn-default",
|
||||||
|
^.onClick ==> backend.updateDepGraphBtn(S.resolutionOpt.getOrElse(Resolution.empty)),
|
||||||
|
"Redraw"
|
||||||
|
),
|
||||||
|
<.div(^.id := "depgraphcanvas")
|
||||||
|
),
|
||||||
|
<.div(^.role := "tabpanel", ^.`class` := "tab-pane", ^.id := "deptreepanel",
|
||||||
|
<.div(^.`class` := "checkbox",
|
||||||
|
<.label(
|
||||||
|
<.input.checkbox(
|
||||||
|
^.onChange ==> backend.toggleReverseTree,
|
||||||
|
if (S.reverseTree) ^.checked := true else TagMod()
|
||||||
|
),
|
||||||
|
"Reverse"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
<.div(^.id := "deptree")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.build
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -2,42 +2,30 @@ package coursier.web
|
||||||
|
|
||||||
import coursier.{Dependency, Fetch, MavenRepository, Module, Platform, Repository, Resolution}
|
import coursier.{Dependency, Fetch, MavenRepository, Module, Platform, Repository, Resolution}
|
||||||
import coursier.maven.MavenSource
|
import coursier.maven.MavenSource
|
||||||
import coursier.util.{Gather, Task}
|
import coursier.util.{EitherT, Gather, Task}
|
||||||
import japgolly.scalajs.react.vdom.{ TagMod, Attr }
|
import japgolly.scalajs.react._
|
||||||
import japgolly.scalajs.react.vdom.Attrs.dangerouslySetInnerHtml
|
import org.scalajs.dom
|
||||||
import japgolly.scalajs.react.{ ReactEventI, ReactComponentB, BackendScope }
|
|
||||||
import japgolly.scalajs.react.vdom.prefix_<^._
|
|
||||||
import scala.scalajs.concurrent.JSExecutionContext.Implicits.queue
|
|
||||||
import org.scalajs.jquery.jQuery
|
import org.scalajs.jquery.jQuery
|
||||||
|
|
||||||
import scala.concurrent.Future
|
|
||||||
|
|
||||||
import scala.scalajs.js
|
import scala.scalajs.js
|
||||||
import js.Dynamic.{ global => g }
|
import scala.util.{Failure, Success}
|
||||||
|
import js.Dynamic.{global => g}
|
||||||
|
|
||||||
final case class ResolutionOptions(
|
final class Backend($: BackendScope[_, State]) {
|
||||||
followOptional: Boolean = false
|
|
||||||
)
|
|
||||||
|
|
||||||
final case class State(
|
|
||||||
modules: Seq[Dependency],
|
|
||||||
repositories: Seq[(String, MavenRepository)],
|
|
||||||
options: ResolutionOptions,
|
|
||||||
resolutionOpt: Option[Resolution],
|
|
||||||
editModuleIdx: Int,
|
|
||||||
editRepoIdx: Int,
|
|
||||||
resolving: Boolean,
|
|
||||||
reverseTree: Boolean,
|
|
||||||
log: Seq[String]
|
|
||||||
)
|
|
||||||
|
|
||||||
class Backend($: BackendScope[Unit, State]) {
|
|
||||||
|
|
||||||
def fetch(
|
def fetch(
|
||||||
repositories: Seq[Repository],
|
repositories: Seq[Repository],
|
||||||
fetch: Fetch.Content[Task]
|
fetch: Fetch.Content[Task]
|
||||||
): Fetch.Metadata[Task] = {
|
): Fetch.Metadata[Task] = {
|
||||||
|
|
||||||
|
val fetch0: Fetch.Content[Task] = { a =>
|
||||||
|
if (a.url.endsWith("/"))
|
||||||
|
// don't fetch directory listings
|
||||||
|
EitherT[Task, String, String](Task.point(Left("")))
|
||||||
|
else
|
||||||
|
fetch(a)
|
||||||
|
}
|
||||||
|
|
||||||
modVers => Gather[Task].gather(
|
modVers => Gather[Task].gather(
|
||||||
modVers.map { case (module, version) =>
|
modVers.map { case (module, version) =>
|
||||||
Fetch.find(repositories, module, version, fetch)
|
Fetch.find(repositories, module, version, fetch)
|
||||||
|
|
@ -102,7 +90,7 @@ class Backend($: BackendScope[Unit, State]) {
|
||||||
println("Rendered canvas")
|
println("Rendered canvas")
|
||||||
}
|
}
|
||||||
|
|
||||||
def updateDepGraphBtn(resolution: Resolution)(e: ReactEventI) = {
|
def updateDepGraphBtn(resolution: Resolution)(e: raw.SyntheticEvent[_]) = CallbackTo[Unit] {
|
||||||
updateDepGraph(resolution)
|
updateDepGraph(resolution)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -150,81 +138,95 @@ class Backend($: BackendScope[Unit, State]) {
|
||||||
.treeview(js.Dictionary("data" -> js.Array(minDependencies.toList.map(tree): _*)))
|
.treeview(js.Dictionary("data" -> js.Array(minDependencies.toList.map(tree): _*)))
|
||||||
}
|
}
|
||||||
|
|
||||||
def resolve(action: => Unit = ()) = {
|
def resolve(action: => Unit = ()): CallbackTo[Unit] = {
|
||||||
|
|
||||||
g.$("#resLogTab a:last").tab("show")
|
g.$("#resLogTab a:last").tab("show")
|
||||||
$.modState(_.copy(resolving = true, log = Nil))
|
$.modState(_.copy(resolving = true, log = Nil)).runNow()
|
||||||
|
|
||||||
val logger: Platform.Logger = new Platform.Logger {
|
val logger: Platform.Logger = new Platform.Logger {
|
||||||
def fetched(url: String) = {
|
def fetched(url: String) = {
|
||||||
println(s"<- $url")
|
println(s"<- $url")
|
||||||
$.modState(s => s.copy(log = s"<- $url" +: s.log))
|
$.modState(s => s.copy(log = s"<- $url" +: s.log)).runNow()
|
||||||
}
|
}
|
||||||
def fetching(url: String) = {
|
def fetching(url: String) = {
|
||||||
println(s"-> $url")
|
println(s"-> $url")
|
||||||
$.modState(s => s.copy(log = s"-> $url" +: s.log))
|
$.modState(s => s.copy(log = s"-> $url" +: s.log)).runNow()
|
||||||
}
|
}
|
||||||
def other(url: String, msg: String) = {
|
def other(url: String, msg: String) = {
|
||||||
println(s"$url: $msg")
|
println(s"$url: $msg")
|
||||||
$.modState(s => s.copy(log = s"$url: $msg" +: s.log))
|
$.modState(s => s.copy(log = s"$url: $msg" +: s.log)).runNow()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val s = $.state
|
$.state.map { s =>
|
||||||
def task = {
|
|
||||||
val res = coursier.Resolution(
|
def task = {
|
||||||
s.modules.toSet,
|
val res = coursier.Resolution(
|
||||||
filter = Some(dep =>
|
s.modules.toSet,
|
||||||
s.options.followOptional || !dep.optional
|
filter = Some(dep =>
|
||||||
|
s.options.followOptional || !dep.optional
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
|
||||||
res
|
res
|
||||||
.process
|
.process
|
||||||
.run(fetch(s.repositories.map { case (_, repo) => repo }, Platform.artifactWithLogger(logger)), 100)
|
.run(fetch(s.repositories.map { case (_, repo) => repo }, Platform.artifactWithLogger(logger)), 100)
|
||||||
}
|
|
||||||
|
|
||||||
// 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.)
|
|
||||||
Future(task)(scala.scalajs.concurrent.JSExecutionContext.Implicits.queue).flatMap(_.future()).foreach { res: Resolution =>
|
|
||||||
$.modState{ s =>
|
|
||||||
updateDepGraph(res)
|
|
||||||
updateTree(res, "#deptree", reverse = s.reverseTree)
|
|
||||||
|
|
||||||
s.copy(
|
|
||||||
resolutionOpt = Some(res),
|
|
||||||
resolving = false
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
g.$("#resResTab a:last")
|
implicit val ec = scala.scalajs.concurrent.JSExecutionContext.Implicits.queue
|
||||||
.tab("show")
|
|
||||||
|
task.map { res: Resolution =>
|
||||||
|
$.modState { s =>
|
||||||
|
updateDepGraph(res)
|
||||||
|
updateTree(res, "#deptree", reverse = s.reverseTree)
|
||||||
|
|
||||||
|
s.copy(
|
||||||
|
resolutionOpt = Some(res),
|
||||||
|
resolving = false
|
||||||
|
)
|
||||||
|
}.runNow()
|
||||||
|
|
||||||
|
g.$("#resResTab a:last")
|
||||||
|
.tab("show")
|
||||||
|
}.future()(ec).onComplete {
|
||||||
|
case Success(_) =>
|
||||||
|
case Failure(t) =>
|
||||||
|
println(s"Caught exception: $t")
|
||||||
|
}
|
||||||
|
|
||||||
|
()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
def handleResolve(e: ReactEventI) = {
|
def handleResolve(e: raw.SyntheticEvent[_]) = {
|
||||||
println(s"Resolving")
|
|
||||||
e.preventDefault()
|
val c = CallbackTo[Unit] {
|
||||||
jQuery("#results").css("display", "block")
|
println(s"Resolving")
|
||||||
resolve()
|
e.preventDefault()
|
||||||
|
jQuery("#results").css("display", "block")
|
||||||
|
}
|
||||||
|
|
||||||
|
c.flatMap { _ =>
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def clearLog(e: ReactEventI) = {
|
def clearLog(e: raw.SyntheticEvent[_]) = {
|
||||||
$.modState(_.copy(log = Nil))
|
$.modState(_.copy(log = Nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
def toggleReverseTree(e: ReactEventI) = {
|
def toggleReverseTree(e: raw.SyntheticEvent[_]) =
|
||||||
$.modState{ s =>
|
$.modState { s =>
|
||||||
for (res <- s.resolutionOpt)
|
for (res <- s.resolutionOpt)
|
||||||
updateTree(res, "#deptree", reverse = !s.reverseTree)
|
updateTree(res, "#deptree", reverse = !s.reverseTree)
|
||||||
s.copy(reverseTree = !s.reverseTree)
|
s.copy(reverseTree = !s.reverseTree)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
def editModule(idx: Int)(e: ReactEventI) = {
|
def editModule(idx: Int)(e: raw.SyntheticEvent[_]) = {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
$.modState(_.copy(editModuleIdx = idx))
|
$.modState(_.copy(editModuleIdx = idx))
|
||||||
}
|
}
|
||||||
|
|
||||||
def removeModule(idx: Int)(e: ReactEventI) = {
|
def removeModule(idx: Int)(e: raw.SyntheticEvent[_]) = {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
$.modState(s =>
|
$.modState(s =>
|
||||||
s.copy(
|
s.copy(
|
||||||
|
|
@ -236,21 +238,22 @@ class Backend($: BackendScope[Unit, State]) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
def updateModule(moduleIdx: Int, update: (Dependency, String) => Dependency)(e: ReactEventI) = {
|
def updateModule(moduleIdx: Int, update: (Dependency, String) => Dependency)(e: raw.SyntheticEvent[dom.raw.HTMLInputElement]) =
|
||||||
if (moduleIdx >= 0) {
|
if (moduleIdx >= 0) {
|
||||||
$.modState{ state =>
|
e.persist()
|
||||||
|
$.modState { state =>
|
||||||
val dep = state.modules(moduleIdx)
|
val dep = state.modules(moduleIdx)
|
||||||
state.copy(
|
state.copy(
|
||||||
modules = state.modules
|
modules = state.modules
|
||||||
.updated(moduleIdx, update(dep, e.target.value))
|
.updated(moduleIdx, update(dep, e.target.value))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
} else
|
||||||
}
|
CallbackTo.pure(())
|
||||||
|
|
||||||
def addModule(e: ReactEventI) = {
|
def addModule(e: raw.SyntheticEvent[_]) = {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
$.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(
|
state.copy(
|
||||||
|
|
@ -260,12 +263,12 @@ class Backend($: BackendScope[Unit, State]) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def editRepo(idx: Int)(e: ReactEventI) = {
|
def editRepo(idx: Int)(e: raw.SyntheticEvent[_]) = {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
$.modState(_.copy(editRepoIdx = idx))
|
$.modState(_.copy(editRepoIdx = idx))
|
||||||
}
|
}
|
||||||
|
|
||||||
def removeRepo(idx: Int)(e: ReactEventI) = {
|
def removeRepo(idx: Int)(e: raw.SyntheticEvent[_]) = {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
$.modState(s =>
|
$.modState(s =>
|
||||||
s.copy(
|
s.copy(
|
||||||
|
|
@ -277,7 +280,7 @@ class Backend($: BackendScope[Unit, State]) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
def moveRepo(idx: Int, up: Boolean)(e: ReactEventI) = {
|
def moveRepo(idx: Int, up: Boolean)(e: raw.SyntheticEvent[_]) = {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
$.modState { s =>
|
$.modState { s =>
|
||||||
val idx0 = if (up) idx - 1 else idx + 1
|
val idx0 = if (up) idx - 1 else idx + 1
|
||||||
|
|
@ -297,21 +300,21 @@ class Backend($: BackendScope[Unit, State]) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def updateRepo(repoIdx: Int, update: ((String, MavenRepository), String) => (String, MavenRepository))(e: ReactEventI) = {
|
def updateRepo(repoIdx: Int, update: ((String, MavenRepository), String) => (String, MavenRepository))(e: raw.SyntheticEvent[dom.raw.HTMLInputElement]) =
|
||||||
if (repoIdx >= 0) {
|
if (repoIdx >= 0)
|
||||||
$.modState{ state =>
|
$.modState { state =>
|
||||||
val repo = state.repositories(repoIdx)
|
val repo = state.repositories(repoIdx)
|
||||||
state.copy(
|
state.copy(
|
||||||
repositories = state.repositories
|
repositories = state.repositories
|
||||||
.updated(repoIdx, update(repo, e.target.value))
|
.updated(repoIdx, update(repo, e.target.value))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
else
|
||||||
}
|
CallbackTo.pure(())
|
||||||
|
|
||||||
def addRepo(e: ReactEventI) = {
|
def addRepo(e: raw.SyntheticEvent[_]) = {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
$.modState{ state =>
|
$.modState { state =>
|
||||||
val repositories = state.repositories :+ ("" -> MavenRepository(""))
|
val repositories = state.repositories :+ ("" -> MavenRepository(""))
|
||||||
println(s"Repositories:\n${repositories.mkString("\n")}")
|
println(s"Repositories:\n${repositories.mkString("\n")}")
|
||||||
state.copy(
|
state.copy(
|
||||||
|
|
@ -321,13 +324,13 @@ class Backend($: BackendScope[Unit, State]) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def enablePopover(e: ReactEventI) = {
|
def enablePopover(e: raw.SyntheticMouseEvent[_]) = CallbackTo[Unit] {
|
||||||
g.$("[data-toggle='popover']")
|
g.$("[data-toggle='popover']")
|
||||||
.popover()
|
.popover()
|
||||||
}
|
}
|
||||||
|
|
||||||
object options {
|
object options {
|
||||||
def toggleOptional(e: ReactEventI) = {
|
def toggleOptional(e: raw.SyntheticEvent[_]) = {
|
||||||
$.modState(s =>
|
$.modState(s =>
|
||||||
s.copy(
|
s.copy(
|
||||||
options = s.options
|
options = s.options
|
||||||
|
|
@ -337,485 +340,3 @@ class Backend($: BackendScope[Unit, State]) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object App {
|
|
||||||
|
|
||||||
lazy val arbor = g.arbor
|
|
||||||
|
|
||||||
val resultDependencies = ReactComponentB[(Resolution, Backend)]("Result")
|
|
||||||
.render{ T =>
|
|
||||||
val (res, backend) = T
|
|
||||||
|
|
||||||
def infoLabel(label: String) =
|
|
||||||
<.span(^.`class` := "label label-info", label)
|
|
||||||
def errorPopOver(label: String, desc: String) =
|
|
||||||
popOver("danger", label, desc)
|
|
||||||
def infoPopOver(label: String, desc: String) =
|
|
||||||
popOver("info", label, desc)
|
|
||||||
def popOver(`type`: String, label: String, desc: String) =
|
|
||||||
<.button(^.`type` := "button", ^.`class` := s"btn btn-xs btn-${`type`}",
|
|
||||||
Attr("data-trigger") := "focus",
|
|
||||||
Attr("data-toggle") := "popover", Attr("data-placement") := "bottom",
|
|
||||||
Attr("data-content") := desc,
|
|
||||||
^.onClick ==> backend.enablePopover,
|
|
||||||
^.onMouseOver ==> backend.enablePopover,
|
|
||||||
label
|
|
||||||
)
|
|
||||||
|
|
||||||
def depItem(dep: Dependency, finalVersionOpt: Option[String]) = {
|
|
||||||
<.tr(
|
|
||||||
^.`class` := (if (res.errorCache.contains(dep.moduleVersion)) "danger" else ""),
|
|
||||||
<.td(dep.module.organization),
|
|
||||||
<.td(dep.module.name),
|
|
||||||
<.td(finalVersionOpt.fold(dep.version)(finalVersion => s"$finalVersion (for ${dep.version})")),
|
|
||||||
<.td(Seq[Seq[TagMod]](
|
|
||||||
if (dep.configuration == "compile") Seq() else Seq(infoLabel(dep.configuration)),
|
|
||||||
if (dep.attributes.`type`.isEmpty || dep.attributes.`type` == "jar") Seq() else Seq(infoLabel(dep.attributes.`type`)),
|
|
||||||
if (dep.attributes.classifier.isEmpty) Seq() else Seq(infoLabel(dep.attributes.classifier)),
|
|
||||||
Some(dep.exclusions).filter(_.nonEmpty).map(excls => infoPopOver("Exclusions", excls.toList.sorted.map{case (org, name) => s"$org:$name"}.mkString("; "))).toSeq,
|
|
||||||
if (dep.optional) Seq(infoLabel("optional")) else Seq(),
|
|
||||||
res.errorCache.get(dep.moduleVersion).map(errs => errorPopOver("Error", errs.mkString("; "))).toSeq
|
|
||||||
)),
|
|
||||||
<.td(Seq[Seq[TagMod]](
|
|
||||||
res.projectCache.get(dep.moduleVersion) match {
|
|
||||||
case Some((source: MavenSource, proj)) =>
|
|
||||||
// FIXME Maven specific, generalize with source.artifacts
|
|
||||||
val version0 = finalVersionOpt getOrElse dep.version
|
|
||||||
val relPath =
|
|
||||||
dep.module.organization.split('.').toSeq ++ Seq(
|
|
||||||
dep.module.name,
|
|
||||||
version0,
|
|
||||||
s"${dep.module.name}-$version0"
|
|
||||||
)
|
|
||||||
|
|
||||||
val root = source.root
|
|
||||||
|
|
||||||
Seq(
|
|
||||||
<.a(^.href := s"$root${relPath.mkString("/")}.pom",
|
|
||||||
<.span(^.`class` := "label label-info", "POM")
|
|
||||||
),
|
|
||||||
<.a(^.href := s"$root${relPath.mkString("/")}.jar",
|
|
||||||
<.span(^.`class` := "label label-info", "JAR")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
case _ => Seq()
|
|
||||||
}
|
|
||||||
))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val sortedDeps = res.minDependencies.toList
|
|
||||||
.sortBy { dep =>
|
|
||||||
val (org, name, _) = coursier.core.Module.unapply(dep.module).get
|
|
||||||
(org, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
<.table(^.`class` := "table",
|
|
||||||
<.thead(
|
|
||||||
<.tr(
|
|
||||||
<.th("Organization"),
|
|
||||||
<.th("Name"),
|
|
||||||
<.th("Version"),
|
|
||||||
<.th("Extra"),
|
|
||||||
<.th("Links")
|
|
||||||
)
|
|
||||||
),
|
|
||||||
<.tbody(
|
|
||||||
sortedDeps.map(dep =>
|
|
||||||
depItem(
|
|
||||||
dep,
|
|
||||||
res
|
|
||||||
.projectCache
|
|
||||||
.get(dep.moduleVersion)
|
|
||||||
.map(_._2.version)
|
|
||||||
.filter(_ != dep.version)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.build
|
|
||||||
|
|
||||||
object icon {
|
|
||||||
def apply(id: String) = <.span(^.`class` := s"glyphicon glyphicon-$id", ^.aria.hidden := "true")
|
|
||||||
def ok = apply("ok")
|
|
||||||
def edit = apply("pencil")
|
|
||||||
def remove = apply("remove")
|
|
||||||
def up = apply("arrow-up")
|
|
||||||
def down = apply("arrow-down")
|
|
||||||
}
|
|
||||||
|
|
||||||
val moduleEditModal = ReactComponentB[((Module, String), Int, Backend)]("EditModule")
|
|
||||||
.render{ P =>
|
|
||||||
val ((module, version), moduleIdx, backend) = P
|
|
||||||
<.div(^.`class` := "modal fade", ^.id := "moduleEdit", ^.role := "dialog", ^.aria.labelledby := "moduleEditTitle",
|
|
||||||
<.div(^.`class` := "modal-dialog", <.div(^.`class` := "modal-content",
|
|
||||||
<.div(^.`class` := "modal-header",
|
|
||||||
<.button(^.`type` := "button", ^.`class` := "close", Attr("data-dismiss") := "modal", ^.aria.label := "Close",
|
|
||||||
<.span(^.aria.hidden := "true", dangerouslySetInnerHtml("×"))
|
|
||||||
),
|
|
||||||
<.h4(^.`class` := "modal-title", ^.id := "moduleEditTitle", "Dependency")
|
|
||||||
),
|
|
||||||
<.div(^.`class` := "modal-body",
|
|
||||||
<.form(
|
|
||||||
<.div(^.`class` := "form-group",
|
|
||||||
<.label(^.`for` := "inputOrganization", "Organization"),
|
|
||||||
<.input(^.`class` := "form-control", ^.id := "inputOrganization", ^.placeholder := "Organization",
|
|
||||||
^.onChange ==> backend.updateModule(moduleIdx, (dep, value) => dep.copy(module = dep.module.copy(organization = value))),
|
|
||||||
^.value := module.organization
|
|
||||||
)
|
|
||||||
),
|
|
||||||
<.div(^.`class` := "form-group",
|
|
||||||
<.label(^.`for` := "inputName", "Name"),
|
|
||||||
<.input(^.`class` := "form-control", ^.id := "inputName", ^.placeholder := "Name",
|
|
||||||
^.onChange ==> backend.updateModule(moduleIdx, (dep, value) => dep.copy(module = dep.module.copy(name = value))),
|
|
||||||
^.value := module.name
|
|
||||||
)
|
|
||||||
),
|
|
||||||
<.div(^.`class` := "form-group",
|
|
||||||
<.label(^.`for` := "inputVersion", "Version"),
|
|
||||||
<.input(^.`class` := "form-control", ^.id := "inputVersion", ^.placeholder := "Version",
|
|
||||||
^.onChange ==> backend.updateModule(moduleIdx, (dep, value) => dep.copy(version = value)),
|
|
||||||
^.value := version
|
|
||||||
)
|
|
||||||
),
|
|
||||||
<.div(^.`class` := "modal-footer",
|
|
||||||
<.button(^.`type` := "submit", ^.`class` := "btn btn-primary", Attr("data-dismiss") := "modal", "Done")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.build
|
|
||||||
|
|
||||||
val modules = ReactComponentB[(Seq[Dependency], Int, Backend)]("Dependencies")
|
|
||||||
.render{ P =>
|
|
||||||
val (deps, editModuleIdx, backend) = P
|
|
||||||
|
|
||||||
def depItem(dep: Dependency, idx: Int) =
|
|
||||||
<.tr(
|
|
||||||
<.td(dep.module.organization),
|
|
||||||
<.td(dep.module.name),
|
|
||||||
<.td(dep.version),
|
|
||||||
<.td(
|
|
||||||
<.a(Attr("data-toggle") := "modal", Attr("data-target") := "#moduleEdit", ^.`class` := "icon-action",
|
|
||||||
^.onClick ==> backend.editModule(idx),
|
|
||||||
icon.edit
|
|
||||||
)
|
|
||||||
),
|
|
||||||
<.td(
|
|
||||||
<.a(Attr("data-toggle") := "modal", Attr("data-target") := "#moduleRemove", ^.`class` := "icon-action",
|
|
||||||
^.onClick ==> backend.removeModule(idx),
|
|
||||||
icon.remove
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
<.div(
|
|
||||||
<.p(
|
|
||||||
<.button(^.`type` := "button", ^.`class` := "btn btn-default customButton",
|
|
||||||
^.onClick ==> backend.addModule,
|
|
||||||
Attr("data-toggle") := "modal",
|
|
||||||
Attr("data-target") := "#moduleEdit",
|
|
||||||
"Add"
|
|
||||||
)
|
|
||||||
),
|
|
||||||
<.table(^.`class` := "table",
|
|
||||||
<.thead(
|
|
||||||
<.tr(
|
|
||||||
<.th("Organization"),
|
|
||||||
<.th("Name"),
|
|
||||||
<.th("Version"),
|
|
||||||
<.th(""),
|
|
||||||
<.th("")
|
|
||||||
)
|
|
||||||
),
|
|
||||||
<.tbody(
|
|
||||||
deps.zipWithIndex
|
|
||||||
.map((depItem _).tupled)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
moduleEditModal((
|
|
||||||
deps
|
|
||||||
.lift(editModuleIdx)
|
|
||||||
.fold(Module("", "") -> "")(_.moduleVersion),
|
|
||||||
editModuleIdx,
|
|
||||||
backend
|
|
||||||
))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.build
|
|
||||||
|
|
||||||
val repoEditModal = ReactComponentB[((String, MavenRepository), Int, Backend)]("EditRepo")
|
|
||||||
.render{ P =>
|
|
||||||
val ((name, repo), repoIdx, backend) = P
|
|
||||||
<.div(^.`class` := "modal fade", ^.id := "repoEdit", ^.role := "dialog", ^.aria.labelledby := "repoEditTitle",
|
|
||||||
<.div(^.`class` := "modal-dialog", <.div(^.`class` := "modal-content",
|
|
||||||
<.div(^.`class` := "modal-header",
|
|
||||||
<.button(^.`type` := "button", ^.`class` := "close", Attr("data-dismiss") := "modal", ^.aria.label := "Close",
|
|
||||||
<.span(^.aria.hidden := "true", dangerouslySetInnerHtml("×"))
|
|
||||||
),
|
|
||||||
<.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
|
|
||||||
))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
<.div(
|
|
||||||
<.p(
|
|
||||||
<.button(^.`type` := "button", ^.`class` := "btn btn-default customButton",
|
|
||||||
^.onClick ==> backend.addRepo,
|
|
||||||
Attr("data-toggle") := "modal",
|
|
||||||
Attr("data-target") := "#repoEdit",
|
|
||||||
"Add"
|
|
||||||
)
|
|
||||||
),
|
|
||||||
<.table(^.`class` := "table",
|
|
||||||
<.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
|
|
||||||
|
|
||||||
val options = ReactComponentB[(ResolutionOptions, Backend)]("ResolutionOptions")
|
|
||||||
.render{ P =>
|
|
||||||
val (options, backend) = P
|
|
||||||
|
|
||||||
<.div(
|
|
||||||
<.div(^.`class` := "checkbox",
|
|
||||||
<.label(
|
|
||||||
<.input(^.`type` := "checkbox",
|
|
||||||
^.onChange ==> backend.options.toggleOptional,
|
|
||||||
if (options.followOptional) Seq(^.checked := "checked") else Seq(),
|
|
||||||
"Follow optional dependencies"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.build
|
|
||||||
|
|
||||||
val resolution = ReactComponentB[(Option[Resolution], Backend)]("Resolution")
|
|
||||||
.render{ T =>
|
|
||||||
val (resOpt, backend) = T
|
|
||||||
|
|
||||||
resOpt match {
|
|
||||||
case Some(res) =>
|
|
||||||
<.div(
|
|
||||||
<.div(^.`class` := "page-header",
|
|
||||||
<.h1("Resolution")
|
|
||||||
),
|
|
||||||
resultDependencies((res, backend))
|
|
||||||
)
|
|
||||||
|
|
||||||
case None =>
|
|
||||||
<.div()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.build
|
|
||||||
|
|
||||||
val initialState = State(
|
|
||||||
Nil,
|
|
||||||
Seq("central" -> MavenRepository("https://repo1.maven.org/maven2/")),
|
|
||||||
ResolutionOptions(),
|
|
||||||
None,
|
|
||||||
-1,
|
|
||||||
-1,
|
|
||||||
resolving = false,
|
|
||||||
reverseTree = false,
|
|
||||||
log = Nil
|
|
||||||
)
|
|
||||||
|
|
||||||
val app = ReactComponentB[Unit]("Coursier")
|
|
||||||
.initialState(initialState)
|
|
||||||
.backend(new Backend(_))
|
|
||||||
.render((_,S,B) =>
|
|
||||||
<.div(
|
|
||||||
<.div(^.role := "tabpanel",
|
|
||||||
<.ul(^.`class` := "nav nav-tabs", ^.role := "tablist",
|
|
||||||
<.li(^.role := "presentation", ^.`class` := "active",
|
|
||||||
<.a(^.href := "#dependencies", ^.aria.controls := "dependencies", ^.role := "tab", Attr("data-toggle") := "tab",
|
|
||||||
s"Dependencies (${S.modules.length})"
|
|
||||||
)
|
|
||||||
),
|
|
||||||
<.li(^.role := "presentation",
|
|
||||||
<.a(^.href := "#repositories", ^.aria.controls := "repositories", ^.role := "tab", Attr("data-toggle") := "tab",
|
|
||||||
s"Repositories (${S.repositories.length})"
|
|
||||||
)
|
|
||||||
),
|
|
||||||
<.li(^.role := "presentation",
|
|
||||||
<.a(^.href := "#options", ^.aria.controls := "options", ^.role := "tab", Attr("data-toggle") := "tab",
|
|
||||||
"Options"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
<.div(^.`class` := "tab-content",
|
|
||||||
<.div(^.role := "tabpanel", ^.`class` := "tab-pane active", ^.id := "dependencies",
|
|
||||||
modules((S.modules, S.editModuleIdx, B))
|
|
||||||
),
|
|
||||||
<.div(^.role := "tabpanel", ^.`class` := "tab-pane", ^.id := "repositories",
|
|
||||||
repositories((S.repositories, S.editRepoIdx, B))
|
|
||||||
),
|
|
||||||
<.div(^.role := "tabpanel", ^.`class` := "tab-pane", ^.id := "options",
|
|
||||||
options((S.options, B))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
|
|
||||||
<.div(<.form(^.onSubmit ==> B.handleResolve,
|
|
||||||
<.button(^.`type` := "submit", ^.id := "resolveButton", ^.`class` := "btn btn-lg btn-primary",
|
|
||||||
if (S.resolving) ^.disabled := "true" else Attr("active") := "true",
|
|
||||||
if (S.resolving) "Resolving..." else "Resolve"
|
|
||||||
)
|
|
||||||
)),
|
|
||||||
|
|
||||||
|
|
||||||
<.div(^.role := "tabpanel", ^.id := "results",
|
|
||||||
<.ul(^.`class` := "nav nav-tabs", ^.role := "tablist", ^.id := "resTabs",
|
|
||||||
<.li(^.role := "presentation", ^.id := "resResTab",
|
|
||||||
<.a(^.href := "#resolution", ^.aria.controls := "resolution", ^.role := "tab", Attr("data-toggle") := "tab",
|
|
||||||
"Resolution"
|
|
||||||
)
|
|
||||||
),
|
|
||||||
<.li(^.role := "presentation", ^.id := "resLogTab",
|
|
||||||
<.a(^.href := "#log", ^.aria.controls := "log", ^.role := "tab", Attr("data-toggle") := "tab",
|
|
||||||
"Log"
|
|
||||||
)
|
|
||||||
),
|
|
||||||
<.li(^.role := "presentation",
|
|
||||||
<.a(^.href := "#depgraph", ^.aria.controls := "depgraph", ^.role := "tab", Attr("data-toggle") := "tab",
|
|
||||||
"Graph"
|
|
||||||
)
|
|
||||||
),
|
|
||||||
<.li(^.role := "presentation",
|
|
||||||
<.a(^.href := "#deptreepanel", ^.aria.controls := "deptreepanel", ^.role := "tab", Attr("data-toggle") := "tab",
|
|
||||||
"Tree"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
<.div(^.`class` := "tab-content",
|
|
||||||
<.div(^.role := "tabpanel", ^.`class` := "tab-pane", ^.id := "resolution",
|
|
||||||
resolution((S.resolutionOpt, B))
|
|
||||||
),
|
|
||||||
<.div(^.role := "tabpanel", ^.`class` := "tab-pane", ^.id := "log",
|
|
||||||
<.button(^.`type` := "button", ^.`class` := "btn btn-default",
|
|
||||||
^.onClick ==> B.clearLog,
|
|
||||||
"Clear"
|
|
||||||
),
|
|
||||||
<.div(^.`class` := "well",
|
|
||||||
<.ul(^.`class` := "log",
|
|
||||||
S.log.map(e => <.li(e))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
<.div(^.role := "tabpanel", ^.`class` := "tab-pane", ^.id := "depgraph",
|
|
||||||
<.button(^.`type` := "button", ^.`class` := "btn btn-default",
|
|
||||||
^.onClick ==> B.updateDepGraphBtn(S.resolutionOpt.getOrElse(Resolution.empty)),
|
|
||||||
"Redraw"
|
|
||||||
),
|
|
||||||
<.div(^.id := "depgraphcanvas")
|
|
||||||
),
|
|
||||||
<.div(^.role := "tabpanel", ^.`class` := "tab-pane", ^.id := "deptreepanel",
|
|
||||||
<.div(^.`class` := "checkbox",
|
|
||||||
<.label(
|
|
||||||
<.input(^.`type` := "checkbox",
|
|
||||||
^.onChange ==> B.toggleReverseTree,
|
|
||||||
if (S.reverseTree) Seq(^.checked := "checked") else Seq(),
|
|
||||||
"Reverse"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
<.div(^.id := "deptree")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.buildU
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,12 @@
|
||||||
package coursier.web
|
package coursier.web
|
||||||
|
|
||||||
import japgolly.scalajs.react.React
|
|
||||||
|
|
||||||
import scala.scalajs.js.annotation.{JSExport, JSExportTopLevel}
|
import scala.scalajs.js.annotation.{JSExport, JSExportTopLevel}
|
||||||
import org.scalajs.dom.document
|
import org.scalajs.dom.document
|
||||||
|
|
||||||
@JSExportTopLevel("CoursierWeb")
|
@JSExportTopLevel("CoursierWeb")
|
||||||
object Main {
|
object Main {
|
||||||
@JSExport
|
@JSExport
|
||||||
def main(): Unit = {
|
def main(): Unit =
|
||||||
React.render(App.app("Coursier"), document.getElementById("demoContent"))
|
App.app()
|
||||||
}
|
.renderIntoDOM(document.getElementById("demoContent"))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
package coursier.web
|
||||||
|
|
||||||
|
final case class ResolutionOptions(
|
||||||
|
followOptional: Boolean = false
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
package coursier.web
|
||||||
|
|
||||||
|
import coursier.{Dependency, MavenRepository, Resolution}
|
||||||
|
|
||||||
|
final case class State(
|
||||||
|
modules: Seq[Dependency],
|
||||||
|
repositories: Seq[(String, MavenRepository)],
|
||||||
|
options: ResolutionOptions,
|
||||||
|
resolutionOpt: Option[Resolution],
|
||||||
|
editModuleIdx: Int,
|
||||||
|
editRepoIdx: Int,
|
||||||
|
resolving: Boolean,
|
||||||
|
reverseTree: Boolean,
|
||||||
|
log: Seq[String]
|
||||||
|
)
|
||||||
Loading…
Reference in New Issue