Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

优化性能和代码风格 #21

Merged
merged 3 commits into from
Sep 14, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 Activity = Interval[js.Dynamic]
type X = Distance
type Y = Gauge.MeterPerBeat
type Y2 = Gauge.BeatPerMinute *: Gauge.StepPerMinute *: Gauge.Pace *: EmptyTuple
given CorrelatePlot[Activity] = CorrelatePlot[X, Y, Y2, Activity]

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
zhongl marked this conversation as resolved.
Show resolved Hide resolved
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
zhongl marked this conversation as resolved.
Show resolved Hide resolved
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]
zhongl marked this conversation as resolved.
Show resolved Hide resolved

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