aboutsummaryrefslogtreecommitdiff
path: root/client/src
diff options
context:
space:
mode:
Diffstat (limited to 'client/src')
-rw-r--r--client/src/Main.scala15
-rw-r--r--client/src/model/FileRights.scala7
-rw-r--r--client/src/model/Licence.scala8
-rw-r--r--client/src/model/Pose.scala11
-rw-r--r--client/src/model/PoseCategory.scala18
-rw-r--r--client/src/model/Route.scala13
-rw-r--r--client/src/router/Router.scala105
-rw-r--r--client/src/service/Licences.scala31
-rw-r--r--client/src/service/Poses.scala58
-rw-r--r--client/src/view/View.scala22
-rw-r--r--client/src/view/index/Index.scala27
-rw-r--r--client/src/view/licence/LicencePage.scala24
-rw-r--r--client/src/view/notFound/NotFound.scala12
13 files changed, 351 insertions, 0 deletions
diff --git a/client/src/Main.scala b/client/src/Main.scala
new file mode 100644
index 0000000..e72c4a0
--- /dev/null
+++ b/client/src/Main.scala
@@ -0,0 +1,15 @@
+package yoga
+
+import mhtml.mount
+import org.scalajs.dom.document
+
+import yoga.view.View
+
+object Main {
+
+ def main(args: Array[String]): Unit = {
+ mount(document.body, View())
+ ()
+ }
+
+}
diff --git a/client/src/model/FileRights.scala b/client/src/model/FileRights.scala
new file mode 100644
index 0000000..3b91d37
--- /dev/null
+++ b/client/src/model/FileRights.scala
@@ -0,0 +1,7 @@
+package yoga.model
+
+final case class FileRights(
+ licence: Licence,
+ author: String,
+ modification: Option[String] = None
+)
diff --git a/client/src/model/Licence.scala b/client/src/model/Licence.scala
new file mode 100644
index 0000000..883d01d
--- /dev/null
+++ b/client/src/model/Licence.scala
@@ -0,0 +1,8 @@
+package yoga.model
+
+import java.net.URI
+
+final case class Licence(
+ name: String,
+ uri: URI
+)
diff --git a/client/src/model/Pose.scala b/client/src/model/Pose.scala
new file mode 100644
index 0000000..e073466
--- /dev/null
+++ b/client/src/model/Pose.scala
@@ -0,0 +1,11 @@
+package yoga.model
+
+import java.net.URI
+
+final case class Pose(
+ name: String,
+ otherNames: Seq[String] = Nil,
+ category: PoseCategory,
+ image: URI,
+ rights: FileRights
+)
diff --git a/client/src/model/PoseCategory.scala b/client/src/model/PoseCategory.scala
new file mode 100644
index 0000000..95eb974
--- /dev/null
+++ b/client/src/model/PoseCategory.scala
@@ -0,0 +1,18 @@
+package yoga.model
+
+import scala.collection.immutable.IndexedSeq
+
+import enumeratum.{ Enum, EnumEntry }
+
+sealed trait PoseCategory extends EnumEntry
+
+object PoseCategory extends Enum[PoseCategory] {
+
+ case object Standing extends PoseCategory
+ case object Balancing extends PoseCategory
+ case object Sitting extends PoseCategory
+ case object Reclining extends PoseCategory
+
+ val values: IndexedSeq[PoseCategory] = findValues
+
+}
diff --git a/client/src/model/Route.scala b/client/src/model/Route.scala
new file mode 100644
index 0000000..34cb1fc
--- /dev/null
+++ b/client/src/model/Route.scala
@@ -0,0 +1,13 @@
+package yoga.model
+
+import java.net.URI
+
+sealed trait Route
+
+case object Route {
+
+ case object Index extends Route
+ final case class Licence(uri: URI) extends Route
+ final object NotFound extends Route
+
+}
diff --git a/client/src/router/Router.scala b/client/src/router/Router.scala
new file mode 100644
index 0000000..60e5ef2
--- /dev/null
+++ b/client/src/router/Router.scala
@@ -0,0 +1,105 @@
+package yoga.router
+
+import scala.scalajs.js
+import scala.scalajs.js.URIUtils
+import scala.xml.Node
+import java.net.URI
+
+import mhtml.Var
+import org.scalajs.dom.raw.{ Element, Node => RawNode }
+import org.scalajs.dom.{ document, window, Event, MouseEvent }
+
+import yoga.model.Route
+import yoga.view.index.Index
+import yoga.view.licence.LicencePage
+import yoga.view.notFound.NotFound
+
+object Router {
+
+ private val route: Var[Route] = Var(parseRoute(Path.current()))
+
+ def render(): Node =
+ <div
+ mhtml-onmount={ (n: RawNode) =>
+ document.addEventListener("click", Handler.click)
+ window.addEventListener("popstate", Handler.popState)
+ }
+ mhtml-onumnount={ (n: RawNode) =>
+ document.removeEventListener("click", Handler.click)
+ window.removeEventListener("popstate", Handler.popState)
+ }
+ >
+ {route.map {
+ case Route.Index => Index()
+ case Route.Licence(uri) => LicencePage(uri)
+ case Route.NotFound => NotFound()
+ }}
+ </div>
+
+ object Path {
+
+ val index: String = "/"
+
+ def licence(uri: URI): String = s"/licence/${uri}"
+
+ def current(): String =
+ URIUtils.decodeURI(s"${window.location.pathname}${window.location.search}")
+
+ }
+
+ private def parseRoute(path: String): Route =
+ pathAndParams(path) match {
+ case (List(), _) => Route.Index
+ case (List("licence", uri), _) => Route.Licence(URI.create(uri))
+ case _ => Route.NotFound
+ }
+
+ private def pathAndParams(path: String): (List[String], List[String]) = {
+ println(path)
+ def splitPath(path: String) = path.split("/").drop(1).toList
+ path.split('?') match {
+ case Array(path) => (splitPath(path), Nil)
+ case Array(path, params) => (splitPath(path), params.split("&").toList)
+ }
+ }
+
+ private object Handler {
+
+ lazy val click: js.Function1[MouseEvent, Unit] =
+ routeTransitionOnAnchorClick { path =>
+ route := parseRoute(path)
+ window.history.pushState(null, "", path)
+ }
+
+ lazy val popState: js.Function1[Event, Unit] =
+ _ => route := parseRoute(Path.current())
+
+ }
+
+ private def routeTransitionOnAnchorClick(goTo: String => Unit)(event: MouseEvent): Unit =
+ if (event.button == 0)
+ Dom.findSelfOrParent("a", event.target.asInstanceOf[RawNode]) match {
+ case Some(node) =>
+ val elem = node.asInstanceOf[Element]
+ if (elem.getAttribute("data-nav") != "ignore") {
+ event.preventDefault()
+ val href = elem.getAttribute("href")
+ if (href != Path.current()) goTo(href)
+ }
+ case _ =>
+ }
+
+ private object Dom {
+
+ @annotation.tailrec
+ def findSelfOrParent(nodeName: String, current: RawNode): Option[RawNode] =
+ if (current.nodeName.toString.toLowerCase == nodeName.toLowerCase)
+ Some(current)
+ else if (current.parentNode == null)
+ None
+ else
+ findSelfOrParent(nodeName, current.parentNode)
+
+ }
+
+}
diff --git a/client/src/service/Licences.scala b/client/src/service/Licences.scala
new file mode 100644
index 0000000..77bfe5d
--- /dev/null
+++ b/client/src/service/Licences.scala
@@ -0,0 +1,31 @@
+package yoga.service
+
+import java.net.URI
+
+import yoga.model.Licence
+
+object Licences {
+
+ object CreativeCommons {
+
+ val attributionAlike_2_5: Licence =
+ Licence(
+ name = "Creative Commons Attribution 2.5 Generic",
+ uri = URI.create("https://creativecommons.org/licenses/by/2.5/deed.en")
+ )
+
+ val attributionAlike_3_0: Licence =
+ Licence(
+ name = "Creative Commons Attribution-Share Alike 3.0 Unported license",
+ uri = URI.create("https://creativecommons.org/licenses/by-sa/3.0/deed.en")
+ )
+
+ val attributionAlike_4_0: Licence =
+ Licence(
+ name = "Creative Commons Attribution 4.0 International license",
+ uri = URI.create("https://creativecommons.org/licenses/by/4.0/deed.en")
+ )
+
+ }
+
+}
diff --git a/client/src/service/Poses.scala b/client/src/service/Poses.scala
new file mode 100644
index 0000000..584c13c
--- /dev/null
+++ b/client/src/service/Poses.scala
@@ -0,0 +1,58 @@
+package yoga.service
+
+import java.net.URI
+
+import yoga.model.{ Pose, PoseCategory, FileRights }
+
+object Poses {
+
+ def values: Seq[Pose] = Seq(
+ Pose(
+ name = "Downward-Facing Dog",
+ category = PoseCategory.Standing,
+ image = URI.create("downward-facing-dog.jpg"),
+ rights = FileRights(
+ author = "Joseph RENGER",
+ licence = Licences.CreativeCommons.attributionAlike_3_0
+ )
+ ),
+ Pose(
+ name = "Downward-Facing Tree",
+ otherNames = Seq("Yoga Handstand"),
+ category = PoseCategory.Balancing,
+ image = URI.create("downward-facing-tree.jpg"),
+ rights = FileRights(
+ author = "Chiswick Chap",
+ licence = Licences.CreativeCommons.attributionAlike_4_0,
+ modification = Some("cropped 81 % horizontally, 9 % vertically using CropTool with precise mode.")
+ )
+ ),
+ // Pose(
+ // name = "Shooting bow",
+ // otherNames = Seq("Archer", "Bow and arrow"),
+ // category = PoseCategory.Sitting,
+ // image = URI.create("shooting-bow.jpg"),
+ // licence = Licences.CreativeCommons.attributionAlike_4_0
+ // ),
+ // Pose(
+ // name = "Ananta’s pose",
+ // otherNames = Seq("Vishnu’s Couch pose"),
+ // category = PoseCategory.Reclining,
+ // image = URI.create("ananta-pose.jpg"),
+ // licence = Licences.CreativeCommons.attributionAlike_4_0
+ // ),
+ // Pose(
+ // name = "Crescent Moon",
+ // category = PoseCategory.Standing,
+ // image = URI.create("crescent-moon.jpg"),
+ // licence = Licences.CreativeCommons.attributionAlike_3_0
+ // ),
+ // Pose(
+ // name = "Half moon",
+ // category = PoseCategory.Standing,
+ // image = URI.create("half-moon.jpg"),
+ // licence = Licences.CreativeCommons.attributionAlike_3_0
+ // )
+ )
+
+}
diff --git a/client/src/view/View.scala b/client/src/view/View.scala
new file mode 100644
index 0000000..5c79a71
--- /dev/null
+++ b/client/src/view/View.scala
@@ -0,0 +1,22 @@
+package yoga.view
+
+import scala.xml.Node
+
+import yoga.router.Router
+
+object View {
+
+ def apply(): Node =
+ <div>
+ <header>
+ <a href={Router.Path.index} class="header__link">
+ YOGA
+ </a>
+ </header>
+
+ <main>
+ {Router.render()}
+ </main>
+ </div>
+
+}
diff --git a/client/src/view/index/Index.scala b/client/src/view/index/Index.scala
new file mode 100644
index 0000000..29cb6ca
--- /dev/null
+++ b/client/src/view/index/Index.scala
@@ -0,0 +1,27 @@
+package yoga.view.index
+
+import scala.xml.Node
+
+import yoga.model.Pose
+import yoga.router.Router
+import yoga.service.Poses
+
+object Index {
+
+ def apply(): Node =
+ <div class="index__poses">
+ {Poses.values.map(renderPose)}
+ </div>
+
+ def renderPose(pose: Pose): Node =
+ <a class="index__poseLink" href={Router.Path.licence(pose.image)}>
+ <div class="index__poseName">
+ {pose.name}
+ </div>
+ <div class="index__poseImageParent">
+ <img class="index__poseImage" src={s"images/${pose.image}"} />
+ </div>
+ </a>
+
+
+}
diff --git a/client/src/view/licence/LicencePage.scala b/client/src/view/licence/LicencePage.scala
new file mode 100644
index 0000000..fc76522
--- /dev/null
+++ b/client/src/view/licence/LicencePage.scala
@@ -0,0 +1,24 @@
+package yoga.view.licence
+
+import java.net.URI
+import scala.xml.Node
+
+import yoga.service.Poses
+
+object LicencePage {
+
+ def apply(uri: URI): Node =
+ <div>
+ {Poses.values.find(_.image == uri).map { pose =>
+ <div>
+ <div>
+ <img src={s"images/${pose.image}"} />
+ </div>
+ <a href={pose.rights.licence.uri.toString}>
+ {pose.rights.licence.name}
+ </a>
+ </div>
+ }}
+ </div>
+
+}
diff --git a/client/src/view/notFound/NotFound.scala b/client/src/view/notFound/NotFound.scala
new file mode 100644
index 0000000..a877640
--- /dev/null
+++ b/client/src/view/notFound/NotFound.scala
@@ -0,0 +1,12 @@
+package yoga.view.notFound
+
+import scala.xml.Node
+
+object NotFound {
+
+ def apply(): Node =
+ <div>
+ Page not found
+ </div>
+
+}