All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.wbillingsley.veautiful.templates.Sequencer.scala Maven / Gradle / Ivy

package com.wbillingsley.veautiful.templates

import com.wbillingsley.veautiful._
import com.wbillingsley.veautiful.html.{<, Styling, DHtmlComponent, VHtmlDiffNode, DHtmlModifier, VHtmlElement, ^}
import com.wbillingsley.veautiful.reconcilers.Reconciler
import com.wbillingsley.veautiful.templates.Sequencer.{LayoutFunc, defaultFootBoxStyle, sequencerSlideStyle}

object Sequencer {

  type LayoutFunc = (Sequencer, DHtmlModifier, Int) => VHtmlElement

  def footBoxLayout(additionalItems:Seq[VHtmlElement]):LayoutFunc = { (sequencer, item, i) =>
    <.div(^.cls := inheritWrapper.className, item, sequencer.footBoxWithInsert(additionalItems)).build()
  }

  def defaultLayout:LayoutFunc = { (sequencer, item, i) =>
    <.div(^.cls := inheritWrapper.className, item, sequencer.footBox).build()
  }

  def bareLayout:LayoutFunc = { (sequencer, item, i) =>
    <.div(^.cls := inheritWrapper.className, item).build()
  }
  
  /** The surround, that contains the slide deck */
  val sequencerSlideStyle = Styling(
    """position: absolute;
      |top: 0;
      |left: 0;
      |width: 100%;
      |height: 100%;
      |""".stripMargin).modifiedBy(
    ".inactive" -> "visibility: hidden;"
  ).register()
  
  val inheritWrapper = Styling("height: inherit;").register()

  val sequencerGallerySlideStyle = Styling(
    """position: relative;
      |top: 0;
      |left: 0;
      |width: 100%;
      |height: 100%;
      |""".stripMargin).modifiedBy(
  ).register()
  
  
  val defaultFootBoxStyle = Styling(
    """position: absolute;
      |bottom: 10px;
      |right: 10px;
      |""".stripMargin).modifiedBy(
    " button" ->
      """border-color: #6c757d;
        |background-color: #6c757d;
        |border-radius: 0.2rem;
        |color: #fff;
        |""".stripMargin,
    " button:disabled" -> "opacity: 0.7"
  ).register()
  
}

case class SequencerConfig(
  nodes: Seq[SequenceItem], 
  index:Int = 0, 
  layout:Sequencer.LayoutFunc = Sequencer.defaultLayout,
  onIndexChange: Option[Int => Unit] = None
)

/**
  * A Sequencer renders a series of nodes into the page, styling one of them to be active.
  * If, in stead, you only want the active element to be present in the dom, just render nodes(i).
  *
  * @param key
  * @param layout
  * @param nodes
  * @param index
  */
case class Sequencer(override val key:Option[String] = None)(
  nodes: Seq[SequenceItem], index:Int = 0, layout:Sequencer.LayoutFunc = Sequencer.defaultLayout,
  onIndexChange: Option[Int => Unit] = None
) extends DHtmlComponent with Morphing(SequencerConfig(nodes, index, layout, onIndexChange)) {
  
  val morpher = createMorpher(this)

  def next():Unit = {
    if (prop.index < nodes.size - 1) {
      prop.onIndexChange match {
        case Some(f) => f(prop.index + 1)
        case None => updateProp(prop.copy(index = prop.index + 1)); rerender()
      }
    }
  }

  def previous():Unit = {
    if (prop.index > 0) {
      prop.onIndexChange match {
        case Some(f) => f(prop.index - 1)
        case None => updateProp(prop.copy(index = prop.index - 1)); rerender()
      }
    }
  }

  private def prevButton() =
    if (prop.index > 0) then
      <.button(^.onClick --> previous(), "<")
    else
      <.button(^.attr("disabled") := "disabled", "<")

  private def nextButton() =
    if (prop.index + 1 < nodes.length) then
      <.button(^.onClick --> next(), ">")
    else
      <.button(^.attr("disabled") := "disabled", ">")

  def footBoxWithInsert(additional:Seq[VHtmlElement]) = {
    <.div(^.cls := s"v-sequencer-footbox ${defaultFootBoxStyle.className} ",
      additional,
      prevButton(),
      <.span(s" ${prop.index+1} / ${nodes.size} "),
      nextButton()
    )
  }
  
  def footBox = {
    <.div(^.cls := s"v-sequencer-footbox ${defaultFootBoxStyle.className} ",
      prevButton(),
      <.span(s" ${prop.index+1} / ${nodes.size} "),
      nextButton()
    )
  }
  
  private def layoutItem(item:SequenceItem, i:Int):VHtmlElement = {
    item match {
      case csi:CustomSequenceItem => csi.layout(this, i)
      case n:VHtmlElement => layout(this, n, i)
    }
  }

  override def render = <.div(^.cls := "v-sequencer",
    <.div(^.cls := "v-sequencer-inner",
      for {
        (item, i) <- prop.nodes.zipWithIndex
      } yield <.div(^.reconciler := Reconciler.onlyIf(prop.index == i),
        ^.cls := (if (prop.index == i) s"${sequencerSlideStyle.className} v-sequencer-slide active" else s"${sequencerSlideStyle.className} v-sequencer-slide inactive"),
        layoutItem(item, i)
      )
    )
  )

  override def beforeAttach(): Unit = {
    super.beforeAttach()
    
    // Ensure the template styles are installed in the page
    templateStyleSuite.install()
  }

}

type SequenceItem = VHtmlElement // | CustomSequenceItem

trait CustomSequenceItem {
  def layout(sequencer:Sequencer, index:Int):VHtmlElement
}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy