From d5e2ec9467a7db5e7dc36bfd7c1794273f881829 Mon Sep 17 00:00:00 2001 From: greg2010 Date: Thu, 25 Feb 2021 05:24:26 -0500 Subject: [PATCH] add proper not found page, update page titles on pushState --- .../src/main/scala/org/kys/athena/App.scala | 41 ++++++++++++------- .../components/notfound/NotFoundPage.scala | 27 ++++++++++++ .../scala/org/kys/athena/routes/package.scala | 7 ++-- 3 files changed, 57 insertions(+), 18 deletions(-) create mode 100644 frontend/src/main/scala/org/kys/athena/components/notfound/NotFoundPage.scala diff --git a/frontend/src/main/scala/org/kys/athena/App.scala b/frontend/src/main/scala/org/kys/athena/App.scala index 3686286..72bd46c 100644 --- a/frontend/src/main/scala/org/kys/athena/App.scala +++ b/frontend/src/main/scala/org/kys/athena/App.scala @@ -9,28 +9,31 @@ import org.kys.athena.riot.api.dto.common.Platform import org.kys.athena.util.CSSUtil import org.kys.athena.components.LandingPage import org.kys.athena.components.common.{AppBar, Footer} +import org.kys.athena.components.notfound.NotFoundPage import org.kys.athena.components.ongoing.OngoingPage import org.kys.athena.components.pregame.PregamePage import org.scalajs.dom +import org.scalajs.dom.document import urldsl.errors.DummyError import urldsl.vocabulary.{FromString, Printer, UrlMatching} object App { - implicit val fs = new FromString[Platform, DummyError] { + private implicit val platformDecoder: FromString[Platform, DummyError] = new FromString[Platform, DummyError] { override def fromString(str: String): Either[DummyError, Platform] = { Platform.withNameEither(str).left.map(_ => DummyError.dummyError) } } - implicit val pr = new Printer[Platform] { + + private implicit val platformPrinter: Printer[Platform] = new Printer[Platform] { override def print(t: Platform): String = t.entryName } - implicit val ls = new FromString[List[String], DummyError] { + private implicit val listDecoder: FromString[List[String], DummyError] = new FromString[List[String], DummyError] { override def fromString(str: String): Either[DummyError, List[String]] = Right(str.split(',').toList) } - implicit val sl = new Printer[List[String]] { + private implicit val listPrinter: Printer[List[String]] = new Printer[List[String]] { override def print(t: List[String]) = t.mkString(",") } @@ -44,8 +47,12 @@ object App { Route[OngoingRoute, (Platform, String)]( encode = p => (p.realm, p.name), decode = a => OngoingRoute(a._1, a._2), - pattern = root / segment[Platform] / segment[String] / endOfSegments) - ) + pattern = root / segment[Platform] / segment[String] / endOfSegments), + // This must be last (catch-all to prevent the router from crashing) + Route[RouteNotFound, List[String]]( + encode = p => p.restOfSegments, + decode = a => RouteNotFound(a), + pattern = root / remainingSegments)) private val router = new Router[PageRoute]( initialUrl = dom.document.location.href, @@ -53,25 +60,29 @@ object App { routes = routes, owner = unsafeWindowOwner, // this router will live as long as the window $popStateEvent = windowEvents.onPopState, - getPageTitle = _.title, // mock page title (displayed in the browser tab next to favicon) + getPageTitle = _.title, // Currently ignored by most browsers (per MDN api spec) serializePage = page => page.asJson.noSpaces, // serialize page data for storage in History API log deserializePage = pageStr => { - decode[PageRoute](pageStr).fold(e => ErrorRoute(e.getMessage), identity) - } // deserialize the above - ) + decode[PageRoute](pageStr).fold(e => RouteNotFound(List("error")), identity) + }) + + // Currently title in pushState is ignored by most (all?) browsers. To fix, dispatch a custom event + // to update the title on route events + router.$currentPage.foreach { page => + document.title = page.title + }(unsafeWindowOwner) private val hideSearchBar = Var(false) private val splitter: SplitRender[PageRoute, HtmlElement] = - SplitRender[PageRoute, HtmlElement](router.$currentPage) - .collectStatic(LandingRoute) { - LandingPage.render(windowEvents.onMouseMove) - }.collectStatic(RouteNotFound) { - LandingPage.render(windowEvents.onMouseMove) + SplitRender[PageRoute, HtmlElement](router.$currentPage).collectStatic(LandingRoute) { + LandingPage.render }.collect[OngoingRoute] { page => OngoingPage.render(page, hideSearchBar.writer) }.collect[PregameRoute] { page => PregamePage.render(page) + }.collect[RouteNotFound] { page => + NotFoundPage.render(page.restOfSegments) } def render(): HtmlElement = { diff --git a/frontend/src/main/scala/org/kys/athena/components/notfound/NotFoundPage.scala b/frontend/src/main/scala/org/kys/athena/components/notfound/NotFoundPage.scala new file mode 100644 index 0000000..16c2a93 --- /dev/null +++ b/frontend/src/main/scala/org/kys/athena/components/notfound/NotFoundPage.scala @@ -0,0 +1,27 @@ +package org.kys.athena.components.notfound + +import com.raquo.laminar.api.L._ +import org.kys.athena.components.common.{AlephEye, Link} +import org.kys.athena.routes.LandingRoute +import org.kys.athena.util.CSSUtil.{paletteContainer, paperCls} + + +object NotFoundPage { + def render(segments: List[String]): HtmlElement = { + div( + cls := s"flex flex-col items-center justify-center p-4 lg:p-8 divide-y divide-gray-500 $paperCls", + backgroundColor := paletteContainer, + AlephEye(), + div( + cls := "flex flex-col justify-center items-center", + span( + cls := "text-xl my-2", + s"Oops! Page /${segments.mkString("/")} is not found. Make sure your URL is correct." + ), + Link( + LandingRoute, + button( + cls := "px-4 py-2 border border-gray-500 rounded-lg mt-2", + "Go home")))) + } +} diff --git a/frontend/src/main/scala/org/kys/athena/routes/package.scala b/frontend/src/main/scala/org/kys/athena/routes/package.scala index 1a58d62..71011be 100644 --- a/frontend/src/main/scala/org/kys/athena/routes/package.scala +++ b/frontend/src/main/scala/org/kys/athena/routes/package.scala @@ -11,16 +11,17 @@ package object routes { } final case object LandingRoute extends PageRoute { - override val title = "Athena" + override val title = "Athena - Landing Page" } - final case object RouteNotFound extends PageRoute { + final case class RouteNotFound(restOfSegments: List[String]) extends PageRoute { override val title: String = "Athena - Page not Found" } + final case class ErrorRoute(msg: String) extends PageRoute { override val title: String = "Athena - Error" } final case class OngoingRoute(realm: Platform, name: String) extends PageRoute { - override val title: String = s"Athena - $realm/$name" + override val title: String = s"Athena - Current game of $realm/$name" } final case class PregameRoute(realm: Platform, names: List[String]) extends PageRoute {