Skip to content

Commit

Permalink
优化性能和代码风格 (#21)
Browse files Browse the repository at this point in the history
* Refactor more generic.

* Clean code.

* Clean code.
  • Loading branch information
zhongl authored Sep 14, 2024
1 parent 1ad2ca1 commit db2e601
Show file tree
Hide file tree
Showing 27 changed files with 457 additions and 426 deletions.
4 changes: 2 additions & 2 deletions .scalafmt.conf
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ version = "3.8.3"
runner.dialect = scala3
project.git = true

preset = Scala.js
# preset = Scala.js
indent.defnSite = 2
align.preset = more
maxColumn = 120
Expand All @@ -28,4 +28,4 @@ fileOverride {
runner.dialect = scala213
rewrite.rules = [SortModifiers, PreferCurlyFors]
}
}
}
20 changes: 17 additions & 3 deletions src/Main.scala
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import org.scalajs.dom.*
import scala.scalajs.js

import org.scalajs.dom.HTMLElement
import org.scalajs.dom.MutationObserver
import org.scalajs.dom.MutationObserverInit
import org.scalajs.dom.URL
import org.scalajs.dom.document
import org.scalajs.dom.window

import core.*
import garmin.*
import plotly.given
import garmin.read.given
import plotly.{*, given}

object Main:

Expand All @@ -14,9 +22,15 @@ object Main:
MutationObserver: (smr, _) =>
val url = URL(window.location.href)
val added = smr.flatMap(_.addedNodes).collect({ case e: HTMLElement => e }).toSeq
summon[Page[(Activities, Profile)]](url -> added)
Page[(Activities, Profile)](url -> added)
.observe(document.querySelector("div.main-body"), init)

end main

type A = js.Dynamic
type X = Distance
type Y = Gauge.MeterPerBeat
type Y2 = Gauge.BeatPerMinute *: Gauge.StepPerMinute *: Gauge.Pace *: EmptyTuple
given CorrelatePlot[Interval[A]] = CorrelatePlot[X, Y, Y2, Interval[A]]

end Main
19 changes: 19 additions & 0 deletions src/core/Gauge.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package core

enum Gauge:
case MeterPerBeat
case BeatPerMinute
case StepPerMinute
case Pace

def label: String = this match
case MeterPerBeat => "mpb"
case BeatPerMinute => "bpm"
case StepPerMinute => "spm"
case Pace => "/km"
end Gauge
object Gauge:
type MeterPerBeat = MeterPerBeat.type
type BeatPerMinute = BeatPerMinute.type
type StepPerMinute = StepPerMinute.type
type Pace = Pace.type
13 changes: 13 additions & 0 deletions src/core/Page.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package core


trait Page[A] extends (Mutation => Option[Mutation])

object Page:
given any: Page[EmptyTuple] = Some(_)

given tuple[H, T <: Tuple](using h: Page[H], t: Page[T]): Page[H *: T] =
a => h(a).flatMap(t)

def apply[A](using pa: Page[A]) = pa
end Page
7 changes: 7 additions & 0 deletions src/core/Read.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package core

opaque type Read[T, A, B] = A => B
object Read:
def apply[T, A, B](a: A)(using r: Read[T, A, B]): B = r(a)

def apply[T, A, B](f: A => B): Read[T, A, B] = f
19 changes: 0 additions & 19 deletions src/core/metrics/Interval.scala

This file was deleted.

21 changes: 0 additions & 21 deletions src/core/metrics/package.scala

This file was deleted.

15 changes: 8 additions & 7 deletions src/core/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@ import org.scalajs.dom.*

type Mutation = (URL, Seq[HTMLElement])

trait Page[A] extends (Mutation => Option[Mutation])
object Page:
given any: Page[EmptyTuple] = Some(_)
type NonEmpty[+A] = (A, List[A])
extension [A](a: A) inline def single: NonEmpty[A] = a -> List.empty[A]

given tuple[H, T <: Tuple](using h: Page[H], t: Page[T]): Page[H *: T] =
a => h(a).flatMap(t)
type Interval[A] = NonEmpty[A]
type History[A] = NonEmpty[Interval[A]]

def apply[A](using pa: Page[A]) = pa
end Page
sealed trait Distance
sealed trait Duration
sealed trait Timestamp
sealed trait Box

type Injection[A] = (HTMLElement, A)
trait Inject[A] extends (Injection[A] => Unit):
Expand Down
12 changes: 4 additions & 8 deletions src/garmin/Activities.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,17 @@ package garmin

import java.util.NoSuchElementException as Complain


import org.scalajs.dom.HTMLElement
import org.scalajs.dom.document

import core.*
import core.metrics.*
import core.service.Fetch
import sourcecode.Name

trait Activities
sealed trait Activities
object Activities:
given page(
using Initialize[HTMLElement],
Fetch[ActivityId, Intervals],
Inject[History]
given page(using
Initialize[HTMLElement],
Inject[List[ActivityId]]
): Page[Activities] =
case (URL("/modern/activities", `activityType`("running")), `a.inline-edit-target`(_)) =>
val es = `a.inline-edit-target`.all(Seq(document)).toList
Expand Down
33 changes: 19 additions & 14 deletions src/garmin/ActivityId.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,36 +7,41 @@ import scala.scalajs.js
import org.scalajs.dom.Request

import core.*
import core.metrics.*
import core.service.Fetch

opaque type ActivityId = String

object ActivityId:
def apply(s: String): ActivityId = s

given inject(using Fetch[ActivityId, Intervals], Inject[History]): Inject[List[ActivityId]] = (e, ids) =>
for history <- Future.sequence(ids.map(_.request)) if history.nonEmpty do history.filter(_.nonEmpty).inject(e)

given fetch(using Fetch[Request, js.Dynamic], Conversion[Get, Request]): Fetch[ActivityId, Intervals] = id =>
given inject(using
Fetch[ActivityId, Option[Interval[js.Dynamic]]],
Inject[History[js.Dynamic]]
): Inject[List[ActivityId]] = (e, ids) =>
for case Some(h) :: t <- Future.sequence(ids.map(_.request)) do
(h -> t.map(_.toList).flatten).inject(e)

given fetch(using
Fetch[Request, js.Dynamic],
Conversion[Get, Request]
): Fetch[ActivityId, Option[Interval[js.Dynamic]]] = id =>
inline def url = s"https://connect.garmin.cn/activity-service/activity/$id/splits"
inline def referrer = s"https://connect.garmin.cn/modern/activity/$id"

inline def isActive(t: js.UndefOr[String]): Boolean =
t == "ACTIVE" || t == "INTERVAL" || t == js.undefined

for
d <- Get(url, referrer).convert.request
laps = d.asInstanceOf[Splits].lapDTOs.toList
yield for l <- laps if isActive(l.intensityType)
yield l.asInstanceOf[Interval]
for d <- Get(url, referrer).convert.request
yield d.asInstanceOf[Splits].lapDTOs.toList.filter(_.isActive) match
case h :: t => Some(h -> t)
case _ => None
end for
end fetch

private trait Lap extends js.Object:
def intensityType: js.UndefOr[String]

extension (l: Lap) private inline def isActive =
l.intensityType == "ACTIVE" || l.intensityType == "INTERVAL" || l.intensityType == js.undefined

private trait Splits extends js.Object:
def lapDTOs: js.Array[Lap]
def lapDTOs: js.Array[Lap & js.Dynamic]

end ActivityId
1 change: 0 additions & 1 deletion src/garmin/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import org.scalajs.dom.Element
import org.scalajs.dom.URL
import org.scalajs.dom.URLSearchParams


private[garmin] object URL:
def unapply(u: URL): Option[(String, URLSearchParams)] =
Some(u.pathname, u.searchParams)
Expand Down
43 changes: 43 additions & 0 deletions src/garmin/read/package.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package garmin.read

import scala.scalajs.js

import core.*

given Read[Gauge.BeatPerMinute, js.Dynamic, Double] = Read: a =>
a.averageHR.asInstanceOf[Double].round

given Read[Gauge.StepPerMinute, js.Dynamic, Double] = Read: a =>
a.averageRunCadence.asInstanceOf[Double].round

given Read[Distance, js.Dynamic, Double] = Read: a =>
a.distance.asInstanceOf[Double]

given Read[Duration, js.Dynamic, Double] = Read: a =>
a.duration.asInstanceOf[Double]

given Read[Timestamp, js.Dynamic, String] = Read: a =>
a.startTimeGMT.asInstanceOf[String]

given [A](using
Read[Distance, A, Double],
Read[Duration, A, Double],
Read[Gauge.BeatPerMinute, A, Double]
): Read[Gauge.MeterPerBeat, A, Double] = Read: a =>
val speed = Read[Distance, A, Double](a) / Read[Duration, A, Double](a)
val hr = Read[Gauge.BeatPerMinute, A, Double](a)
speed * 60 / hr
end given

given [A](using
Read[Distance, A, Double],
Read[Duration, A, Double]
): Read[Gauge.Pace, A, js.Date] = Read: a =>
inline val minute = 60
inline val hour = (60 * minute)
inline def spm = Read[Duration, A, Double](a) / Read[Distance, A, Double](a)
inline def spkm = Math.round(spm * 1000).intValue
new js.Date(0, 0, 0, spkm / hour, spkm / minute, spkm % minute)
end given

extension (d: Double) private inline def round = js.Math.round(d)
57 changes: 57 additions & 0 deletions src/plotly/Axis.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package plotly

import scala.scalajs.js

import typings.plotlyJs.anon.PartialLayoutAxis
import typings.plotlyJs.plotlyJsStrings.array
import typings.plotlyJs.plotlyJsStrings.reversed
import typings.plotlyJs.plotlyJsStrings.right
import typings.plotlyJs.plotlyJsStrings.y2

import core.*

opaque type Axis[T] = List[PartialLayoutAxis]
object Axis:

def apply[T](using a: Axis[T]): List[PartialLayoutAxis] = a

given [A]: Axis[EmptyTuple] = Nil
given [H, T <: Tuple](using h: Axis[H], t: Axis[T]): Axis[H *: T] = h ::: t

given Axis[Gauge.MeterPerBeat] = PartialLayoutAxis()
.setOverlaying(y2)
.setTickformat(".2f")
.setTickmodeSync
:: Nil

given Axis[Gauge.BeatPerMinute] = PartialLayoutAxis()
.setSide(right)
:: Nil

given Axis[Gauge.StepPerMinute] = PartialLayoutAxis()
.setSide(right)
:: Nil

given Axis[Gauge.Pace] = PartialLayoutAxis()
.setSide(right)
.setTickformat("%M:%S")
.setAutorange(reversed)
:: Nil

given Axis[Distance] = PartialLayoutAxis()
.setTickformat("~s")
.setTicksuffix("m")
.setTickmode(array)
:: Nil

given (using ColorPalette): Axis[Box] = PartialLayoutAxis()
.setTickformat(".2f")
.setColorIndex(0)
:: Nil

extension (a: PartialLayoutAxis)
private inline def setTickmodeSync: PartialLayoutAxis =
a.asInstanceOf[js.Dynamic].tickmode = "sync"
a

end Axis
Loading

0 comments on commit db2e601

Please sign in to comment.