Skip to content

Commit

Permalink
Timeline: add relative time and prepare pace/speed for switching
Browse files Browse the repository at this point in the history
See #39
  • Loading branch information
v6ak committed Sep 25, 2023
1 parent 61f49b0 commit 037f3da
Show file tree
Hide file tree
Showing 2 changed files with 138 additions and 119 deletions.
2 changes: 2 additions & 0 deletions client/src/main/scala/com/v6ak/zbdb/TextUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@ object TextUtils {

def formatLength(length: BigDecimal, space: String = " ") = length.toString().replace('.', ',') + space + "km"

def formatSpeed(speedInKmPerH: BigDecimal) = f"$speedInKmPerH%1.2f km/h".replace('.', ',')

}
255 changes: 136 additions & 119 deletions client/src/main/scala/com/v6ak/zbdb/TimeLineRenderer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,140 +22,158 @@ final class TimeLineRenderer(participantTable: ParticipantTable, plotRenderer: P
import participantTable._
import plotRenderer.{Plots, initializePlot}

private def timePoint(time: Moment, content: Frag, className: String = "") = tr(
`class` := s"timeline-point $className",
td(`class` := "timeline-time", time.hoursAndMinutes),
td(),
td(`class` := "timeline-content", content),
)
val brief = true

private def process(content: Frag, duration: TimeInterval, durationIcon: String, className: String = "") = tr(
`class` := s"timeline-process $className",
td(`class` := "timeline-time"),
td(`class` := "timeline-duration")(
fseq(
glyphicon(durationIcon),
" ",
duration.toString
)
),
td(`class` := "timeline-content", content),
)
def verbose(f: Frag) = if (brief) "" else f

def verbose(s: String) = if (brief) "" else s

private def end(content: Frag, className: String = "") = tr(
`class` := s"timeline-end $className",
td(`class` := "timeline-time"),
td(`class` := "timeline-duration"),
td(`class` := "timeline-content", content),
)

private def pause(content: Frag, duration: TimeInterval) =
process(content, duration = duration, durationIcon = "pause", className = "pause")
private def walk(content: Frag, duration: TimeInterval) =
process(content, duration = duration, durationIcon = "play", className = "walk")
private def gaveUp(content: Frag) = end(content, className = "gave-up")
private def finish(time: Moment, content: Frag) = timePoint(time, content, className = "finish")

def timeLine(row: Participant) = {
val prevParts = Seq(None) ++ parts.map(Some(_))
val nextPartInfos: Seq[Option[PartTimeInfo]] = row.partTimes.drop(1).map(Some(_)) ++ Seq(None)
div(
div(`class` := "legend")(
h2("Legenda"),
legendTable,
final private class ForPlayer(row: Participant) {
private def timePoint(time: Moment, content: Frag, className: String = "") = tr(
`class` := s"timeline-point $className",
td(
`class` := "timeline-time",
span(`class` := "clock-time", time.hoursAndMinutes),
" ",
span(`class` := "relative-time", "+" + TimeInterval((time - row.startTime) / 60 / 1000)),
),
table(
`class` := "timeline",
(
prevParts lazyZip
parts lazyZip
row.partTimes lazyZip
nextPartInfos
).map(FullPartInfo).zipWithIndex.flatMap{ case (fpi, pos) =>
partTimeLine(row)(pos, fpi)
}
td(),
td(`class` := "timeline-content", content),
)

private def process(content: Frag, duration: TimeInterval, durationIcon: String, className: String = "") = tr(
`class` := s"timeline-process $className",
td(`class` := "timeline-time"),
td(`class` := "timeline-duration")(
fseq(
glyphicon(durationIcon),
" ",
duration.toString
)
),
h2("Vizualizace"),
chartButtons(row),
td(`class` := "timeline-content", content),
)
}

private def legendTable = {
table(
`class` := "timeline",
walk("chůze trvající 4:20", TimeInterval(260)),
timePoint(moment("2016-01-20 15:15"), "příchod/odchod v 15:15"),
pause("pauza trvající 10 minut", TimeInterval(10)),
gaveUp("konec před dosažením cíle"),
finish(moment("2016-01-20 16:20"), "Dosažení cíle v 16:20"),
private def end(content: Frag, className: String = "") = tr(
`class` := s"timeline-end $className",
td(`class` := "timeline-time"),
td(`class` := "timeline-duration"),
td(`class` := "timeline-content", content),
)
}

val brief = true
private def pause(content: Frag, duration: TimeInterval) =
process(content, duration = duration, durationIcon = "pause", className = "pause")

def verbose(f: Frag) = if(brief) "" else f
def verbose(s: String) = if(brief) "" else s

private def partTimeLine(row: Participant)(
pos: Int,
fullPartInfo: FullPartInfo
): Seq[Frag] = {
import fullPartInfo._
import row.gender
def langGaveUp = gender.inflect("vzdala", "vzdal")
def langArrived = gender.inflect("dorazila", "dorazil")
def cumLen: Frag = fseq(" (celkem ", strong(formatLength(partMeta.cumulativeTrackLength)), ")")
Seq(
timePoint(
part.startTime,
previousPartMetaOption.fold("Start")(pm =>
verbose(s"${gender.inflect("vyšla", "vyšel")} ${
s"z $pos. stanoviště ${pm.place}"
}")
private def walk(content: Frag, duration: TimeInterval) =
process(content, duration = duration, durationIcon = "play", className = "walk")

private def gaveUp(content: Frag) = end(content, className = "gave-up")

private def finish(time: Moment, content: Frag) = timePoint(time, content, className = "finish")

def timeLine = {
val prevParts = Seq(None) ++ parts.map(Some(_))
val nextPartInfos: Seq[Option[PartTimeInfo]] = row.partTimes.drop(1).map(Some(_)) ++ Seq(None)
div(
div(`class` := "legend")(
h2("Legenda"),
legendTable,
),
className = if (previousPartMetaOption.isEmpty) "start" else ""
),
) ++ (part match {
case PartTimeInfo.Finished(_startTime, endTime, intervalTime) =>
val isFinish = pos == parts.size - 1
fseq(
walk(
fseq(
strong(formatLength(partMeta.trackLength)), br(),
"rychlost ",
strong(f"${partMeta.trackLength * 60 / intervalTime.totalMinutes}%1.2f km/h"),
" = tempo ",
strong(f"${intervalTime / partMeta.trackLength} / km"),
),
duration = intervalTime,
// TODO: buttons for swithching clock/relTime and pace/speed
table(
`class` := "timeline",
(
prevParts lazyZip
parts lazyZip
row.partTimes lazyZip
nextPartInfos
)
.map(FullPartInfo)
.zipWithIndex
.flatMap((partTimeLine _).tupled)
),
h2("Vizualizace"),
chartButtons(row),
)
}

private def legendTable = {
table(
`class` := "timeline",
walk("chůze trvající 4:20", TimeInterval(260)),
timePoint(moment("2016-01-20 15:15"), "příchod/odchod v 15:15"),
pause("pauza trvající 10 minut", TimeInterval(10)),
gaveUp("konec před dosažením cíle"),
finish(moment("2016-01-20 16:20"), "Dosažení cíle v 16:20"),
)
}

private def partTimeLine(
fullPartInfo: FullPartInfo,
pos: Int,
): Seq[Frag] = {
import fullPartInfo._
import row.gender
def langGaveUp = gender.inflect("vzdala", "vzdal")

def langArrived = gender.inflect("dorazila", "dorazil")

def cumLen: Frag = fseq(" (celkem ", strong(formatLength(partMeta.cumulativeTrackLength)), ")")

Seq(
timePoint(
part.startTime,
previousPartMetaOption.fold("Start")(pm =>
verbose(s"${gender.inflect("vyšla", "vyšel")} ${
s"z $pos. stanoviště ${pm.place}"
}")
),
if(isFinish)
finish(
endTime,
fseq(s"Cíl: ${partMeta.place}", cumLen)
// TODO: stats
)
else
timePoint(
endTime,
verbose(s"$langArrived na ${pos + 1}. stanoviště ${partMeta.place}")
className = if (previousPartMetaOption.isEmpty) "start" else ""
),
) ++ (part match {
case PartTimeInfo.Finished(_startTime, endTime, intervalTime) =>
val isFinish = pos == parts.size - 1
fseq(
walk(
fseq(
strong(formatLength(partMeta.trackLength)), br(),
span(`class` := "speed")(strong(formatSpeed(partMeta.trackLength * 60 / intervalTime.totalMinutes))),
" ",
span(`class` := "pace")(strong(f"${intervalTime / partMeta.trackLength} / km")),
),
duration = intervalTime,
),
nextPartOption.fold[Frag](
if (isFinish) ""
else gaveUp(fseq(s"$langGaveUp to na stanovišti ", strong(s"${pos + 1}."), s" ${partMeta.place}", cumLen))
) { nextPart =>
pause(
fseq(strong(s"${pos+1}."), s" ${partMeta.place}", cumLen),
duration = TimeInterval((nextPart.startTime - endTime)/60/1000)
)
}
if (isFinish)
finish(
endTime,
fseq(s"Cíl: ${partMeta.place}", cumLen)
// TODO: stats
)
else
timePoint(
endTime,
verbose(s"$langArrived na ${pos + 1}. stanoviště ${partMeta.place}")
),
nextPartOption.fold[Frag](
if (isFinish) ""
else gaveUp(fseq(s"$langGaveUp to na stanovišti ", strong(s"${pos + 1}."), s" ${partMeta.place}", cumLen))
) { nextPart =>
pause(
fseq(strong(s"${pos + 1}."), s" ${partMeta.place}", cumLen),
duration = TimeInterval((nextPart.startTime - endTime) / 60 / 1000)
)
}
)
case PartTimeInfo.Unfinished(_startTime) => Seq(
gaveUp(s"$langGaveUp to při cestě na ${pos + 1}. stanoviště.")
)
case PartTimeInfo.Unfinished(_startTime) => Seq(
gaveUp(s"$langGaveUp to při cestě na ${pos+1}. stanoviště.")
)
})
})
}

}

def timeLine(row: Participant) = new ForPlayer(row).timeLine

private def chartButtons(row: Participant) = div(`class` := "chart-buttons")(
for {
Expand All @@ -177,5 +195,4 @@ final class TimeLineRenderer(participantTable: ParticipantTable, plotRenderer: P
jqModal.modal(js.Dictionary("keyboard" -> true))
})


}

0 comments on commit 037f3da

Please sign in to comment.