diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/main/scala/reading/Route.scala | 13 | ||||
| -rw-r--r-- | src/main/scala/reading/component/widget/Animate.scala | 55 | ||||
| -rw-r--r-- | src/main/scala/reading/component/widget/Modal.scala | 36 | ||||
| -rw-r--r-- | src/main/scala/reading/component/widget/Transition.scala | 12 | ||||
| -rw-r--r-- | src/main/scala/reading/component/widget/style/Modal.scala | 24 | 
5 files changed, 115 insertions, 25 deletions
| diff --git a/src/main/scala/reading/Route.scala b/src/main/scala/reading/Route.scala index c1f993e..9295d49 100644 --- a/src/main/scala/reading/Route.scala +++ b/src/main/scala/reading/Route.scala @@ -1,6 +1,7 @@  package reading -import org.scalajs.dom +import org.scalajs.dom.window +import org.scalajs.dom.raw.PopStateEvent  import scala.scalajs.js.URIUtils  import rx.Var @@ -12,10 +13,10 @@ sealed trait Route  object Route {    case class Books(filters: Seq[Filter]) extends Route -  val current: Var[Route] = Var(parse(dom.window.location.hash)) +  val current: Var[Route] = Var(parse(window.location.hash)) -  dom.window.onpopstate = (e: dom.raw.PopStateEvent) => { -    current() = parse(dom.window.location.hash) +  window.onpopstate = (e: PopStateEvent) => { +    current() = parse(window.location.hash)    }    def parse(hash: String): Route = @@ -50,7 +51,7 @@ object Route {        case Books(filters) => "/books" ++ (if (filters.nonEmpty) filters.map(filter => s"${filter.kind}=${filter.nonFormattedName}").mkString("?", "&", "") else "")        case _ => "/books"      } -    dom.window.location.origin + dom.window.location.pathname + "#" + URIUtils.encodeURI(hash) +    window.location.origin + window.location.pathname + "#" + URIUtils.encodeURI(hash)    }    def goTo(route: Route): Unit = { @@ -59,6 +60,6 @@ object Route {    }    def push(route: Route): Unit = { -    dom.window.history.pushState(null, "", url(route)); +    window.history.pushState(null, "", url(route));    }  } diff --git a/src/main/scala/reading/component/widget/Animate.scala b/src/main/scala/reading/component/widget/Animate.scala new file mode 100644 index 0000000..0e848aa --- /dev/null +++ b/src/main/scala/reading/component/widget/Animate.scala @@ -0,0 +1,55 @@ +package reading.component.widget + +import scala.collection.mutable.Map + +import org.scalajs.dom.{ window, document } +import org.scalajs.dom.raw.HTMLElement + +object Animate { +  val animationFrames: Map[String, Int] = Map.empty + +  def apply( +    id: String, +    duration: Double, +    transition: (Double, Double) => Double, +    animate: (Double, HTMLElement) => Unit, +    onEnd: => Unit = () +  ): Unit = { +    animationFrames.get(id) match { +      case Some(animationFrame) => window.cancelAnimationFrame(animationFrame) +      case None => () +    } +    val animationFrame = window.requestAnimationFrame(ts => +      frame(id, ts, duration, transition, animate, onEnd)(ts)) +    animationFrames.put(id, animationFrame) +    () +  } + +  private def frame( +    id: String, +    start: Double, +    duration: Double, +    transition: (Double, Double) => Double, +    animate: (Double, HTMLElement) => Unit, +    onEnd: => Unit +  )( +    timestamp: Double +  ): Unit = { +    document.getElementById(id).asInstanceOf[HTMLElement] match { +      case element: HTMLElement => +        val elapsed = timestamp - start +        if (elapsed < duration) { +          animate(Transition.easeOut(elapsed, duration), element) +          val animationFrame = window.requestAnimationFrame(frame(id, start, duration, transition, animate, onEnd)) +          animationFrames.put(id, animationFrame) +        } else { +          animate(1, element) +          onEnd +        } +      case _ => +        val animationFrame = window.requestAnimationFrame(ts => frame(id, ts, duration, transition, animate, onEnd)(ts)) +        animationFrames.put(id, animationFrame) +    } +    () +  } +} diff --git a/src/main/scala/reading/component/widget/Modal.scala b/src/main/scala/reading/component/widget/Modal.scala index fe10d1f..02c42be 100644 --- a/src/main/scala/reading/component/widget/Modal.scala +++ b/src/main/scala/reading/component/widget/Modal.scala @@ -1,24 +1,41 @@  package reading.component.widget -import rx._ -import Ctx.Owner.Unsafe._ +import scala.util.Random -import scalatags.JsDom.all._ +import org.scalajs.dom.raw.HTMLElement +import rx._ +import rx.Ctx.Owner.Unsafe._  import scalacss.Defaults._  import scalacss.ScalatagsCss._ +import scalatags.JsDom.all._  import reading.component.widget.style.{ Modal => ModalStyle }  import reading.utils.{ RxAttr }  object Modal {    def apply(onClose: => Unit)(content: HtmlTag): HtmlTag = { +    val modalId = s"modal${Random.nextInt}" + +    Animate( +      id = modalId, +      duration = 300, +      transition = Transition.easeOut, +      animate = (progress, element) => { +      element.style.opacity = s"$progress" +      element.childNodes(2) match { +        case e: HTMLElement => e.style.transform = s"translateY(${40 * (progress - 1)}px)" +      } +    } +    ) +      div(        ModalStyle.render,        ModalStyle.modal, +      id := modalId,        div(          ModalStyle.curtain, -        RxAttr(onclick, Rx(() => onClose)) +        RxAttr(onclick, Rx(() => close(modalId, onClose)))        ),        div( @@ -26,10 +43,19 @@ object Modal {          content,          button(            ModalStyle.close, -          RxAttr(onclick, Rx(() => onClose)), +          RxAttr(onclick, Rx(() => close(modalId, onClose))),            "Fermer"          )        )      )    } + +  private def close(modalId: String, onClose: => Unit): Unit = +    Animate( +      id = modalId, +      duration = 300, +      transition = Transition.linear, +      onEnd = onClose, +      animate = (progress, element) => element.style.opacity = s"${1 - progress}" +    )  } diff --git a/src/main/scala/reading/component/widget/Transition.scala b/src/main/scala/reading/component/widget/Transition.scala new file mode 100644 index 0000000..aa8ff3d --- /dev/null +++ b/src/main/scala/reading/component/widget/Transition.scala @@ -0,0 +1,12 @@ +package reading.component.widget + +object Transition { +  def linear(progress: Double, total: Double): Double = +    progress / total + +  def easeIn(progress: Double, total: Double): Double = +    math.pow(progress, 2) / math.pow(total, 2) + +  def easeOut(progress: Double, total: Double): Double = +    (-1) * (progress / total) * (progress / total - 2) +} diff --git a/src/main/scala/reading/component/widget/style/Modal.scala b/src/main/scala/reading/component/widget/style/Modal.scala index bfcc276..ae37c4b 100644 --- a/src/main/scala/reading/component/widget/style/Modal.scala +++ b/src/main/scala/reading/component/widget/style/Modal.scala @@ -19,23 +19,19 @@ object Modal extends StyleSheet.Inline {      right(0.px),      bottom(0.px),      left(0.px), -    overflowY.scroll +    overflowY.scroll, +    opacity(0)    )    val curtain = style( -    Media.desktop( -      width(100.%%), -      height(100.%%), -      position.absolute, -      top(0.px), -      left(0.px), -      backgroundColor(C.black.value), -      opacity(0.5), -      cursor.pointer -    ), -    Media.mobile( -      display.none -    ) +    width(100.%%), +    height(100.%%), +    position.absolute, +    top(0.px), +    left(0.px), +    backgroundColor(C.black.value), +    opacity(0.7), +    cursor.pointer    )    val content = style( | 
