Skip to content

Commit

Permalink
Enhance expanding/collapsing the table
Browse files Browse the repository at this point in the history
  • Loading branch information
v6ak committed Oct 23, 2023
1 parent e49172c commit 0d27d1d
Show file tree
Hide file tree
Showing 6 changed files with 158 additions and 54 deletions.
50 changes: 29 additions & 21 deletions client/src/main/scala/com/v6ak/scalajs/tables/Column.scala
@@ -1,45 +1,46 @@
package com.v6ak.scalajs.tables

import com.v6ak.zbdb.HtmlUtils.EmptyHtml
import org.scalajs.dom.Node
import scalatags.JsDom.all.*

import scalatags.JsDom.all
import scalatags.JsDom.all._


final case class TableHeadColumn(content: Node, colCount: Int = 1, rowCountOption: Option[Int] = None, className: String) {
final case class TableHeadColumn(
content: Node,
colCount: Int = 1,
rowCountOption: Option[Int] = None,
className: String,
):
def create(headRows: Int) = th(
`class` := className,
colspan := colCount,
rowspan := rowCountOption.getOrElse(headRows),
content
)

}

abstract sealed class TableHeadCell{
abstract sealed class TableHeadCell:
def rowCount: Int
def build(className: String): Option[TableHeadColumn]
}

object TableHeadCell{
object TableHeadCell:
def apply(frag: Frag, colCount: Int = 1, rowCount: Int = 1, additionalClass: String = "") = Full(
content = frag.render, colCount = colCount, rowCount = rowCount, additionalClass = additionalClass
)
final case class Full(content: Node, colCount: Int, rowCount: Int, additionalClass: String) extends TableHeadCell{
final case class Full(content: Node, colCount: Int, rowCount: Int, additionalClass: String) extends TableHeadCell:
def build(className: String) = Some(TableHeadColumn(
content = content,
colCount = colCount,
rowCountOption = Some(rowCount),
className = className + " " + additionalClass
))
}
case object Empty extends TableHeadCell{

case object Empty extends TableHeadCell:
override def rowCount: Int = 0
override def build(className: String): Option[TableHeadColumn] = None
}
}

object Column{

object Column:

def apply[T](header: Frag)(cellRenderer: T => Frag) = new Column[T] {
override def rowCount: Int = 1
Expand All @@ -48,27 +49,34 @@ object Column{
case _ => None
}
private def renderContent(data: T): Node = cellRenderer(data).render
override def createContentCell(row: T): Frag = td(renderContent(row))
override def createContentCell(row: T, pos: Int, size: Int): Frag = td(renderContent(row))
}

def apply[T](headers: TableHeadCell*)(cellRenderer: T => Frag)(className: String = "") = new Column[T] {
override def rowCount: Int = headers.map(_.rowCount).sum
override def renderHeader(i: Int): Option[TableHeadColumn] = headers.lift(i).flatMap(_.build(className = className))
private def renderContent(data: T): Node = cellRenderer(data).render
override def createContentCell(row: T): Frag = td(`class` := className, renderContent(row))
override def createContentCell(row: T, pos: Int, size: Int): Frag = td(`class` := className, renderContent(row))
}

def singleCell[T](headers: TableHeadCell*)(cell: Frag)(className: String = "") = new Column[T] {
override def rowCount: Int = headers.map(_.rowCount).sum
override def renderHeader(i: Int): Option[TableHeadColumn] = headers.lift(i).flatMap(_.build(className = className))
private def renderContent(data: T): Node = cell.render
override def createContentCell(row: T, pos: Int, size: Int): Frag =
if(pos == 0) td(`class` := className, rowspan := size, renderContent(row))
else EmptyHtml
}

def rich[T](headers: TableHeadCell*)(cellRenderer: T => Seq[Modifier])(className: String = "") = new Column[T] {
override def rowCount: Int = headers.map(_.rowCount).sum
override def renderHeader(i: Int): Option[TableHeadColumn] = headers.lift(i).flatMap(_.build(className = className))
private def renderContent(data: T): Modifier = cellRenderer(data)
override def createContentCell(row: T): Frag = td(`class` := className, renderContent(row))
override def createContentCell(row: T, pos: Int, size: Int): Frag = td(`class` := className, renderContent(row))
}

}

abstract class Column[T]{
abstract class Column[T]:
def rowCount: Int
def renderHeader(i: Int): Option[TableHeadColumn]
def createContentCell(row: T): Frag
}
def createContentCell(row: T, pos: Int, size: Int): Frag
35 changes: 22 additions & 13 deletions client/src/main/scala/com/v6ak/scalajs/tables/TableRenderer.scala
@@ -1,17 +1,18 @@
package com.v6ak.scalajs.tables

import org.scalajs.dom.html.TableRow

import scalatags.JsDom.TypedTag
import scalatags.JsDom.all._
import scalatags.JsDom.all.*

final class TableRenderer[T](headRows: Int = 1, tableModifiers: Seq[Modifier] = Seq(), trWrapper: (TypedTag[TableRow], T) => TypedTag[TableRow] = {(a: TypedTag[TableRow], _: T) => a})(columns: Seq[Column[T]]){
final class TableRenderer[T](
headRows: Int = 1,
tableModifiers: Seq[Modifier] = Seq(),
trWrapper: (TypedTag[TableRow], T) => TypedTag[TableRow] = {(a: TypedTag[TableRow], _: T) => a},
)(columns: Seq[Column[T]]):

columns foreach { c =>
if(c.rowCount > headRows){ // TODO: consider !=; however, I am not sure about multiple-row cells
columns foreach: c =>
if(c.rowCount > headRows) // TODO: consider !=; however, I am not sure about multiple-row cells
sys.error(s"bad rowCount ${c.rowCount} for $c")
}
}

def renderTableHead = thead(0 until headRows map { headRowIndex =>
tr(columns.map{col =>
Expand All @@ -21,13 +22,21 @@ final class TableRenderer[T](headRows: Int = 1, tableModifiers: Seq[Modifier] =
})
})

def renderTableBody(data: Seq[T]) = tbody(data.map(row => trWrapper(tr(columns.map(col => col.createContentCell(row))), row)))
def renderTableBody(data: Seq[T]) = tbody(
data.zipWithIndex.map( (row, i) =>
trWrapper(
tr(
columns.map(col =>
col.createContentCell(row, i, data.size)
)
),
row
)
)
)

def renderTable(data: Seq[T]) = table(
renderTableHead,
renderTableBody(data)
)(
tableModifiers : _*
renderTableBody(data),
tableModifiers,
).render

}
6 changes: 5 additions & 1 deletion client/src/main/scala/com/v6ak/zbdb/ClassSwitches.scala
Expand Up @@ -5,7 +5,10 @@ import org.scalajs.dom.*
import scalatags.JsDom.all.{button as buttonTag, *}


final class ClassSwitches(initialSwitchesState: Map[String, String]) {
final class ClassSwitches(
initialSwitchesState: Map[String, String],
alreadySwitchedClasses: Map[String, String] = Map(),
) {
private val switchesState = collection.mutable.Map(initialSwitchesState.toSeq: _*)

def values = switchesState.values
Expand All @@ -16,6 +19,7 @@ final class ClassSwitches(initialSwitchesState: Map[String, String]) {
classList.add(newClass)
oldClasses.foreach(classList.remove)
switchesState(switchName) = newClass
alreadySwitchedClasses.get(switchName).foreach(classList.add)
}

def classSelect(switchName: String)(items: (String, String)*) = select(
Expand Down
22 changes: 14 additions & 8 deletions client/src/main/scala/com/v6ak/zbdb/Renderer.scala
Expand Up @@ -52,7 +52,10 @@ final class Renderer private(

private val plotRenderer = new PlotRenderer(participantTable)
private val timeLineRenderer = new TimeLineRenderer(participantTable, plotRenderer)
private val switches = new ClassSwitches(Map("details" -> "without-details"))
private val switches = new ClassSwitches(
Map("details" -> "without-details"),
Map("details" -> "details-switched"),
)

import ChartJsUtils._
import Renderer._
Expand Down Expand Up @@ -117,29 +120,32 @@ final class Renderer private(
val expandButton = switches.button("details", "with-details", detailsValues)(expandCollapseStyle)("")
val collapseButton = switches.button("details", "without-details", detailsValues)(
expandCollapseStyle, `class`:="btn btn-secondary"
)("«")
)("Zobrazit stručně")

private val renderer = new TableRenderer[Participant](
headRows = 2,
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"),
Column(
TableHeadCell(fseq(
collapseButton(`class` := "detailed-only"),
yearSelection,
)),
TableHeadCell("id, jméno")
)(renderParticipantColumn)(className = "participant-header"),
Column[Participant](TableHeadCell(""), TableHeadCell("Kat."))(p => Seq[Frag](span(cls:="gender")(Genders(p.gender)), " ", span(cls:="age")(p.age)))(className = "category")
) ++ Seq[Option[Column[Participant]]](
if(formatVersion.ageType == AgeType.BirthYear) Some(Column[Participant]("Roč.")(p => Seq[Frag](p.birthYear.get))) else None
).flatten ++ Seq(
Column[Participant](TableHeadCell(expandButton, rowCount = 2))(_ => expandButton)(
Column.singleCell[Participant](TableHeadCell(fseq(expandButton), rowCount = 2))(expandButton)(
className = "without-details-only expand-columns"
),
Column[Participant](TableHeadCell(collapseButton, rowCount = 2))(_ => collapseButton)(
className = "detailed-only"
),
) ++ parts.zipWithIndex.flatMap{case (part, i) =>
createTrackPartColumns(part, i)
} ++ Seq[Column[Participant]](
Column(
TableHeadCell("", additionalClass = "without-details-only"),
TableHeadCell("", additionalClass = "without-details-only dont-highlight"),
TableHeadCell(span(title := "Celkový čas")("Celk."))
){(p: Participant) =>
if(p.hasFinished) p.totalTime.toString else ""
Expand Down
45 changes: 34 additions & 11 deletions server/app/assets/main.scss
Expand Up @@ -50,17 +50,29 @@
$color1: lightgray;
$color2: transparent;
background-color: transparent;
background-image: linear-gradient(
90deg,
$color1 8.33%,
$color2 8.33%,
$color2 50%,
$color1 50%,
$color1 58.33%,
$color2 58.33%,
$color2 100%
);
background-size: 12px 12px;
background-image:
url("other-columns.svg?inline"),
linear-gradient(
90deg,
$color1 8.33%,
$color2 8.33%,
$color2 50%,
$color1 50%,
$color1 58.33%,
$color2 58.33%,
$color2 100%,
),
;
background-size:
40% auto,
12px 12px,
;
background-repeat:
repeat-y,
repeat;
background-position:
center 10px,
left;
position: absolute;
width: 100%;
top: -1px; // over border
Expand Down Expand Up @@ -321,6 +333,12 @@ body{
margin-bottom: 20px;
}

@keyframes highlight-background {
from {
background-color: yellow;
color: black;
}
}

body.with-details {
.without-details-only {
Expand All @@ -337,6 +355,11 @@ body.without-details {
display: none;
}
}
body.details-switched {
.detailed-only:not(.dont-highlight), .without-details-only:not(.dont-highlight) {
animation: 1s linear highlight-background;
}
}

.with-relative-time .clock-time,
.with-clock-time .relative-time,
Expand Down
54 changes: 54 additions & 0 deletions server/app/assets/other-columns.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 0d27d1d

Please sign in to comment.