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

Update software stack #38

Merged
merged 5 commits into from
Sep 22, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
16 changes: 15 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,18 @@ jobs:
uses: actions/upload-artifact@v2
with:
name: site
path: pack.zip
path: pack.zip

test-bleeding-edge-java-and-npm:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: build image
run: docker build . --target bleeding-edge
- name: build website
run: env DOCKER_TAG=bleeding-edge ./dockerenv-github.sh ./pack.sh
- name: Upload artifact
uses: actions/upload-artifact@v2
with:
name: site
path: pack-for-verification.zip
31 changes: 22 additions & 9 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,26 +1,39 @@
# Adopted from mozilla/sbt

# This Dockerfile has one required ARG to determine which base image
# to use for the JDK to install.

# We need JDK 8, as it does not compile with JDK 11 for some unknown reason.
# JDK 8 is the LTS with the longest support at the time of writing.
ARG OPENJDK_TAG=8

# First stage just determines SBT version. If build.properties changes without changing the SBT version, it just rebuilds the first stage without rebuilding the second stage.
FROM amazoncorretto:${OPENJDK_TAG} AS sbt-version
COPY project/build.properties /
#RUN sed -n -e 's/^sbt\.version=//p' /build.properties > /version
RUN echo 1.1.1 > /version
RUN sed -n -e 's/^sbt\.version=//p' /build.properties > /version

# Second stage creates final image with the right SBT version
FROM amazoncorretto:${OPENJDK_TAG}
FROM amazoncorretto:${OPENJDK_TAG} AS conservative
RUN yum install -y rsync lftp zip unzip which
ARG NODE_VERSION=16
RUN yum install https://rpm.nodesource.com/pub_${NODE_VERSION}.x/nodistro/repo/nodesource-release-nodistro-1.noarch.rpm -y
RUN yum install nodejs npm -y --setopt=nodesource-nodejs.module_hotfixes=1
COPY --from=sbt-version /version /sbt-version
RUN \
curl -L -o sbt-$(cat /sbt-version).rpm https://scala.jfrog.io/ui/api/v1/download\?repoKey=rpm\&path=%252Fsbt-$(cat /sbt-version).rpm && \
echo SBT launcher downloaded && \
rpm -i sbt-$(cat /sbt-version).rpm && \
rm sbt-$(cat /sbt-version).rpm && \
mkdir /project
WORKDIR /project

# This stage can be used for testing whether the build is compatible with fresh versions of JDK and NodeJS/NPM
FROM fedora:latest AS bleeding-edge
RUN dnf install -y rsync lftp zip unzip which nodejs npm java-latest-openjdk-devel
COPY --from=sbt-version /version /sbt-version
RUN \
curl -L -o sbt-$(cat /sbt-version).rpm https://scala.jfrog.io/ui/api/v1/download\?repoKey=rpm\&path=%252Fsbt-$(cat /sbt-version).rpm && \
echo SBT launcher downloaded && \
rpm -i sbt-$(cat /sbt-version).rpm && \
rm sbt-$(cat /sbt-version).rpm && \
sbt sbtVersion && \
mkdir /project
WORKDIR /project
RUN yum install -y rsync lftp nodejs zip unzip which


FROM conservative AS default
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,18 @@ Kompiluje se do statického JS, běží celé v prohlížeči. Troška informac
## Běh lokálně

1. Nainstaluj si SBT (nebo použij Dockerové prostředí)
2. activator ~run
2. `sbt "project server" ~run`
3. Otevři http://localhost:9000/2016/statistiky/ (případně jiný rok)

## Přidání ročníku

1. Uprav project/PageGenerator.scala
2. Pokud běží Activator, restartuj ho nebo použij příkaz reload.
2. Pokud běží SBT, restartuj ho nebo použij příkaz reload.

## Export na web

a. Pouze pro Linux/MacOS: `./pack.sh` vygeneruje pack.zip
b. Kdekoliv: `activator stage` vygeneruje soubor server/target/scala-$scalaVersion/zbdb-stats-server_$scalaVersion-$version-web-assets.jar, ve kterém je adresář public.
b. Kdekoliv: `sbt stage` vygeneruje soubor server/target/scala-$scalaVersion/zbdb-stats-server_sjs${scalaJsVersion}_$scalaVersion-$version-web-assets.jar, ve kterém je adresář public.

## Verze formátu

Expand Down
90 changes: 55 additions & 35 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import scala.sys.process._
import java.io.IOException

val appVersion= "1.0"

lazy val scalaV = "2.11.8"
lazy val scalaV = "2.13.12"

val jqueryName: String = "jquery/2.1.4/jquery.js"

Expand Down Expand Up @@ -34,22 +37,40 @@ def write(file: File, content: String) = {
file
}

def download(out: File, source: URL) = {
if(out.exists) {
println(s"Source $source is already downloaded at $out.")
} else {
val tmpFile = file(out + ".tmp")
println(s"Downloading $source…")
tmpFile.getParentFile.mkdirs()
source #> tmpFile !;
if(! tmpFile.renameTo(out)) {
throw new IOException(s"Renaming $tmpFile to $out failed!")
}
println(s"Downloaded $source as $out")
}
out
}

// Generates other assets than client JS, plus contains a server for development purposes
lazy val server = (project in file("server")).settings(
version := appVersion,
name := "zbdb-stats-server",
scalaVersion := scalaV,
scalaJSProjects := Seq(client),
pipelineStages in Assets := Seq(scalaJSPipeline),
scalaJSStage := FullOptStage,
Assets / pipelineStages := Seq(scalaJSPipeline),
pipelineStages := Seq(concat, removeLibs, filter, digest, simpleUrlUpdate/*, digest*/, removeUnversionedAssets, gzip, moveLibs),
includeFilter in digest := "*",
excludeFilter in digest := "*.html" || "*.csv" || "*.json" || "*.json.new" ||
digest / includeFilter := "*",
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",
includeFilter in filter := "*.less" || "*.note" || "*.source" || "*.css" || "*.js",
excludeFilter in filter := "main.js" || "main.min.js" || "main.css" || "main.min.css",
filter / excludeFilter := "*.less" || "*.note" || "*.source" || "*.css" - "main.min.css" || "*.js" - "main.min.js",
filter / includeFilter := "*.css" || "*.html" || "*.js" || "*.csv" || "*.svg" || "*.woff" || "*.ttf" || "*.eot" || "*.woff2" || "*.json.new",
genHtmlDir := target.value / "web" / "html" / "main",
resourceDirectories in Assets += genHtmlDir.value,
resourceGenerators in Assets += Def.task {
Assets / resourceDirectories += genHtmlDir.value,
Assets / resourceGenerators += Def.task {
val yearHtmlFiles = for(year <- PageGenerator.Years) yield {
write(
file = genHtmlDir.value / s"${year.year}" / PublicDirName / s"index.html",
Expand All @@ -59,14 +80,12 @@ lazy val server = (project in file("server")).settings(
val allYearsListJsonFile = write(genHtmlDir.value / "years.json.new", PageGenerator.allYearsJsonString)
yearHtmlFiles :+ allYearsListJsonFile
}.taskValue,
resourceGenerators in Assets += Def.task {
Assets / resourceGenerators += Def.task {
for(year <- PageGenerator.Years if year.dataSource.csvDownloadUrl startsWith "https://") yield {
val out = genHtmlDir.value / s"${year.year}" / PublicDirName / s"${year.year}.csv"
IO.download(
new java.net.URL(year.dataSource.csvDownloadUrl),
out
download(
out = genHtmlDir.value / s"${year.year}" / PublicDirName / s"${year.year}.csv",
source = url(year.dataSource.csvDownloadUrl)
)
out
}
}.taskValue,
removeUnversionedAssets := { mappings: Seq[PathMapping] =>
Expand All @@ -81,40 +100,43 @@ lazy val server = (project in file("server")).settings(
case (file, name) => (file, PublicDirName + "/" + name)
}
},
includeFilter in simpleUrlUpdate := "*.css" || "*.js" || "*.html",
simpleUrlUpdate / includeFilter := "*.css" || "*.js" || "*.html",
// triggers scalaJSPipeline when using compile or continuous compilation
compile in Compile <<= (compile in Compile) dependsOn scalaJSPipeline,
Compile / compile := ((Compile / compile) dependsOn scalaJSPipeline).value,
Concat.groups := Seq(
"main.min.js" -> group(Seq("zbdb-stats-client-jsdeps.min.js", "zbdb-stats-client-opt.js", "zbdb-stats-client-launcher.js"))
"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.0.0",
"com.vmunier" %% "scalajs-scripts" % "1.2.0",
guice,
bootstrap,
jqPlot,
specs2 % Test
),
// Compile the project before generating Eclipse files, so that generated .scala or .class files for views and routes are present
EclipseKeys.preTasks := Seq(compile in Compile)
).enablePlugins(PlayScala)//.dependsOn(sharedJvm)
).enablePlugins(PlayScala, JSDependenciesPlugin, SbtWeb)//.dependsOn(sharedJvm)

def toPathMapping(f: File): PathMapping = f -> f.getName

// Generates client JS; other assets are generated by the server subproject
lazy val client = (project in file("client")).settings(
name := "zbdb-stats-client",
version := appVersion,
scalaJSStage := FullOptStage,
scalaVersion := scalaV,
persistLauncher := true,
persistLauncher in Test := false,
scalaJSSemantics ~= (_.withRuntimeClassName { linkedClass =>
val fullName = linkedClass.fullName
if (fullName.endsWith("Exception")) fullName
else ""
}),
scalaJSUseMainModuleInitializer := true,
Test / scalaJSUseMainModuleInitializer := false,
libraryDependencies ++= Seq(
"org.scala-js" %%% "scalajs-dom" % "0.9.1",
"com.lihaoyi" %%% "scalatags" % "0.5.2",
"com.github.marklister" %%% "product-collections" % "1.4.2"
"org.scala-js" %%% "scalajs-dom" % "2.7.0",
"com.lihaoyi" %%% "scalatags" % "0.12.0",
"com.nrinaudo" %%% "kantan.csv" % "0.7.0",
),

// Some magic required for compatibility with running the website directly from SBT (using Play)
Compile / fastLinkJS / jsMappings += toPathMapping((Compile / packageJSDependencies).value),
Compile / fullLinkJS / jsMappings += toPathMapping((Compile / packageMinifiedJSDependencies).value),

jsDependencies ++= Seq(
bootstrap / "bootstrap.min.js",
"org.webjars" % "momentjs" % "2.10.6" / "min/moment.min.js",
Expand All @@ -130,8 +152,8 @@ lazy val client = (project in file("client")).settings(
jqPlot / "jqplot.pointLabels.min.js" dependsOn "jquery.jqplot.min.js",
jqPlot / "jqplot.highlighter.min.js" dependsOn "jquery.jqplot.min.js",
"org.webjars.bower" % "console-polyfill" % "0.2.2" / "console-polyfill/0.2.2/index.js"
)
).enablePlugins(ScalaJSPlugin, ScalaJSWeb)//.dependsOn(sharedJs)
),
).enablePlugins(ScalaJSPlugin, JSDependenciesPlugin, ScalaJSWeb)//.dependsOn(sharedJs)

/*lazy val shared = (crossProject.crossType(CrossType.Pure) in file("shared")).
settings(scalaVersion := scalaV).
Expand All @@ -145,5 +167,3 @@ name := "zbdb-stats"

version := appVersion

// loads the server project at sbt startup
onLoad in Global := (Command.process("project server", _: State)) compose (onLoad in Global).value
41 changes: 27 additions & 14 deletions client/src/main/scala/com/example/Moment.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ package com.example

import com.example.moment.Moment

import scalajs.js
import scala.scalajs.js
import scala.scalajs.js.annotation._

@js.native class MomentSingleton extends js.Any{
@js.native trait MomentSingleton extends js.Any {
//def moment(): Moment = js.native
def utc(): Moment = js.native
def utc(x: Number): Moment = js.native
Expand All @@ -18,9 +19,9 @@ import scalajs.js
def tz(time: String, tz: String): Moment = js.native
}

package object moment extends scalajs.js.GlobalScope {


@js.native
@JSGlobalScope
object MomentJsGlobal extends js.Any {
def moment(moment: Moment): Moment = js.native
def moment(dateString: String): Moment = js.native
def moment(dateString: String, format: String): Moment = js.native
Expand All @@ -32,12 +33,24 @@ package object moment extends scalajs.js.GlobalScope {

}

package object moment {
@inline def moment(moment: Moment): Moment = MomentJsGlobal.moment(moment: Moment)
@inline def moment(dateString: String): Moment = MomentJsGlobal.moment(dateString: String)
@inline def moment(dateString: String, format: String): Moment = MomentJsGlobal.moment(dateString: String, format: String)
@inline def moment(dateString: String, format: String, locale: String): Moment = MomentJsGlobal.moment(dateString: String, format: String, locale: String)
@inline def moment(dateString: String, format: String, strict: Boolean): Moment = MomentJsGlobal.moment(dateString: String, format: String, strict: Boolean)
@inline def moment(dateString: String, format: String, locale: String, strict: Boolean): Moment = MomentJsGlobal.moment(dateString: String, format: String, locale: String, strict: Boolean)
@inline def moment: MomentSingleton = MomentJsGlobal.moment
}

package moment{



import scala.scalajs.js.Date

@js.native
class Moment extends js.Any {
trait Moment extends js.Any {
def add(time: Int, units: String): Moment = js.native
//def plus(time: Int, units: String): Moment = js.native

Expand All @@ -52,9 +65,9 @@ class Moment extends js.Any {
def minutes(v: Int): Moment = js.native
def seconds(): Int = js.native
def minus(other: Moment): Int = js.native
def -(other: Moment): Int = js.native
@JSOperator def -(other: Moment): Int = js.native
def plus(other: Int): Moment = js.native
def +(other: Int): Moment = js.native
@JSOperator def +(other: Int): Moment = js.native

def date(): Int = js.native
def date(v: Int): Moment = js.native
Expand All @@ -66,11 +79,6 @@ class Moment extends js.Any {
def isAfter(other: Moment): Boolean = js.native
def isAfter(other: Moment, precision: String): Boolean = js.native

def >=(other: Moment):Boolean = isSame(other) || (this isAfter other)
def <=(other: Moment):Boolean = isSame(other) || (this isBefore other)
def >(other: Moment): Boolean = this isAfter other
def <(other: Moment): Boolean = this isBefore other

def isSame(other: Moment): Boolean = js.native
//def clone(): Moment = js.native

Expand All @@ -87,10 +95,15 @@ object Moment {

class RichMoment(val moment: Moment) extends AnyVal{

def >=(other: Moment):Boolean = moment.isSame(other) || (moment isAfter other)
def <=(other: Moment):Boolean = moment.isSame(other) || (moment isBefore other)
def >(other: Moment): Boolean = moment isAfter other
def <(other: Moment): Boolean = moment isBefore other

def hoursAndMinutes = f"${moment.hours()}%d:${moment.minutes()}%02d"

}

object RichMoment{
implicit def toRichMoment(moment: Moment) = new RichMoment(moment)
implicit def toRichMoment(moment: Moment): RichMoment = new RichMoment(moment)
}
22 changes: 22 additions & 0 deletions client/src/main/scala/com/v6ak/scalajs/regex/JsPattern.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.v6ak.scalajs.regex

import scala.scalajs.js
import scala.language.implicitConversions


class JsPattern(val regex: js.RegExp) extends AnyVal {
def unapplySeq(s: String): Option[Seq[String]] = regex.exec(s) match {
case null => None
case parts => Some(parts.toSeq.drop(1).asInstanceOf[Seq[String]])
}
}

object JsPattern {

class JsPatternFactory(val s: String) extends AnyVal {
@inline def jsr: JsPattern = new JsPattern(new js.RegExp(s))
}

@inline implicit def wrapString(s: String): JsPatternFactory = new JsPatternFactory(s)

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.v6ak.scalajs.time

import com.v6ak.scalajs.regex.JsPattern.wrapString

case class TimeInterval(totalMinutes: Int) extends AnyVal{
def hours = totalMinutes/60
def minutes = totalMinutes%60
Expand All @@ -8,7 +10,7 @@ case class TimeInterval(totalMinutes: Int) extends AnyVal{
}

object TimeInterval{
private val TimeIntervalRegex = """^([0-9]+):([0-9]+)$""".r
private val TimeIntervalRegex = """^([0-9]+):([0-9]+)$""".jsr

def parse(s: String) = s match {
case TimeIntervalRegex(hs, ms) => TimeInterval(hs.toInt*60 + ms.toInt)
Expand Down
Loading
Loading