Skip to content

Commit

Permalink
Merge pull request #198 from scalacenter/issue/122
Browse files Browse the repository at this point in the history
Fix #122: Add autocomplete bar
  • Loading branch information
MasseGuillaume committed Jul 20, 2016
2 parents b31012f + cea98a4 commit 36c980d
Show file tree
Hide file tree
Showing 10 changed files with 262 additions and 10 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Expand Up @@ -18,4 +18,4 @@ git:
submodules: false

before_script:
- 'gem install sass --version "=3.2.12"'
- 'gem install sass --version "=3.2.12"'
7 changes: 4 additions & 3 deletions README.MD
Expand Up @@ -94,11 +94,12 @@ sbt scaladex:publish
$ git submodule init
$ git submodule update
$ sbt
> data/reStart elastic
> data/reStart elastic # do only once to populate the indices
> project server
> ~re-start
open http://localhost:8080
```

Then, open `localhost:8080` in your browser.

## How to publish the Scaladex-Plugin

Expand All @@ -124,4 +125,4 @@ The idea behind sbt-scaladex is to notify the scala index of a new libraries bei
sbt server/universal:packageBin
git clone git@github.com:scalacenter/scaladex.git
nohup ~/webapp/bin/server -Dconfig.file=scaladex-credentials/application.conf &
```
```
24 changes: 21 additions & 3 deletions build.sbt
@@ -1,3 +1,6 @@
import ScalaJSHelper._
import org.scalajs.sbtplugin.cross.CrossProject

lazy val baseSettings = Seq(
organization := "ch.epfl.scala.index",
version := "0.1.3"
Expand Down Expand Up @@ -43,8 +46,21 @@ lazy val template = project
.dependsOn(model)
.enablePlugins(SbtTwirl)

lazy val server = project
lazy val scaladex =
CrossProject("server", "client", new File("."), CustomCrossType)
.settings(commonSettings: _*)
.settings(
libraryDependencies ++= Seq(
"com.lihaoyi" %%% "scalatags" % Version.scalatags,
"com.lihaoyi" %%% "upickle" % Version.upickle,
"com.lihaoyi" %%% "autowire" % Version.autowire
)
)

lazy val client = scaladex.js.dependsOn(model)

lazy val server = scaladex.jvm
.settings(packageScalaJS(client))
.settings(
resolvers += Resolver.bintrayRepo("btomala", "maven"),
libraryDependencies ++= Seq(
Expand All @@ -56,7 +72,8 @@ lazy val server = project
"org.webjars.bower" % "bootstrap-select" % "1.10.0",
"org.webjars.bower" % "font-awesome" % "4.6.3",
"org.webjars.bower" % "jQuery" % "2.2.4",
"org.webjars.bower" % "select2" % "4.0.3"
"org.webjars.bower" % "select2" % "4.0.3",
"com.lihaoyi" %%% "scalatags" % "0.6.0"
),
reStart <<= reStart.dependsOn(WebKeys.assets in Assets),
unmanagedResourceDirectories in Compile += (WebKeys.public in Assets).value,
Expand All @@ -66,14 +83,15 @@ lazy val server = project
"-Xmx3g"
)
)
.dependsOn(template, data)
.dependsOn(client, template, data)
.enablePlugins(SbtSass, JavaServerAppPackaging)

lazy val model = project
.settings(commonSettings: _*)
.settings(
libraryDependencies += "com.lihaoyi" %% "fastparse" % "0.3.7"
)
.enablePlugins(ScalaJSPlugin)

lazy val data = project
.settings(commonSettings: _*)
Expand Down
100 changes: 100 additions & 0 deletions client/src/main/scala/ch/epfl/scala/index/client/Client.scala
@@ -0,0 +1,100 @@
package ch.epfl.scala.index
package client

import autowire._
import api.Api
import api.Api.Autocompletion
import rpc.AutowireClient
import org.scalajs.dom

import scalatags.JsDom.all._
import org.scalajs.dom.{Event, document}

import scala.scalajs.js.annotation.JSExport
import org.scalajs.dom.raw.{Element, HTMLInputElement, Node}

import scalajs.concurrent.JSExecutionContext.Implicits.queue
import scala.concurrent.Future
import scala.scalajs.js.JSApp
import scala.util.Try

trait ClientBase {

val searchId = "search"
val resultElementId = "list-result"

def getResultList: Option[Element] = getElement(resultElementId)

def getSearchBox: Option[Element] =
getElement(searchId)

def getSearchInput: Option[HTMLInputElement] =
getSearchBox.map(_.getInput)

def getElement(id: String): Option[Element] =
Try(document.getElementById(id)).toOption

def appendResult(
owner: String, repo: String, description: String): Option[Node] = {
for {
resultContainer <- getResultList
newItem = newProjectItem(owner, repo, description)
} yield resultContainer.appendChild(newItem)
}

def newProjectItem(
owner: String, repo: String, description: String): Element = {
li(
a(href := s"/$owner/$repo")(
p(s"$owner / $repo"),
span(description)
)
).render
}

def getQuery(input: Option[HTMLInputElement]): Option[String] = input match {
case Some(i) if i.value.length > 1 => Option(i.value)
case _ => None
}

def getProjects(query: String) =
AutowireClient[Api].search(query).call()

def showResults(
projects: List[Autocompletion]): List[Option[Node]] =
projects.map {
case result =>
val (ref, description) = result
appendResult(
ref.organization,
ref.repository,
description
)
}

def cleanResults(): Unit = getResultList.fold()(_.innerHTML = "")

@JSExport
def runSearch(event: dom.Event): Future[List[Option[Node]]] = {
cleanResults()
getQuery(getSearchInput)
.fold(
Future.successful(List.empty[Autocompletion])
)(getProjects)
.map(showResults)
}

implicit class ElementOps(e: Element) {
def getInput: HTMLInputElement = get[HTMLInputElement]
def get[A <: Element]: A = e.asInstanceOf[A]
}
}

object Client extends JSApp with ClientBase {

override def main(): Unit = {
getSearchBox.foreach(_.addEventListener[Event]("input", runSearch _))
}

}

@@ -0,0 +1,35 @@
package ch.epfl.scala.index.client
package rpc

import upickle.default.{Reader, Writer, read => uread, write => uwrite}

import scala.concurrent.Future
import scalajs.concurrent.JSExecutionContext.Implicits.queue
import org.scalajs.dom


object AutowireClient extends autowire.Client[String, Reader, Writer]{

import scala.scalajs.js.URIUtils.encodeURIComponent
/** Reuse URL encoding and update implementation to reserve square brackets. */
def encode(uri: String): String =
encodeURIComponent(uri.filterNot(c => c == '[' || c == ']'))

def buildQueryGet(args: Map[String, String]): String = {
args.iterator.map{
case (key, value) =>
key + "=" + encode(value.toString.tail.init) // removes quotes around
}.mkString("&")
}

/** Change this if you add more features to the client. */
override def doCall(req: Request): Future[String] = {
dom.ext.Ajax.get(
url = "/api/" + req.path.last + "?" + buildQueryGet(req.args)
).map(_.responseText)
}

def read[T: Reader](p: String) = uread[T](p)
def write[T: Writer](r: T) = uwrite(r)
}

57 changes: 57 additions & 0 deletions project/ScalaJSHelper.scala
@@ -0,0 +1,57 @@
import sbt._
import Keys._
import org.scalajs.sbtplugin.ScalaJSPlugin.AutoImport._
import org.scalajs.sbtplugin.cross.CrossType

object Version {
val upickle = "0.4.1"
val scalatags = "0.6.0"
val autowire = "0.2.5"
}

object ScalaJSHelper {

/** Tweak JS sbt config so that sources for javascript go into `client`,
* JVM sources into `server` * and shared sources into the `shared` folder.
*/
object CustomCrossType extends CrossType {
override def projectDir(crossBase: File, projectType: String): File = {
if (projectType == "jvm") crossBase / "server"
else if (projectType == "js") crossBase / "client"
else throw new Error("Something wrong happened in the ScalaJS CrossType.")
}

override def sharedSrcDir(projectBase: File, conf: String): Option[File] =
Some(projectBase.getParentFile / "shared" / "src" / conf / "scala")
}

def packageScalaJS(client: Project) = Seq(
watchSources ++= (watchSources in client).value,

// Pick fastOpt when developing and fullOpt when publishing
resourceGenerators in Compile += Def.task {
val jsdeps = (packageJSDependencies in (client, Compile)).value
val (js, map) = andSourceMap((fastOptJS in (client, Compile)).value.data)
IO.copy(Seq(
js -> target.value / js.getName,
map -> target.value / map.getName,
jsdeps -> target.value / jsdeps.getName
)).toSeq
}.taskValue,
mappings in (Compile, packageBin) := (mappings in (Compile,packageBin))
.value.filterNot{ case (f, r) =>
f.getName.endsWith("-fastopt.js") ||
f.getName.endsWith("js.map")
} ++ {
val (js, map) = andSourceMap((fullOptJS in (client, Compile)).value.data)
Seq(
js -> js.getName,
map -> map.getName
)
}
)

private def andSourceMap(aFile: java.io.File) =
aFile -> file(aFile.getAbsolutePath + ".map")
}

3 changes: 2 additions & 1 deletion project/plugins.sbt
Expand Up @@ -4,5 +4,6 @@ addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.1.1")
addSbtPlugin("org.madoushi.sbt" % "sbt-sass" % "0.9.3")
addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.1.1")
addSbtPlugin("me.lessis" % "bintray-sbt" % "0.3.0")
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.10")

libraryDependencies += "org.scala-sbt" % "scripted-plugin" % sbtVersion.value
libraryDependencies += "org.scala-sbt" % "scripted-plugin" % sbtVersion.value
20 changes: 20 additions & 0 deletions server/src/main/scala/ch.epfl.scala.index.server/Server.scala
Expand Up @@ -20,6 +20,8 @@ import akka.actor.ActorSystem
import akka.http.scaladsl.model.headers.HttpCredentials
import akka.http.scaladsl.server.directives.Credentials.{Missing, Provided}
import akka.stream.ActorMaterializer
import ch.epfl.scala.index.data.github.GithubCredentials
import upickle.default.{write => uwrite}

import scala.concurrent.duration._
import scala.concurrent.{Await, Future}
Expand Down Expand Up @@ -250,6 +252,24 @@ object Server {
path("fonts" / Remaining) { path
getFromResource(path)
} ~
pathPrefix("api") {
path("search") {
get {
parameters('q, 'page.as[Int] ? 1, 'sort.?, 'you.?) {
(query, page, sorting, you) =>
complete {
api.find(query, page, sorting).map {
case (pagination, projects) =>
val summarisedProjects = projects.take(5).map(p =>
p.reference ->
p.github.flatMap(_.description).getOrElse(""))
uwrite(summarisedProjects)
}
}
}
}
}
} ~
path("search") {
optionalSession(refreshable, usingCookies) { userId =>
parameters('q, 'page.as[Int] ? 1, 'sort.?, 'you.?) { (query, page, sorting, you) =>
Expand Down
16 changes: 16 additions & 0 deletions shared/src/main/scala/ch/epfl/scala/index/api/Api.scala
@@ -0,0 +1,16 @@
package ch.epfl.scala.index
package api

import ch.epfl.scala.index.api.Api.Autocompletion
import ch.epfl.scala.index.model.Project.Reference

import scala.concurrent.Future

trait Api {
def search(q: String): Future[List[Autocompletion]]
}

object Api {
type Autocompletion = (Reference, String)
}

Expand Up @@ -92,7 +92,8 @@ <h5>Follow us:</h5></li>
</div>
</footer>
<!-- Coding End -->

<script src="/assets/scaladex-jsdeps.js"></script>
<script src="/assets/scaladex-fastopt.js"></script>
<script src="/assets/lib/jQuery/dist/jquery.min.js"></script>
<script src="/assets/lib/bootstrap-sass/assets/javascripts/bootstrap.min.js"></script>
<script src="/assets/lib/bootstrap-select/dist/js/bootstrap-select.min.js"></script>
Expand All @@ -104,7 +105,10 @@ <h5>Follow us:</h5></li>
$('[data-toggle="tooltip"]').tooltip();
$(".js-keywords-multiple").select2({tags: true});
$(".js-stackoverflowtags-multiple").select2({tags: true});
})
});

// Run client scalajs code (for instance, autocomplete)
ch.epfl.scala.index.client.Client().main();
</script>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
Expand Down

0 comments on commit 36c980d

Please sign in to comment.