Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #198 from scalacenter/issue/122
Fix #122: Add autocomplete bar
- Loading branch information
Showing
10 changed files
with
262 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
100 changes: 100 additions & 0 deletions
100
client/src/main/scala/ch/epfl/scala/index/client/Client.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 _)) | ||
} | ||
|
||
} | ||
|
35 changes: 35 additions & 0 deletions
35
client/src/main/scala/ch/epfl/scala/index/client/rpc/AutowireClient.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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") | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters