Skip to content

Commit

Permalink
Upgrade to Bootstrap 5 (#55)
Browse files Browse the repository at this point in the history
  • Loading branch information
v6ak committed Oct 12, 2023
1 parent d4359fe commit c4d7fc2
Show file tree
Hide file tree
Showing 14 changed files with 300 additions and 173 deletions.
8 changes: 3 additions & 5 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ val jqueryName: String = "jquery/2.1.4/jquery.js"

val jqPlot = "org.webjars" % "jqplot" % "1.0.8r1250"

val bootstrap = "org.webjars" % "bootstrap" % "3.3.7-1"
val bootstrap = "org.webjars" % "bootstrap" % "5.3.2"

import com.typesafe.sbt.web.PathMapping
import com.typesafe.sbt.web.pipeline.Pipeline
Expand Down Expand Up @@ -67,7 +67,7 @@ lazy val server = (project in file("server")).settings(
digest / excludeFilter := "*.html" || "*.csv" || "*.json" || "*.json.new" ||
// When sbt-simple-url-update updates path for glyphicons-halflings-regular.woff, it garbles the path for glyphicons-halflings-regular.woff2.
"glyphicons-halflings-regular.woff",
filter / excludeFilter := "*.less" || "*.note" || "*.source" || "*.css" - "main.min.css" || "*.js" - "main.min.js",
filter / excludeFilter := "*.scss" || "*.note" || "*.source" || "*.css" - "main.css" || "*.js" - "main.min.js",
filter / includeFilter := "*.css" || "*.html" || "*.js" || "*.csv" || "*.svg" || "*.woff" || "*.ttf" || "*.eot" || "*.woff2" || "*.json.new",
genHtmlDir := target.value / "web" / "html" / "main",
Assets / resourceDirectories += genHtmlDir.value,
Expand Down Expand Up @@ -107,8 +107,6 @@ lazy val server = (project in file("server")).settings(
Concat.groups := Seq(
"main.min.js" -> group(Seq("zbdb-stats-client-jsdeps.min.js", "zbdb-stats-client-opt/main.js"))
),
LessKeys.cleancss := true,
LessKeys.compress := true,
libraryDependencies ++= Seq(
"com.vmunier" %% "scalajs-scripts" % "1.2.0",
guice,
Expand Down Expand Up @@ -140,7 +138,7 @@ lazy val client = (project in file("client")).settings(
Compile / fullLinkJS / jsMappings += toPathMapping((Compile / packageMinifiedJSDependencies).value),

jsDependencies ++= Seq(
bootstrap / "bootstrap.min.js",
bootstrap / "bootstrap.bundle.min.js",
"org.webjars" % "momentjs" % "2.10.6" / "min/moment.min.js",
"org.webjars" % "moment-timezone" % "0.4.0-1" / "moment-timezone-with-data.js" dependsOn "min/moment.min.js",
"org.webjars" % "jquery" % "2.1.4" / jqueryName minified "jquery/2.1.4/jquery.min.js",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import scalatags.JsDom.all.Modifier
object ScalaTagsBootstrapDomModifiers {

def onBsShown(handler: js.Function1[js.Dynamic, Unit]): Modifier = new Modifier{
override def applyTo(el: Element): Unit = dom.window.asInstanceOf[js.Dynamic].`$`(el).on("show.bs.dropdown", handler)
override def applyTo(el: Element): Unit = el.addEventListener("show.bs.dropdown", handler)
}

}
26 changes: 22 additions & 4 deletions client/src/main/scala/com/v6ak/zbdb/Bootstrap.scala
Original file line number Diff line number Diff line change
@@ -1,11 +1,29 @@
package com.v6ak.zbdb

import org.scalajs.dom._
import scala.scalajs.js
import scala.scalajs.js.annotation._
import scalatags.JsDom.all._

@js.native
@JSGlobal("bootstrap.Modal")
class BsModal (el: Element) extends js.Any {
def show(): Unit = js.native
}


object Bootstrap {
val toggle = data.toggle
val dismiss = data.dismiss
def glyphicon(name: String) = span(`class`:=s"glyphicon glyphicon-${name}", aria.hidden := "true")
val toggle = attr("data-bs-toggle")
val dismiss = attr("data-bs-dismiss")
def btn = button(`class` := "btn")
def btnDefault = btn(`class` := "btn-default")
def btnPrimary = btn(`class` := "btn-primary")
def btnDefault = btn(`class` := "btn-secondary")
def btnLight = btn(`class` := "btn-light")
implicit final class DialogUtils(val el: Element) extends AnyVal {
@inline def onBsModalShown(f: js.Function) =
el.addEventListener("shown.bs.modal", f.asInstanceOf[js.Function1[_, Any]])

@inline def onBsModalHidden(f: js.Function) =
el.addEventListener("hidden.bs.modal", f.asInstanceOf[js.Function1[_, Any]])
}
}
18 changes: 18 additions & 0 deletions client/src/main/scala/com/v6ak/zbdb/Glyphs.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.v6ak.zbdb

import scalatags.JsDom.all._

final case class Glyph(frag: Frag) extends AnyVal {
@inline def toHtml: Frag = frag

}

object Glyphs {
private def css(name: String) = Glyph(span(`class`:=s"icon-$name"))
val Pedestrian = Glyph("\uD83D\uDEB6")
val Globe = Glyph("\uD83C\uDF10")
val Play = css("play")
val Pause = css("pause")
val Timeline = css("timeline")

}
26 changes: 18 additions & 8 deletions client/src/main/scala/com/v6ak/zbdb/HtmlUtils.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.v6ak.zbdb

import org.scalajs.dom
import org.scalajs.dom._

import scala.scalajs.js
import scalatags.JsDom.all._
Expand All @@ -11,24 +12,33 @@ object HtmlUtils {
val EmptyHtml: Frag = ""

def dropdownGroup(mods: Modifier*)(buttons: Frag*) = div(cls:="btn-group")(
button(cls:="btn btn-normal dropdown-toggle", toggle := "dropdown", aria.haspopup := "true", aria.expanded := "false")(mods: _*),
btnLight(cls:="dropdown-toggle", toggle := "dropdown", aria.haspopup := "true", aria.expanded := "false")(mods: _*),
ul(cls:="dropdown-menu")(buttons.map(li(_)) : _*)
)

def modal(title: Frag) = {
def modal(title: Frag, keyboard: Boolean = true) = {
val modalBodyId = IdGenerator.newId()
val modalHeader = div(`class`:="modal-header")(button(`type`:="button", `class`:="close", dismiss := "modal")(span(aria.hidden := "true")("×")))(h4(`class`:="modal-title")(title))
val modalHeader = div(`class`:="modal-header")(
h5(`class`:="modal-title")(title),
button(`type`:="button", `class`:="btn-close", dismiss := "modal", aria.label := "Zavřít"),
)
val modalBody = div(`class`:="modal-body", id := modalBodyId)
val modalFooter = div(`class`:="modal-footer")
val modalDialog = div(`class`:="modal-dialog modal-xxl")(div(`class`:="modal-content")(modalHeader, modalBody, modalFooter))
val dialog = div(`class`:="modal fade")(modalDialog).render
val jqModal = dom.window.asInstanceOf[js.Dynamic].$(dialog)
jqModal.on("hidden.bs.modal", {() => dialog.parentNode.removeChild(dialog)})
(dialog, jqModal, modalBodyId)
val modalDialog = div(`class`:="modal-dialog modal-xxl")(
div(`class`:="modal-content")(
modalHeader, modalBody, modalFooter
)
)
val dialog = div(`class`:="modal fade", attr("data-bs-keyboard") := keyboard.toString)(modalDialog).render
dialog.onBsModalHidden({() => dialog.parentNode.removeChild(dialog)})
val bsMod = new BsModal(dialog)
(dialog, modalBodyId, bsMod)
}

@inline def fseq(frags: Frag*): Seq[Frag] = frags

def spaceSeparated(values: Frag*): Frag = values.map(fseq(_, " "))

def onChange(e: js.Function1[Event, Unit]) = onchange := e

}
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
package com.v6ak.zbdb

final case class ParticipantPlotGenerator(nameGenitive: String, nameAccusative: String, glyphiconName: String, generator: Seq[Participant] => PlotData)
final case class ParticipantPlotGenerator(
nameGenitive: String,
nameAccusative: String,
glyph: Glyph,
generator: Seq[Participant] => PlotData
)
6 changes: 3 additions & 3 deletions client/src/main/scala/com/v6ak/zbdb/PlotRenderer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ final class PlotRenderer(participantTable: ParticipantTable) {
import participantTable._

val Plots = Seq(
ParticipantPlotGenerator("chůze", "chůzi", "globe", generateWalkPlotData),
ParticipantPlotGenerator("rychlosti", "rychlost", "play", generateSpeedPlotData),
ParticipantPlotGenerator("pauz", "pauzy", "pause", generatePausesPlotData)
ParticipantPlotGenerator("chůze", "chůzi", Glyphs.Pedestrian, generateWalkPlotData),
ParticipantPlotGenerator("rychlosti", "rychlost", Glyphs.Play, generateSpeedPlotData),
ParticipantPlotGenerator("pauz", "pauzy", Glyphs.Pause, generatePausesPlotData)
)

val GlobalPlots = Seq(
Expand Down
59 changes: 33 additions & 26 deletions client/src/main/scala/com/v6ak/zbdb/Renderer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,12 @@ import com.v6ak.scalajs.scalatags.ScalaTagsDomModifiers._
import com.v6ak.scalajs.scalatags.ScalaTagsBootstrapDomModifiers._

object Renderer{
private val FirstBadge = div(cls := "label label-success first-badge")("1.")
def renderPositionBadge(position: Option[Int]) =
position.fold(span(cls := "badge bg-danger badge-result")("DNF"))(order =>
span(cls := "badge bg-success badge-result")(s"$order.")
)

private val FirstBadge = div(cls := "badge bg-success first-badge")("1.")

def initialize(participantTable: ParticipantTable, processingErrors: Seq[(Seq[String], Throwable)], content: Node, plots: Seq[(String, String)], enableHorizontalStickyness: Boolean, year: String, yearLinksOption: Option[Seq[(String, String)]]) = {
val r = new Renderer(participantTable, processingErrors, content, plots, enableHorizontalStickyness, year, yearLinksOption)
Expand Down Expand Up @@ -86,13 +91,13 @@ final class Renderer private(participantTable: ParticipantTable, processingError
private def yearSelection = dropdownGroup(Seq[Frag](year, " ", span(cls:="caret")))(
yearLinksOption match{
case Some(yearLinks) => yearLinks.reverse.map{case (y, yearLink) => a(href:=yearLink)(y)}
case None => span(cls:="label label-danger")("Ročníky nejsou k dispozici.")
case None => span(cls:="badge bg-danger")("Ročníky nejsou k dispozici.")
}
)

private val renderer = new TableRenderer[Participant](
headRows = 2,
tableModifiers = Seq(`class` := "table table-condensed table-hover participants-table"),
tableModifiers = Seq(`class` := "table table-sm table-hover participants-table"),
trWrapper = {(tableRow, row) => tableRow(id := row.trId)}
)(Seq[Column[Participant]](
Column(TableHeadCell(yearSelection), TableHeadCell("id, jméno"))(renderParticipantColumn)(className = "participant-header"),
Expand Down Expand Up @@ -136,7 +141,7 @@ final class Renderer private(participantTable: ParticipantTable, processingError
dom.console.warn(s"It seems that nobody has reached part #$i")
BestParticipantData.Empty
}
def moreButton(c: String) = button(cls := s"btn btn-default btn-xs dropdown-toggle $c", `type` := "button", toggle := "dropdown")(span(cls:="caret"))//("⠇")
def moreButton(c: String) = btnDefault(cls := s"btn-sm dropdown-toggle $c", toggle:="dropdown")(span(cls:="caret"))
val firstCell = if (i == 0) TableHeadCell("Start") else TableHeadCell.Empty
Seq[Column[Participant]](
Column.rich(firstCell, TableHeadCell("|=>"))((r: Participant) =>
Expand Down Expand Up @@ -211,8 +216,8 @@ final class Renderer private(participantTable: ParticipantTable, processingError

def renderParticipantColumn(r: Participant): Frag = Seq(
label(`for` := r.checkboxId, cls := "participant-header-label")(
r.orderOption.fold(span(cls := "label label-danger label-result")("DNF"))(order => span(cls := "label label-success label-result")(s"$order.")),
input(`type` := "checkbox", `class` := "participant-checkbox hidden-print", id := r.checkboxId, onchange := { (e: Event) =>
renderPositionBadge(r.orderOption),
input(`type`:="checkbox", `class`:="participant-checkbox d-print-none", id:=r.checkboxId, onChange { e =>
val el = e.currentTarget.asInstanceOf[HTMLInputElement]
el.checked match {
case true => selection.addRow(r)
Expand All @@ -222,15 +227,15 @@ final class Renderer private(participantTable: ParticipantTable, processingError
" ",
s"${r.id}: ${r.fullNameWithNick}",
),
div(`class` := "actions hidden-print")(chartButtons(r))
div(`class` := "actions d-print-none")(chartButtons(r))
)

private val tableElement = renderer.renderTable(data)

private val selectedParticipantsElement = span(`class` := "selected-participants")("").render

private val barElement = div(id := "button-bar")(
button("×", `class` := "close")(onclick := {(e: Event) => selection.clear() }),
button(`class` := "btn-close")(onclick := {(e: Event) => selection.clear() }),
dropdownGroup("Porovnat vybrané účastníky…", cls:="btn btn-primary dropdown-toggle")(
for(plot <- Plots) yield chartButton(s"Porovnat ${plot.nameAccusative}", selection().toSeq, plot.generator, s"Porovnat ${plot.nameAccusative}")
)(cls := "btn-group dropup"),
Expand All @@ -239,15 +244,15 @@ final class Renderer private(participantTable: ParticipantTable, processingError

private val globalStats = div(id := "global-stats")(
GlobalPlots.map{case (plotName, plotFunction) =>
button(plotName)(cls := "btn btn-default hidden-print")(onclick := {(e: Event) =>
val (dialog, jqModal, modalBodyId) = modal(plotName)
jqModal.on("shown.bs.modal", {() => plotFunction(modalBodyId, data, participantTable)})
button(plotName)(cls := "btn btn-secondary d-print-none")(onclick := {(e: Event) =>
val (dialog, modalBodyId, bsMod) = modal(plotName)
dialog.onBsModalShown{ () => plotFunction(modalBodyId, data, participantTable) }
dom.document.body.appendChild(dialog)
jqModal.modal(js.Dictionary("keyboard" -> true))
bsMod.show()
})
},
additionalPlots.map{case (name, url) =>
a(href:=url, cls:="btn btn-default", target := "_blank")(name)
a(href:=url, cls:="btn btn-secondary", target := "_blank")(name)
}
).render

Expand All @@ -257,27 +262,29 @@ final class Renderer private(participantTable: ParticipantTable, processingError
barElement.style.visibility = if(shown) "" else "hidden"
}

private def chartButton(title: String, rowsLoader: => Seq[Participant], plotDataGenerator: Seq[Participant] => PlotData, description: Frag) = button(`class` := "btn btn-default btn-xs")(description)(onclick := {(_:Any) =>
val (dialog, jqModal, modalBodyId) = modal(title)
jqModal.on("shown.bs.modal", {() => initializePlot(modalBodyId, plotDataGenerator(rowsLoader))})
dom.document.body.appendChild(dialog)
jqModal.modal(js.Dictionary("keyboard" -> true))
})
private def chartButton(title: String, rowsLoader: => Seq[Participant], plotDataGenerator: Seq[Participant] => PlotData, description: Frag) =
btnDefault(`class` := "btn-sm")(description)(onclick := {(_:Any) =>
val (dialog, modalBodyId, bsMod) = modal(title)
dialog.onBsModalShown({() => initializePlot(modalBodyId, plotDataGenerator(rowsLoader))})
dom.document.body.appendChild(dialog)
bsMod.show()
})

private def chartButtons(row: Participant) = span(cls := "detailed-only")(
timelineButton(row)(`class` := "btn-xs")
timelineButton(row)(`class` := "btn-sm")
)

private def timelineButton(row: Participant) =
button(
`class` := "btn btn-default",
Seq(span(`class` := s"glyphicon glyphicon-list", aria.hidden := "true")),
btnPrimary(
Glyphs.Timeline.toHtml,
onclick := { (_: Any) =>
val (dialog, jqModal, modalBodyId) = modal(s"Časová osa pro #${row.id}: ${row.fullNameWithNick}")
val (dialog, modalBodyId, bsMod) = modal(s"Časová osa pro #${row.id}: ${row.fullNameWithNick}")
dom.document.body.appendChild(dialog)
dom.document.getElementById(modalBodyId).appendChild(timeLineRenderer.timeLine(row).render)
val (timeLine, renderPlots) = timeLineRenderer.timeLine(row)
dom.document.getElementById(modalBodyId).appendChild(timeLine)
timeLineRenderer.bodyClasses.foreach(dom.document.body.classList.add)
jqModal.modal(js.Dictionary("keyboard" -> true))
bsMod.show()
dialog.onBsModalShown(renderPlots)
}
)

Expand Down

0 comments on commit c4d7fc2

Please sign in to comment.