Initial commit
This commit is contained in:
commit
a8168cf585
28 changed files with 2041 additions and 0 deletions
2
.tool-versions
Normal file
2
.tool-versions
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
gleam 1.2.1
|
||||||
|
erlang 27.0
|
5
backend/.gitignore
vendored
Normal file
5
backend/.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
*.beam
|
||||||
|
*.ez
|
||||||
|
/build
|
||||||
|
erl_crash.dump
|
||||||
|
.env
|
25
backend/README.md
Normal file
25
backend/README.md
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
# aurinko
|
||||||
|
|
||||||
|
[![Package Version](https://img.shields.io/hexpm/v/aurinko)](https://hex.pm/packages/aurinko)
|
||||||
|
[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/aurinko/)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
gleam add aurinko
|
||||||
|
```
|
||||||
|
```gleam
|
||||||
|
import aurinko
|
||||||
|
|
||||||
|
pub fn main() {
|
||||||
|
// TODO: An example of the project in use
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Further documentation can be found at <https://hexdocs.pm/aurinko>.
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
```sh
|
||||||
|
gleam run # Run the project
|
||||||
|
gleam test # Run the tests
|
||||||
|
gleam shell # Run an Erlang shell
|
||||||
|
```
|
34
backend/gleam.toml
Normal file
34
backend/gleam.toml
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
name = "aurinko_backend"
|
||||||
|
version = "1.0.0"
|
||||||
|
target = "erlang"
|
||||||
|
|
||||||
|
# Fill out these fields if you intend to generate HTML documentation or publish
|
||||||
|
# your project to the Hex package manager.
|
||||||
|
#
|
||||||
|
# description = ""
|
||||||
|
# licences = ["Apache-2.0"]
|
||||||
|
# repository = { type = "github", user = "username", repo = "project" }
|
||||||
|
# links = [{ title = "Website", href = "https://gleam.run" }]
|
||||||
|
#
|
||||||
|
# For a full reference of all the available options, you can have a look at
|
||||||
|
# https://gleam.run/writing-gleam/gleam-toml/.
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
gleam_stdlib = ">= 0.34.0 and < 2.0.0"
|
||||||
|
wisp = ">= 0.15.0 and < 1.0.0"
|
||||||
|
lustre = ">= 4.3.0 and < 5.0.0"
|
||||||
|
sqlight = ">= 0.9.0 and < 1.0.0"
|
||||||
|
gleam_httpc = ">= 2.2.0 and < 3.0.0"
|
||||||
|
biscotto = ">= 1.0.0 and < 2.0.0"
|
||||||
|
gleam_otp = ">= 0.10.0 and < 1.0.0"
|
||||||
|
gleam_erlang = ">= 0.25.0 and < 1.0.0"
|
||||||
|
gleam_http = ">= 3.6.0 and < 4.0.0"
|
||||||
|
htmgrrrl = ">= 0.3.0 and < 1.0.0"
|
||||||
|
form_coder = ">= 0.3.0 and < 1.0.0"
|
||||||
|
birl = ">= 1.7.1 and < 2.0.0"
|
||||||
|
gleam_json = ">= 1.0.1 and < 2.0.0"
|
||||||
|
envoy = ">= 1.0.1 and < 2.0.0"
|
||||||
|
mist = ">= 1.2.0 and < 2.0.0"
|
||||||
|
aurinko_common = { path = "../common" }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
53
backend/manifest.toml
Normal file
53
backend/manifest.toml
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
# This file was generated by Gleam
|
||||||
|
# You typically do not need to edit this file
|
||||||
|
|
||||||
|
packages = [
|
||||||
|
{ name = "birl", version = "1.7.1", build_tools = ["gleam"], requirements = ["gleam_stdlib", "ranger"], otp_app = "birl", source = "hex", outer_checksum = "5C66647D62BCB11FE327E7A6024907C4A17954EF22865FE0940B54A852446D01" },
|
||||||
|
{ name = "biscotto", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_http", "gleam_stdlib"], otp_app = "biscotto", source = "hex", outer_checksum = "A1CFEA1686FA8ABDE90B76E22775FF29EE8156A64DAC327F48141A68951D662C" },
|
||||||
|
{ name = "envoy", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "envoy", source = "hex", outer_checksum = "CFAACCCFC47654F7E8B75E614746ED924C65BD08B1DE21101548AC314A8B6A41" },
|
||||||
|
{ name = "esqlite", version = "0.8.8", build_tools = ["rebar3"], requirements = [], otp_app = "esqlite", source = "hex", outer_checksum = "374902457C7D94DC9409C98D3BDD1CA0D50A60DC9F3BDF1FD8EB74C0DCDF02D6" },
|
||||||
|
{ name = "exception", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "exception", source = "hex", outer_checksum = "F5580D584F16A20B7FCDCABF9E9BE9A2C1F6AC4F9176FA6DD0B63E3B20D450AA" },
|
||||||
|
{ name = "filepath", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "EFB6FF65C98B2A16378ABC3EE2B14124168C0CE5201553DE652E2644DCFDB594" },
|
||||||
|
{ name = "form_coder", version = "0.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "form_coder", source = "hex", outer_checksum = "FA27C97AADF66A79E4EEE78D30A50C18660A9FAFD91C27A5BC780DBC753CB8AF" },
|
||||||
|
{ name = "gleam_community_colour", version = "1.4.0", build_tools = ["gleam"], requirements = ["gleam_json", "gleam_stdlib"], otp_app = "gleam_community_colour", source = "hex", outer_checksum = "795964217EBEDB3DA656F5EB8F67D7AD22872EB95182042D3E7AFEF32D3FD2FE" },
|
||||||
|
{ name = "gleam_crypto", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_crypto", source = "hex", outer_checksum = "ADD058DEDE8F0341F1ADE3AAC492A224F15700829D9A3A3F9ADF370F875C51B7" },
|
||||||
|
{ name = "gleam_erlang", version = "0.25.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "054D571A7092D2A9727B3E5D183B7507DAB0DA41556EC9133606F09C15497373" },
|
||||||
|
{ name = "gleam_http", version = "3.6.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "8C07DF9DF8CC7F054C650839A51C30A7D3C26482AC241C899C1CEA86B22DBE51" },
|
||||||
|
{ name = "gleam_httpc", version = "2.2.0", build_tools = ["gleam"], requirements = ["gleam_http", "gleam_stdlib"], otp_app = "gleam_httpc", source = "hex", outer_checksum = "CF76C71002DEECF6DC5D9CA83D962728FAE166B57926BE442D827004D3C7DF1B" },
|
||||||
|
{ name = "gleam_json", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib", "thoas"], otp_app = "gleam_json", source = "hex", outer_checksum = "9063D14D25406326C0255BDA0021541E797D8A7A12573D849462CAFED459F6EB" },
|
||||||
|
{ name = "gleam_otp", version = "0.10.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "0B04FE915ACECE539B317F9652CAADBBC0F000184D586AAAF2D94C100945D72B" },
|
||||||
|
{ name = "gleam_stdlib", version = "0.38.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "663CF11861179AF415A625307447775C09404E752FF99A24E2057C835319F1BE" },
|
||||||
|
{ name = "glisten", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib"], otp_app = "glisten", source = "hex", outer_checksum = "CF3A9383E9BA4A8CBAF2F7B799716290D02F2AC34E7A77556B49376B662B9314" },
|
||||||
|
{ name = "gramps", version = "2.0.3", build_tools = ["gleam"], requirements = ["gleam_crypto", "gleam_erlang", "gleam_http", "gleam_stdlib"], otp_app = "gramps", source = "hex", outer_checksum = "3CCAA6E081225180D95C79679D383BBF51C8D1FDC1B84DA1DA444F628C373793" },
|
||||||
|
{ name = "hpack_erl", version = "0.3.0", build_tools = ["rebar3"], requirements = [], otp_app = "hpack", source = "hex", outer_checksum = "D6137D7079169D8C485C6962DFE261AF5B9EF60FBC557344511C1E65E3D95FB0" },
|
||||||
|
{ name = "htmerl", version = "0.1.0", build_tools = ["rebar3"], requirements = [], otp_app = "htmerl", source = "hex", outer_checksum = "D932F76EA33C318A79F41A429FDBEAE30820390DDB72BA89F38EEF32FEC36395" },
|
||||||
|
{ name = "htmgrrrl", version = "0.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "htmerl"], otp_app = "htmgrrrl", source = "hex", outer_checksum = "983492567967DAA64776E005B9E70353368B14E6F87D543E01308B48A7A0398F" },
|
||||||
|
{ name = "logging", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "logging", source = "hex", outer_checksum = "FCB111401BDB4703A440A94FF8CC7DA521112269C065F219C2766998333E7738" },
|
||||||
|
{ name = "lustre", version = "4.3.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_json", "gleam_otp", "gleam_stdlib"], otp_app = "lustre", source = "hex", outer_checksum = "43642C0602D3E2D6FEC3E24173D68A1F8E646969B53A2B0A5EB61238DDA739C4" },
|
||||||
|
{ name = "lustre_ui", version = "0.6.0", build_tools = ["gleam"], requirements = ["gleam_community_colour", "gleam_json", "gleam_stdlib", "lustre"], otp_app = "lustre_ui", source = "hex", outer_checksum = "FA1F9E89D89CDD5DF376ED86ABA8A38441CB2E664CD4D402F22A49DA4D7BB56D" },
|
||||||
|
{ name = "marceau", version = "1.2.0", build_tools = ["gleam"], requirements = [], otp_app = "marceau", source = "hex", outer_checksum = "5188D643C181EE350D8A20A3BDBD63AF7B6C505DE333CFBE05EF642ADD88A59B" },
|
||||||
|
{ name = "mist", version = "1.2.0", build_tools = ["gleam"], requirements = ["birl", "gleam_erlang", "gleam_http", "gleam_otp", "gleam_stdlib", "glisten", "gramps", "hpack_erl", "logging"], otp_app = "mist", source = "hex", outer_checksum = "109B4D64E68C104CC23BB3CC5441ECD479DD7444889DA01113B75C6AF0F0E17B" },
|
||||||
|
{ name = "ranger", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "ranger", source = "hex", outer_checksum = "1566C272B1D141B3BBA38B25CB761EF56E312E79EC0E2DFD4D3C19FB0CC1F98C" },
|
||||||
|
{ name = "simplifile", version = "2.0.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "95219227A43FCFE62C6E494F413A1D56FF953B68FE420698612E3D89A1EFE029" },
|
||||||
|
{ name = "sqlight", version = "0.9.0", build_tools = ["gleam"], requirements = ["esqlite", "gleam_stdlib"], otp_app = "sqlight", source = "hex", outer_checksum = "2D9C9BA420A5E7DCE7DB2DAAE4CAB0BE6218BEB48FD1531C583550B3D1316E94" },
|
||||||
|
{ name = "thoas", version = "1.2.1", build_tools = ["rebar3"], requirements = [], otp_app = "thoas", source = "hex", outer_checksum = "E38697EDFFD6E91BD12CEA41B155115282630075C2A727E7A6B2947F5408B86A" },
|
||||||
|
{ name = "wisp", version = "0.15.0", build_tools = ["gleam"], requirements = ["exception", "gleam_crypto", "gleam_erlang", "gleam_http", "gleam_json", "gleam_stdlib", "logging", "marceau", "mist", "simplifile"], otp_app = "wisp", source = "hex", outer_checksum = "33D17A50276FE0A10E4F694E4EF7D99836954DC2D920D4B5741B1E0EBCAE403F" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[requirements]
|
||||||
|
birl = { version = ">= 1.7.1 and < 2.0.0" }
|
||||||
|
biscotto = { version = ">= 1.0.0 and < 2.0.0" }
|
||||||
|
envoy = { version = ">= 1.0.1 and < 2.0.0" }
|
||||||
|
form_coder = { version = ">= 0.3.0 and < 1.0.0" }
|
||||||
|
gleam_erlang = { version = ">= 0.25.0 and < 1.0.0" }
|
||||||
|
gleam_http = { version = ">= 3.6.0 and < 4.0.0" }
|
||||||
|
gleam_httpc = { version = ">= 2.2.0 and < 3.0.0" }
|
||||||
|
gleam_json = { version = ">= 1.0.1 and < 2.0.0" }
|
||||||
|
gleam_otp = { version = ">= 0.10.0 and < 1.0.0" }
|
||||||
|
gleam_stdlib = { version = ">= 0.34.0 and < 2.0.0" }
|
||||||
|
htmgrrrl = { version = ">= 0.3.0 and < 1.0.0" }
|
||||||
|
lustre = { version = ">= 4.3.0 and < 5.0.0" }
|
||||||
|
lustre_ui = { version = ">= 0.6.0 and < 1.0.0"}
|
||||||
|
mist = { version = ">= 1.2.0 and < 2.0.0" }
|
||||||
|
sqlight = { version = ">= 0.9.0 and < 1.0.0" }
|
||||||
|
wisp = { version = ">= 0.15.0 and < 1.0.0" }
|
12
backend/priv/sql/0000_init.sql
Normal file
12
backend/priv/sql/0000_init.sql
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
create table modules (
|
||||||
|
id integer primary key,
|
||||||
|
sid text not null,
|
||||||
|
vid text not null
|
||||||
|
) strict;
|
||||||
|
|
||||||
|
create table module_measurements (
|
||||||
|
id integer primary key,
|
||||||
|
foreign key (module_id) references modules(id)
|
||||||
|
on update cascade
|
||||||
|
on delete restrict
|
||||||
|
) strict;
|
5
backend/priv/sql/init_migrations.sql
Normal file
5
backend/priv/sql/init_migrations.sql
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
create table if not exists migrations (
|
||||||
|
id integer primary key,
|
||||||
|
migration integer,
|
||||||
|
migrated_at text
|
||||||
|
) strict;
|
240
backend/src/ap_systems/api.gleam
Normal file
240
backend/src/ap_systems/api.gleam
Normal file
|
@ -0,0 +1,240 @@
|
||||||
|
import birl
|
||||||
|
import biscotto
|
||||||
|
import form_coder
|
||||||
|
import gleam/dynamic
|
||||||
|
import gleam/http
|
||||||
|
import gleam/http/request
|
||||||
|
import gleam/httpc
|
||||||
|
import gleam/int
|
||||||
|
import gleam/io
|
||||||
|
import gleam/json
|
||||||
|
import gleam/list
|
||||||
|
import gleam/option
|
||||||
|
import gleam/result
|
||||||
|
import gleam/string
|
||||||
|
import gleam/uri.{Uri}
|
||||||
|
import htmgrrrl
|
||||||
|
|
||||||
|
pub type APIError {
|
||||||
|
RequestError(err: dynamic.Dynamic)
|
||||||
|
DecodeError(err: json.DecodeError)
|
||||||
|
ScrapeError
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type ModuleIDs {
|
||||||
|
ModuleIDs(sid: String, vids: List(String))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type ModulePower {
|
||||||
|
ModulePower(detail: String, power: String, time: String)
|
||||||
|
}
|
||||||
|
|
||||||
|
const user_agent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36"
|
||||||
|
|
||||||
|
const root_path = "/ema"
|
||||||
|
|
||||||
|
const root_url = Uri(
|
||||||
|
scheme: option.Some("https"),
|
||||||
|
userinfo: option.None,
|
||||||
|
host: option.Some("www.apsystemsema.com"),
|
||||||
|
port: option.Some(443),
|
||||||
|
path: root_path,
|
||||||
|
query: option.None,
|
||||||
|
fragment: option.None,
|
||||||
|
)
|
||||||
|
|
||||||
|
const dashboard_root = "/ajax/getDashboardApiAjax"
|
||||||
|
|
||||||
|
const ajax_view_root = "/ajax/getViewAjax"
|
||||||
|
|
||||||
|
pub fn login(username: String, password: String) {
|
||||||
|
let cookies = biscotto.init()
|
||||||
|
|
||||||
|
let assert Ok(req) = request.from_uri(index_url())
|
||||||
|
let req = req |> request.set_method(http.Get) |> set_common_headers()
|
||||||
|
use resp <- result.try(httpc.send(req) |> result.map_error(RequestError))
|
||||||
|
let cookies = biscotto.from_response(cookies, resp)
|
||||||
|
|
||||||
|
let url =
|
||||||
|
Uri(
|
||||||
|
..login_url(),
|
||||||
|
query: option.Some(
|
||||||
|
uri.query_to_string([#("username", username), #("password", password)]),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
let assert Ok(req) = request.from_uri(url)
|
||||||
|
let req =
|
||||||
|
req
|
||||||
|
|> request.set_method(http.Post)
|
||||||
|
|> set_common_headers()
|
||||||
|
|> biscotto.with_cookies(cookies)
|
||||||
|
use resp <- result.try(httpc.send(req) |> result.map_error(RequestError))
|
||||||
|
Ok(biscotto.from_response(cookies, resp))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_production(cookies: biscotto.CookieJar) {
|
||||||
|
let assert Ok(req) = request.from_uri(production_info_url())
|
||||||
|
req
|
||||||
|
|> request.set_method(http.Post)
|
||||||
|
|> set_common_headers()
|
||||||
|
|> biscotto.with_cookies(cookies)
|
||||||
|
|> httpc.send()
|
||||||
|
|> result.map_error(RequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_module_ids(cookies: biscotto.CookieJar) {
|
||||||
|
let assert Ok(req) = request.from_uri(module_ids_url())
|
||||||
|
let req =
|
||||||
|
req
|
||||||
|
|> request.set_method(http.Post)
|
||||||
|
|> set_common_headers()
|
||||||
|
|> biscotto.with_cookies(cookies)
|
||||||
|
|> httpc.send()
|
||||||
|
|
||||||
|
use resp <- result.try(result.map_error(req, RequestError))
|
||||||
|
let parse_result =
|
||||||
|
htmgrrrl.sax(
|
||||||
|
resp.body,
|
||||||
|
#(False, ModuleIDs(sid: "", vids: [])),
|
||||||
|
fn(acc, _line, event) {
|
||||||
|
case acc, event {
|
||||||
|
#(False, _),
|
||||||
|
htmgrrrl.StartElement(
|
||||||
|
local_name: "select",
|
||||||
|
attributes: attributes,
|
||||||
|
..,
|
||||||
|
)
|
||||||
|
-> {
|
||||||
|
case
|
||||||
|
list.find(attributes, fn(attribute) {
|
||||||
|
attribute.name == "id" && attribute.value == "viewname"
|
||||||
|
})
|
||||||
|
{
|
||||||
|
Ok(_) -> #(True, acc.1)
|
||||||
|
Error(_) -> acc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#(True, _), htmgrrrl.EndElement(local_name: "select", ..) -> {
|
||||||
|
#(False, acc.1)
|
||||||
|
}
|
||||||
|
|
||||||
|
#(True, _),
|
||||||
|
htmgrrrl.StartElement(
|
||||||
|
local_name: "option",
|
||||||
|
attributes: attributes,
|
||||||
|
..,
|
||||||
|
)
|
||||||
|
-> {
|
||||||
|
let maybe_ids = {
|
||||||
|
use sid <- result.try(
|
||||||
|
list.find(attributes, fn(attribute) { attribute.name == "sid" }),
|
||||||
|
)
|
||||||
|
|
||||||
|
use vid <- result.try(
|
||||||
|
list.find(attributes, fn(attribute) {
|
||||||
|
attribute.name == "value"
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
let new_ids =
|
||||||
|
ModuleIDs(sid: sid.value, vids: [vid.value, ..{ acc.1 }.vids])
|
||||||
|
Ok(new_ids)
|
||||||
|
}
|
||||||
|
|
||||||
|
case maybe_ids {
|
||||||
|
Ok(new_ids) -> #(acc.0, new_ids)
|
||||||
|
Error(_) -> acc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, _ -> acc
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
use #(_, ids) <- result.try(result.replace_error(parse_result, ScrapeError))
|
||||||
|
Ok(ids)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_module_power(cookies: biscotto.CookieJar, modules: ModuleIDs) {
|
||||||
|
let today = birl.utc_now() |> birl.get_day()
|
||||||
|
|
||||||
|
list.try_fold(modules.vids, [], fn(acc, vid) {
|
||||||
|
let assert Ok(req) = request.from_uri(module_power_url())
|
||||||
|
let maybe_resp =
|
||||||
|
req
|
||||||
|
|> request.set_method(http.Post)
|
||||||
|
|> request.set_body(
|
||||||
|
form_coder.encode([
|
||||||
|
#("sid", form_coder.QStr(modules.sid)),
|
||||||
|
#("vid", form_coder.QStr(vid)),
|
||||||
|
#(
|
||||||
|
"date",
|
||||||
|
form_coder.QStr(
|
||||||
|
int.to_string(today.year)
|
||||||
|
<> string.pad_left(int.to_string(today.month), 2, "0")
|
||||||
|
<> string.pad_left(int.to_string(today.date), 2, "0"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
|> request.set_header(
|
||||||
|
"content-type",
|
||||||
|
"application/x-www-form-urlencoded; charset=UTF-8",
|
||||||
|
)
|
||||||
|
|> set_common_headers()
|
||||||
|
|> biscotto.with_cookies(cookies)
|
||||||
|
|> io.debug()
|
||||||
|
|> httpc.send()
|
||||||
|
|> result.map_error(RequestError)
|
||||||
|
|
||||||
|
use resp <- result.try(maybe_resp)
|
||||||
|
use body_dynamic <- result.try(
|
||||||
|
json.decode(
|
||||||
|
resp.body,
|
||||||
|
dynamic.decode3(
|
||||||
|
ModulePower,
|
||||||
|
dynamic.field("detail", dynamic.string),
|
||||||
|
dynamic.field("power", dynamic.string),
|
||||||
|
dynamic.field("time", dynamic.string),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|> result.map_error(DecodeError),
|
||||||
|
)
|
||||||
|
Ok([body_dynamic, ..acc])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_common_headers(req: request.Request(_)) {
|
||||||
|
request.set_header(req, "user-agent", user_agent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn index_url() {
|
||||||
|
Uri(..root_url, path: root_path <> "/index.action")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn login_url() {
|
||||||
|
Uri(..root_url, path: root_path <> "/loginEMA.action")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn production_info_url() {
|
||||||
|
Uri(
|
||||||
|
..root_url,
|
||||||
|
path: root_path <> dashboard_root <> "/getDashboardProductionInfoAjax",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn module_ids_url() {
|
||||||
|
Uri(
|
||||||
|
..root_url,
|
||||||
|
path: root_path <> "/security/optsecondmenu/intoViewOptModule.action",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn module_power_url() {
|
||||||
|
Uri(
|
||||||
|
..root_url,
|
||||||
|
path: root_path <> ajax_view_root <> "/getViewPowerByViewAjax",
|
||||||
|
)
|
||||||
|
}
|
87
backend/src/ap_systems/module_power.gleam
Normal file
87
backend/src/ap_systems/module_power.gleam
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
import ap_systems/api.{type ModulePower}
|
||||||
|
import birl
|
||||||
|
import gleam/dict
|
||||||
|
import gleam/dynamic.{DecodeError}
|
||||||
|
import gleam/int
|
||||||
|
import gleam/list
|
||||||
|
import gleam/result
|
||||||
|
import gleam/string
|
||||||
|
|
||||||
|
pub type MomentaryPower {
|
||||||
|
NotConnected
|
||||||
|
Watts(Int)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type ModuleDailyPower {
|
||||||
|
ModuleDailyPower(id: String, power: dict.Dict(birl.Time, MomentaryPower))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type CombinedDailyPower {
|
||||||
|
CombinedDailyPower(
|
||||||
|
times: List(birl.Time),
|
||||||
|
modules: dict.Dict(String, ModuleDailyPower),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decode_api_data(data: List(ModulePower)) {
|
||||||
|
list.try_fold(
|
||||||
|
data,
|
||||||
|
CombinedDailyPower(times: [], modules: dict.new()),
|
||||||
|
fn(acc, data) {
|
||||||
|
let times = case acc.times {
|
||||||
|
[] -> decode_times(data.time)
|
||||||
|
already_decoded -> Ok(already_decoded)
|
||||||
|
}
|
||||||
|
|
||||||
|
use times <- result.try(times)
|
||||||
|
|
||||||
|
let modules = string.split(data.detail, "&")
|
||||||
|
|
||||||
|
use modules <- result.try(list.try_map(modules, decode_module(_, times)))
|
||||||
|
|
||||||
|
let modules =
|
||||||
|
list.fold(modules, acc.modules, fn(acc, module) {
|
||||||
|
dict.insert(acc, module.id, module)
|
||||||
|
})
|
||||||
|
|
||||||
|
Ok(CombinedDailyPower(times: times, modules: modules))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decode_times(data: String) {
|
||||||
|
let times = string.split(data, ",")
|
||||||
|
list.try_map(times, fn(time) {
|
||||||
|
use time <- result.try(
|
||||||
|
int.parse(time)
|
||||||
|
|> result.replace_error(DecodeError("unix time", time, [])),
|
||||||
|
)
|
||||||
|
Ok(birl.from_unix_milli(time))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decode_module(data: String, times: List(birl.Time)) {
|
||||||
|
use #(id, data) <- result.try(
|
||||||
|
string.split_once(data, "/")
|
||||||
|
|> result.replace_error(DecodeError("/", "string without slash", [])),
|
||||||
|
)
|
||||||
|
|
||||||
|
let datapoints = string.split(data, ",")
|
||||||
|
use datapoints <- result.try(
|
||||||
|
list.try_map(datapoints, fn(datapoint) {
|
||||||
|
case datapoint {
|
||||||
|
"NC" -> Ok(NotConnected)
|
||||||
|
other ->
|
||||||
|
case int.parse(other) {
|
||||||
|
Ok(watts) -> Ok(Watts(watts))
|
||||||
|
Error(_) -> Error(DecodeError("integer or \"NC\"", other, []))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
let zipped = list.zip(times, datapoints)
|
||||||
|
let power = dict.from_list(zipped)
|
||||||
|
|
||||||
|
Ok(ModuleDailyPower(id: id, power: power))
|
||||||
|
}
|
34
backend/src/aurinko.gleam
Normal file
34
backend/src/aurinko.gleam
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import ap_systems/api
|
||||||
|
import ap_systems/module_power
|
||||||
|
import aurinko/web
|
||||||
|
import envoy
|
||||||
|
import gleam/erlang/process
|
||||||
|
import gleam/int
|
||||||
|
import gleam/io
|
||||||
|
import gleam/result
|
||||||
|
import gleam/string
|
||||||
|
|
||||||
|
pub fn main() {
|
||||||
|
let assert Ok(username) = envoy.get("USERNAME")
|
||||||
|
let assert Ok(password) = envoy.get("PASSWORD")
|
||||||
|
let assert Ok(port) = envoy.get("PORT")
|
||||||
|
let assert Ok(port) = int.parse(port)
|
||||||
|
let assert Ok(secret_key_base) = envoy.get("SECRET_KEY_BASE")
|
||||||
|
|
||||||
|
let assert Ok(_) = web.init(port, secret_key_base)
|
||||||
|
process.sleep_forever()
|
||||||
|
// use cookies <- result.try(
|
||||||
|
// api.login(username, password) |> result.map_error(string.inspect),
|
||||||
|
// )
|
||||||
|
|
||||||
|
// io.debug(cookies)
|
||||||
|
// io.debug(api.get_production(cookies) |> result.map_error(string.inspect))
|
||||||
|
// use ids <- result.try(
|
||||||
|
// io.debug(api.get_module_ids(cookies)) |> result.map_error(string.inspect),
|
||||||
|
// )
|
||||||
|
// use module_data <- result.try(
|
||||||
|
// api.get_module_power(cookies, ids) |> result.map_error(string.inspect),
|
||||||
|
// )
|
||||||
|
// io.debug(module_power.decode_api_data(module_data))
|
||||||
|
// |> result.map_error(string.inspect)
|
||||||
|
}
|
0
backend/src/aurinko/storage/modules.gleam
Normal file
0
backend/src/aurinko/storage/modules.gleam
Normal file
13
backend/src/aurinko/web.gleam
Normal file
13
backend/src/aurinko/web.gleam
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import aurinko/web/router
|
||||||
|
import mist
|
||||||
|
import wisp
|
||||||
|
|
||||||
|
pub fn init(port: Int, secret_key_base: String) {
|
||||||
|
wisp.configure_logger()
|
||||||
|
|
||||||
|
let assert Ok(_) =
|
||||||
|
wisp.mist_handler(router.handle_request, secret_key_base)
|
||||||
|
|> mist.new()
|
||||||
|
|> mist.port(port)
|
||||||
|
|> mist.start_http()
|
||||||
|
}
|
9
backend/src/aurinko/web/router.gleam
Normal file
9
backend/src/aurinko/web/router.gleam
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import wisp.{type Request}
|
||||||
|
|
||||||
|
pub fn handle_request(req: Request) {
|
||||||
|
use <- wisp.log_request(req)
|
||||||
|
use <- wisp.rescue_crashes()
|
||||||
|
use _ <- wisp.handle_head(req)
|
||||||
|
|
||||||
|
wisp.ok() |> wisp.string_body("Hello.")
|
||||||
|
}
|
1
backend/src/aurinko/web/templates/base.gleam
Normal file
1
backend/src/aurinko/web/templates/base.gleam
Normal file
|
@ -0,0 +1 @@
|
||||||
|
|
4
common/.gitignore
vendored
Normal file
4
common/.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
*.beam
|
||||||
|
*.ez
|
||||||
|
/build
|
||||||
|
erl_crash.dump
|
25
common/README.md
Normal file
25
common/README.md
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
# aurinko_common
|
||||||
|
|
||||||
|
[![Package Version](https://img.shields.io/hexpm/v/aurinko_common)](https://hex.pm/packages/aurinko_common)
|
||||||
|
[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/aurinko_common/)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
gleam add aurinko_common
|
||||||
|
```
|
||||||
|
```gleam
|
||||||
|
import aurinko_common
|
||||||
|
|
||||||
|
pub fn main() {
|
||||||
|
// TODO: An example of the project in use
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Further documentation can be found at <https://hexdocs.pm/aurinko_common>.
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
```sh
|
||||||
|
gleam run # Run the project
|
||||||
|
gleam test # Run the tests
|
||||||
|
gleam shell # Run an Erlang shell
|
||||||
|
```
|
18
common/gleam.toml
Normal file
18
common/gleam.toml
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
name = "aurinko_common"
|
||||||
|
version = "1.0.0"
|
||||||
|
|
||||||
|
# Fill out these fields if you intend to generate HTML documentation or publish
|
||||||
|
# your project to the Hex package manager.
|
||||||
|
#
|
||||||
|
# description = ""
|
||||||
|
# licences = ["Apache-2.0"]
|
||||||
|
# repository = { type = "github", user = "username", repo = "project" }
|
||||||
|
# links = [{ title = "Website", href = "https://gleam.run" }]
|
||||||
|
#
|
||||||
|
# For a full reference of all the available options, you can have a look at
|
||||||
|
# https://gleam.run/writing-gleam/gleam-toml/.
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
gleam_stdlib = ">= 0.34.0 and < 2.0.0"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
9
common/manifest.toml
Normal file
9
common/manifest.toml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
# This file was generated by Gleam
|
||||||
|
# You typically do not need to edit this file
|
||||||
|
|
||||||
|
packages = [
|
||||||
|
{ name = "gleam_stdlib", version = "0.38.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "663CF11861179AF415A625307447775C09404E752FF99A24E2057C835319F1BE" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[requirements]
|
||||||
|
gleam_stdlib = { version = ">= 0.34.0 and < 2.0.0" }
|
4
frontend/.gitignore
vendored
Normal file
4
frontend/.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
*.beam
|
||||||
|
*.ez
|
||||||
|
/build
|
||||||
|
erl_crash.dump
|
25
frontend/README.md
Normal file
25
frontend/README.md
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
# frontend
|
||||||
|
|
||||||
|
[![Package Version](https://img.shields.io/hexpm/v/frontend)](https://hex.pm/packages/frontend)
|
||||||
|
[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/frontend/)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
gleam add frontend
|
||||||
|
```
|
||||||
|
```gleam
|
||||||
|
import frontend
|
||||||
|
|
||||||
|
pub fn main() {
|
||||||
|
// TODO: An example of the project in use
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Further documentation can be found at <https://hexdocs.pm/frontend>.
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
```sh
|
||||||
|
gleam run # Run the project
|
||||||
|
gleam test # Run the tests
|
||||||
|
gleam shell # Run an Erlang shell
|
||||||
|
```
|
23
frontend/gleam.toml
Normal file
23
frontend/gleam.toml
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
name = "aurinko_frontend"
|
||||||
|
version = "1.0.0"
|
||||||
|
target = "javascript"
|
||||||
|
|
||||||
|
# Fill out these fields if you intend to generate HTML documentation or publish
|
||||||
|
# your project to the Hex package manager.
|
||||||
|
#
|
||||||
|
# description = ""
|
||||||
|
# licences = ["Apache-2.0"]
|
||||||
|
# repository = { type = "github", user = "username", repo = "project" }
|
||||||
|
# links = [{ title = "Website", href = "https://gleam.run" }]
|
||||||
|
#
|
||||||
|
# For a full reference of all the available options, you can have a look at
|
||||||
|
# https://gleam.run/writing-gleam/gleam-toml/.
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
gleam_stdlib = ">= 0.34.0 and < 2.0.0"
|
||||||
|
aurinko_common = { path = "../common" }
|
||||||
|
lustre = ">= 4.3.0 and < 5.0.0"
|
||||||
|
lustre_ui = ">= 0.6.0 and < 1.0.0"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
lustre_dev_tools = ">= 1.3.4 and < 2.0.0"
|
19
frontend/index.html
Normal file
19
frontend/index.html
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Aurinko</title>
|
||||||
|
<link rel="icon"
|
||||||
|
href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%2210 0 100 100%22><text y=%22.90em%22 font-size=%2290%22>🌅</text></svg>" />
|
||||||
|
<link rel="stylesheet" type="text/css" href="./priv/static/aurinko_frontend.css" />
|
||||||
|
<link rel="stylesheet" type="text/css" href="./priv/static/lustre-ui.css" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
|
||||||
|
<script type="module" src="./priv/static/aurinko_frontend.mjs"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
48
frontend/manifest.toml
Normal file
48
frontend/manifest.toml
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
# This file was generated by Gleam
|
||||||
|
# You typically do not need to edit this file
|
||||||
|
|
||||||
|
packages = [
|
||||||
|
{ name = "argv", version = "1.0.2", build_tools = ["gleam"], requirements = [], otp_app = "argv", source = "hex", outer_checksum = "BA1FF0929525DEBA1CE67256E5ADF77A7CDDFE729E3E3F57A5BDCAA031DED09D" },
|
||||||
|
{ name = "aurinko_common", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], source = "local", path = "../common" },
|
||||||
|
{ name = "birl", version = "1.7.1", build_tools = ["gleam"], requirements = ["gleam_stdlib", "ranger"], otp_app = "birl", source = "hex", outer_checksum = "5C66647D62BCB11FE327E7A6024907C4A17954EF22865FE0940B54A852446D01" },
|
||||||
|
{ name = "exception", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "exception", source = "hex", outer_checksum = "F5580D584F16A20B7FCDCABF9E9BE9A2C1F6AC4F9176FA6DD0B63E3B20D450AA" },
|
||||||
|
{ name = "filepath", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "EFB6FF65C98B2A16378ABC3EE2B14124168C0CE5201553DE652E2644DCFDB594" },
|
||||||
|
{ name = "fs", version = "8.6.1", build_tools = ["rebar3"], requirements = [], otp_app = "fs", source = "hex", outer_checksum = "61EA2BDAEDAE4E2024D0D25C63E44DCCF65622D4402DB4A2DF12868D1546503F" },
|
||||||
|
{ name = "gleam_community_ansi", version = "1.4.0", build_tools = ["gleam"], requirements = ["gleam_community_colour", "gleam_stdlib"], otp_app = "gleam_community_ansi", source = "hex", outer_checksum = "FE79E08BF97009729259B6357EC058315B6FBB916FAD1C2FF9355115FEB0D3A4" },
|
||||||
|
{ name = "gleam_community_colour", version = "1.4.0", build_tools = ["gleam"], requirements = ["gleam_json", "gleam_stdlib"], otp_app = "gleam_community_colour", source = "hex", outer_checksum = "795964217EBEDB3DA656F5EB8F67D7AD22872EB95182042D3E7AFEF32D3FD2FE" },
|
||||||
|
{ name = "gleam_crypto", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_crypto", source = "hex", outer_checksum = "ADD058DEDE8F0341F1ADE3AAC492A224F15700829D9A3A3F9ADF370F875C51B7" },
|
||||||
|
{ name = "gleam_erlang", version = "0.25.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "054D571A7092D2A9727B3E5D183B7507DAB0DA41556EC9133606F09C15497373" },
|
||||||
|
{ name = "gleam_http", version = "3.6.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "8C07DF9DF8CC7F054C650839A51C30A7D3C26482AC241C899C1CEA86B22DBE51" },
|
||||||
|
{ name = "gleam_httpc", version = "2.2.0", build_tools = ["gleam"], requirements = ["gleam_http", "gleam_stdlib"], otp_app = "gleam_httpc", source = "hex", outer_checksum = "CF76C71002DEECF6DC5D9CA83D962728FAE166B57926BE442D827004D3C7DF1B" },
|
||||||
|
{ name = "gleam_json", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib", "thoas"], otp_app = "gleam_json", source = "hex", outer_checksum = "9063D14D25406326C0255BDA0021541E797D8A7A12573D849462CAFED459F6EB" },
|
||||||
|
{ name = "gleam_otp", version = "0.10.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "0B04FE915ACECE539B317F9652CAADBBC0F000184D586AAAF2D94C100945D72B" },
|
||||||
|
{ name = "gleam_package_interface", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_json", "gleam_stdlib"], otp_app = "gleam_package_interface", source = "hex", outer_checksum = "CF3BFC5D0997750D9550D8D73A90F4B8D71C6C081B20ED4E70FFBE1E99AFC3C2" },
|
||||||
|
{ name = "gleam_stdlib", version = "0.38.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "663CF11861179AF415A625307447775C09404E752FF99A24E2057C835319F1BE" },
|
||||||
|
{ name = "glearray", version = "0.2.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "glearray", source = "hex", outer_checksum = "9C207E05F38D724F464FA921378DB3ABC2B0A2F5821116D8BC8B2CACC68930D5" },
|
||||||
|
{ name = "glint", version = "0.18.1", build_tools = ["gleam"], requirements = ["gleam_community_ansi", "gleam_community_colour", "gleam_stdlib", "snag"], otp_app = "glint", source = "hex", outer_checksum = "5FB54D7732B4105E4AF4D89A7EE6D5E8CF33DA13A3575D0C6ECE470B97958454" },
|
||||||
|
{ name = "glisten", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib"], otp_app = "glisten", source = "hex", outer_checksum = "CF3A9383E9BA4A8CBAF2F7B799716290D02F2AC34E7A77556B49376B662B9314" },
|
||||||
|
{ name = "gramps", version = "2.0.3", build_tools = ["gleam"], requirements = ["gleam_crypto", "gleam_erlang", "gleam_http", "gleam_stdlib"], otp_app = "gramps", source = "hex", outer_checksum = "3CCAA6E081225180D95C79679D383BBF51C8D1FDC1B84DA1DA444F628C373793" },
|
||||||
|
{ name = "hpack_erl", version = "0.3.0", build_tools = ["rebar3"], requirements = [], otp_app = "hpack", source = "hex", outer_checksum = "D6137D7079169D8C485C6962DFE261AF5B9EF60FBC557344511C1E65E3D95FB0" },
|
||||||
|
{ name = "logging", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "logging", source = "hex", outer_checksum = "FCB111401BDB4703A440A94FF8CC7DA521112269C065F219C2766998333E7738" },
|
||||||
|
{ name = "lustre", version = "4.3.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_json", "gleam_otp", "gleam_stdlib"], otp_app = "lustre", source = "hex", outer_checksum = "43642C0602D3E2D6FEC3E24173D68A1F8E646969B53A2B0A5EB61238DDA739C4" },
|
||||||
|
{ name = "lustre_dev_tools", version = "1.3.4", build_tools = ["gleam"], requirements = ["argv", "filepath", "fs", "gleam_community_ansi", "gleam_erlang", "gleam_http", "gleam_httpc", "gleam_json", "gleam_otp", "gleam_package_interface", "gleam_stdlib", "glint", "glisten", "mist", "simplifile", "spinner", "term_size", "tom", "wisp"], otp_app = "lustre_dev_tools", source = "hex", outer_checksum = "FB056F18870EA7FE2A070264A598CFCDB8F6F24D65FF989F18F3F46C9ABEEE31" },
|
||||||
|
{ name = "lustre_ui", version = "0.6.0", build_tools = ["gleam"], requirements = ["gleam_community_colour", "gleam_json", "gleam_stdlib", "lustre"], otp_app = "lustre_ui", source = "hex", outer_checksum = "FA1F9E89D89CDD5DF376ED86ABA8A38441CB2E664CD4D402F22A49DA4D7BB56D" },
|
||||||
|
{ name = "marceau", version = "1.2.0", build_tools = ["gleam"], requirements = [], otp_app = "marceau", source = "hex", outer_checksum = "5188D643C181EE350D8A20A3BDBD63AF7B6C505DE333CFBE05EF642ADD88A59B" },
|
||||||
|
{ name = "mist", version = "1.2.0", build_tools = ["gleam"], requirements = ["birl", "gleam_erlang", "gleam_http", "gleam_otp", "gleam_stdlib", "glisten", "gramps", "hpack_erl", "logging"], otp_app = "mist", source = "hex", outer_checksum = "109B4D64E68C104CC23BB3CC5441ECD479DD7444889DA01113B75C6AF0F0E17B" },
|
||||||
|
{ name = "ranger", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "ranger", source = "hex", outer_checksum = "1566C272B1D141B3BBA38B25CB761EF56E312E79EC0E2DFD4D3C19FB0CC1F98C" },
|
||||||
|
{ name = "repeatedly", version = "2.1.1", build_tools = ["gleam"], requirements = [], otp_app = "repeatedly", source = "hex", outer_checksum = "38808C3EC382B0CD981336D5879C24ECB37FCB9C1D1BD128F7A80B0F74404D79" },
|
||||||
|
{ name = "simplifile", version = "1.7.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "1D5DFA3A2F9319EC85825F6ED88B8E449F381B0D55A62F5E61424E748E7DDEB0" },
|
||||||
|
{ name = "snag", version = "0.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "snag", source = "hex", outer_checksum = "54D32E16E33655346AA3E66CBA7E191DE0A8793D2C05284E3EFB90AD2CE92BCC" },
|
||||||
|
{ name = "spinner", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_community_ansi", "gleam_erlang", "gleam_stdlib", "glearray", "repeatedly"], otp_app = "spinner", source = "hex", outer_checksum = "200BA3D4A04D468898E63C0D316E23F526E02514BC46454091975CB5BAE41E8F" },
|
||||||
|
{ name = "term_size", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "term_size", source = "hex", outer_checksum = "D00BD2BC8FB3EBB7E6AE076F3F1FF2AC9D5ED1805F004D0896C784D06C6645F1" },
|
||||||
|
{ name = "thoas", version = "1.2.1", build_tools = ["rebar3"], requirements = [], otp_app = "thoas", source = "hex", outer_checksum = "E38697EDFFD6E91BD12CEA41B155115282630075C2A727E7A6B2947F5408B86A" },
|
||||||
|
{ name = "tom", version = "0.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "tom", source = "hex", outer_checksum = "0831C73E45405A2153091226BF98FB485ED16376988602CC01A5FD086B82D577" },
|
||||||
|
{ name = "wisp", version = "0.14.0", build_tools = ["gleam"], requirements = ["exception", "gleam_crypto", "gleam_erlang", "gleam_http", "gleam_json", "gleam_stdlib", "logging", "marceau", "mist", "simplifile"], otp_app = "wisp", source = "hex", outer_checksum = "9F5453AF1F9275E6F8707BC815D6A6A9DF41551921B16FBDBA52883773BAE684" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[requirements]
|
||||||
|
aurinko_common = { path = "../common" }
|
||||||
|
gleam_stdlib = { version = ">= 0.34.0 and < 2.0.0" }
|
||||||
|
lustre = { version = ">= 4.3.0 and < 5.0.0" }
|
||||||
|
lustre_dev_tools = { version = ">= 1.3.4 and < 2.0.0"}
|
||||||
|
lustre_ui = { version = ">= 0.6.0 and < 1.0.0" }
|
545
frontend/priv/static/aurinko_frontend.css
Normal file
545
frontend/priv/static/aurinko_frontend.css
Normal file
|
@ -0,0 +1,545 @@
|
||||||
|
/*
|
||||||
|
! tailwindcss v3.4.1 | MIT License | https://tailwindcss.com
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
|
||||||
|
2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
|
||||||
|
*/
|
||||||
|
|
||||||
|
*,
|
||||||
|
::before,
|
||||||
|
::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
/* 1 */
|
||||||
|
border-width: 0;
|
||||||
|
/* 2 */
|
||||||
|
border-style: solid;
|
||||||
|
/* 2 */
|
||||||
|
border-color: #e5e7eb;
|
||||||
|
/* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
::before,
|
||||||
|
::after {
|
||||||
|
--tw-content: '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Use a consistent sensible line-height in all browsers.
|
||||||
|
2. Prevent adjustments of font size after orientation changes in iOS.
|
||||||
|
3. Use a more readable tab size.
|
||||||
|
4. Use the user's configured `sans` font-family by default.
|
||||||
|
5. Use the user's configured `sans` font-feature-settings by default.
|
||||||
|
6. Use the user's configured `sans` font-variation-settings by default.
|
||||||
|
7. Disable tap highlights on iOS
|
||||||
|
*/
|
||||||
|
|
||||||
|
html,
|
||||||
|
:host {
|
||||||
|
line-height: 1.5;
|
||||||
|
/* 1 */
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
/* 2 */
|
||||||
|
-moz-tab-size: 4;
|
||||||
|
/* 3 */
|
||||||
|
-o-tab-size: 4;
|
||||||
|
tab-size: 4;
|
||||||
|
/* 3 */
|
||||||
|
font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||||
|
/* 4 */
|
||||||
|
font-feature-settings: normal;
|
||||||
|
/* 5 */
|
||||||
|
font-variation-settings: normal;
|
||||||
|
/* 6 */
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
/* 7 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Remove the margin in all browsers.
|
||||||
|
2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
|
||||||
|
*/
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
/* 1 */
|
||||||
|
line-height: inherit;
|
||||||
|
/* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Add the correct height in Firefox.
|
||||||
|
2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
|
||||||
|
3. Ensure horizontal rules are visible by default.
|
||||||
|
*/
|
||||||
|
|
||||||
|
hr {
|
||||||
|
height: 0;
|
||||||
|
/* 1 */
|
||||||
|
color: inherit;
|
||||||
|
/* 2 */
|
||||||
|
border-top-width: 1px;
|
||||||
|
/* 3 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Add the correct text decoration in Chrome, Edge, and Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
abbr:where([title]) {
|
||||||
|
-webkit-text-decoration: underline dotted;
|
||||||
|
text-decoration: underline dotted;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Remove the default font size and weight for headings.
|
||||||
|
*/
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6 {
|
||||||
|
font-size: inherit;
|
||||||
|
font-weight: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Reset links to optimize for opt-in styling instead of opt-out.
|
||||||
|
*/
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Add the correct font weight in Edge and Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
b,
|
||||||
|
strong {
|
||||||
|
font-weight: bolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Use the user's configured `mono` font-family by default.
|
||||||
|
2. Use the user's configured `mono` font-feature-settings by default.
|
||||||
|
3. Use the user's configured `mono` font-variation-settings by default.
|
||||||
|
4. Correct the odd `em` font sizing in all browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
code,
|
||||||
|
kbd,
|
||||||
|
samp,
|
||||||
|
pre {
|
||||||
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||||
|
/* 1 */
|
||||||
|
font-feature-settings: normal;
|
||||||
|
/* 2 */
|
||||||
|
font-variation-settings: normal;
|
||||||
|
/* 3 */
|
||||||
|
font-size: 1em;
|
||||||
|
/* 4 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Add the correct font size in all browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
small {
|
||||||
|
font-size: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Prevent `sub` and `sup` elements from affecting the line height in all browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
sub,
|
||||||
|
sup {
|
||||||
|
font-size: 75%;
|
||||||
|
line-height: 0;
|
||||||
|
position: relative;
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub {
|
||||||
|
bottom: -0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
sup {
|
||||||
|
top: -0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
|
||||||
|
2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
|
||||||
|
3. Remove gaps between table borders by default.
|
||||||
|
*/
|
||||||
|
|
||||||
|
table {
|
||||||
|
text-indent: 0;
|
||||||
|
/* 1 */
|
||||||
|
border-color: inherit;
|
||||||
|
/* 2 */
|
||||||
|
border-collapse: collapse;
|
||||||
|
/* 3 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Change the font styles in all browsers.
|
||||||
|
2. Remove the margin in Firefox and Safari.
|
||||||
|
3. Remove default padding in all browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
button,
|
||||||
|
input,
|
||||||
|
optgroup,
|
||||||
|
select,
|
||||||
|
textarea {
|
||||||
|
font-family: inherit;
|
||||||
|
/* 1 */
|
||||||
|
font-feature-settings: inherit;
|
||||||
|
/* 1 */
|
||||||
|
font-variation-settings: inherit;
|
||||||
|
/* 1 */
|
||||||
|
font-size: 100%;
|
||||||
|
/* 1 */
|
||||||
|
font-weight: inherit;
|
||||||
|
/* 1 */
|
||||||
|
line-height: inherit;
|
||||||
|
/* 1 */
|
||||||
|
color: inherit;
|
||||||
|
/* 1 */
|
||||||
|
margin: 0;
|
||||||
|
/* 2 */
|
||||||
|
padding: 0;
|
||||||
|
/* 3 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Remove the inheritance of text transform in Edge and Firefox.
|
||||||
|
*/
|
||||||
|
|
||||||
|
button,
|
||||||
|
select {
|
||||||
|
text-transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Correct the inability to style clickable types in iOS and Safari.
|
||||||
|
2. Remove default button styles.
|
||||||
|
*/
|
||||||
|
|
||||||
|
button,
|
||||||
|
[type='button'],
|
||||||
|
[type='reset'],
|
||||||
|
[type='submit'] {
|
||||||
|
-webkit-appearance: button;
|
||||||
|
/* 1 */
|
||||||
|
background-color: transparent;
|
||||||
|
/* 2 */
|
||||||
|
background-image: none;
|
||||||
|
/* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Use the modern Firefox focus style for all focusable elements.
|
||||||
|
*/
|
||||||
|
|
||||||
|
:-moz-focusring {
|
||||||
|
outline: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
|
||||||
|
*/
|
||||||
|
|
||||||
|
:-moz-ui-invalid {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Add the correct vertical alignment in Chrome and Firefox.
|
||||||
|
*/
|
||||||
|
|
||||||
|
progress {
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Correct the cursor style of increment and decrement buttons in Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
::-webkit-inner-spin-button,
|
||||||
|
::-webkit-outer-spin-button {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Correct the odd appearance in Chrome and Safari.
|
||||||
|
2. Correct the outline style in Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
[type='search'] {
|
||||||
|
-webkit-appearance: textfield;
|
||||||
|
/* 1 */
|
||||||
|
outline-offset: -2px;
|
||||||
|
/* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Remove the inner padding in Chrome and Safari on macOS.
|
||||||
|
*/
|
||||||
|
|
||||||
|
::-webkit-search-decoration {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Correct the inability to style clickable types in iOS and Safari.
|
||||||
|
2. Change font properties to `inherit` in Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
::-webkit-file-upload-button {
|
||||||
|
-webkit-appearance: button;
|
||||||
|
/* 1 */
|
||||||
|
font: inherit;
|
||||||
|
/* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Add the correct display in Chrome and Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
summary {
|
||||||
|
display: list-item;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Removes the default spacing and border for appropriate elements.
|
||||||
|
*/
|
||||||
|
|
||||||
|
blockquote,
|
||||||
|
dl,
|
||||||
|
dd,
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6,
|
||||||
|
hr,
|
||||||
|
figure,
|
||||||
|
p,
|
||||||
|
pre {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldset {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
legend {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol,
|
||||||
|
ul,
|
||||||
|
menu {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Reset default styling for dialogs.
|
||||||
|
*/
|
||||||
|
|
||||||
|
dialog {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Prevent resizing textareas horizontally by default.
|
||||||
|
*/
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
|
||||||
|
2. Set the default placeholder color to the user's configured gray 400 color.
|
||||||
|
*/
|
||||||
|
|
||||||
|
input::-moz-placeholder, textarea::-moz-placeholder {
|
||||||
|
opacity: 1;
|
||||||
|
/* 1 */
|
||||||
|
color: #9ca3af;
|
||||||
|
/* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
input::placeholder,
|
||||||
|
textarea::placeholder {
|
||||||
|
opacity: 1;
|
||||||
|
/* 1 */
|
||||||
|
color: #9ca3af;
|
||||||
|
/* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Set the default cursor for buttons.
|
||||||
|
*/
|
||||||
|
|
||||||
|
button,
|
||||||
|
[role="button"] {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Make sure disabled buttons don't get the pointer cursor.
|
||||||
|
*/
|
||||||
|
|
||||||
|
:disabled {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
|
||||||
|
2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
|
||||||
|
This can trigger a poorly considered lint error in some tools but is included by design.
|
||||||
|
*/
|
||||||
|
|
||||||
|
img,
|
||||||
|
svg,
|
||||||
|
video,
|
||||||
|
canvas,
|
||||||
|
audio,
|
||||||
|
iframe,
|
||||||
|
embed,
|
||||||
|
object {
|
||||||
|
display: block;
|
||||||
|
/* 1 */
|
||||||
|
vertical-align: middle;
|
||||||
|
/* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
|
||||||
|
*/
|
||||||
|
|
||||||
|
img,
|
||||||
|
video {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make elements with the HTML hidden attribute stay hidden by default */
|
||||||
|
|
||||||
|
[hidden] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
*, ::before, ::after {
|
||||||
|
--tw-border-spacing-x: 0;
|
||||||
|
--tw-border-spacing-y: 0;
|
||||||
|
--tw-translate-x: 0;
|
||||||
|
--tw-translate-y: 0;
|
||||||
|
--tw-rotate: 0;
|
||||||
|
--tw-skew-x: 0;
|
||||||
|
--tw-skew-y: 0;
|
||||||
|
--tw-scale-x: 1;
|
||||||
|
--tw-scale-y: 1;
|
||||||
|
--tw-pan-x: ;
|
||||||
|
--tw-pan-y: ;
|
||||||
|
--tw-pinch-zoom: ;
|
||||||
|
--tw-scroll-snap-strictness: proximity;
|
||||||
|
--tw-gradient-from-position: ;
|
||||||
|
--tw-gradient-via-position: ;
|
||||||
|
--tw-gradient-to-position: ;
|
||||||
|
--tw-ordinal: ;
|
||||||
|
--tw-slashed-zero: ;
|
||||||
|
--tw-numeric-figure: ;
|
||||||
|
--tw-numeric-spacing: ;
|
||||||
|
--tw-numeric-fraction: ;
|
||||||
|
--tw-ring-inset: ;
|
||||||
|
--tw-ring-offset-width: 0px;
|
||||||
|
--tw-ring-offset-color: #fff;
|
||||||
|
--tw-ring-color: rgb(59 130 246 / 0.5);
|
||||||
|
--tw-ring-offset-shadow: 0 0 #0000;
|
||||||
|
--tw-ring-shadow: 0 0 #0000;
|
||||||
|
--tw-shadow: 0 0 #0000;
|
||||||
|
--tw-shadow-colored: 0 0 #0000;
|
||||||
|
--tw-blur: ;
|
||||||
|
--tw-brightness: ;
|
||||||
|
--tw-contrast: ;
|
||||||
|
--tw-grayscale: ;
|
||||||
|
--tw-hue-rotate: ;
|
||||||
|
--tw-invert: ;
|
||||||
|
--tw-saturate: ;
|
||||||
|
--tw-sepia: ;
|
||||||
|
--tw-drop-shadow: ;
|
||||||
|
--tw-backdrop-blur: ;
|
||||||
|
--tw-backdrop-brightness: ;
|
||||||
|
--tw-backdrop-contrast: ;
|
||||||
|
--tw-backdrop-grayscale: ;
|
||||||
|
--tw-backdrop-hue-rotate: ;
|
||||||
|
--tw-backdrop-invert: ;
|
||||||
|
--tw-backdrop-opacity: ;
|
||||||
|
--tw-backdrop-saturate: ;
|
||||||
|
--tw-backdrop-sepia: ;
|
||||||
|
}
|
||||||
|
|
||||||
|
::backdrop {
|
||||||
|
--tw-border-spacing-x: 0;
|
||||||
|
--tw-border-spacing-y: 0;
|
||||||
|
--tw-translate-x: 0;
|
||||||
|
--tw-translate-y: 0;
|
||||||
|
--tw-rotate: 0;
|
||||||
|
--tw-skew-x: 0;
|
||||||
|
--tw-skew-y: 0;
|
||||||
|
--tw-scale-x: 1;
|
||||||
|
--tw-scale-y: 1;
|
||||||
|
--tw-pan-x: ;
|
||||||
|
--tw-pan-y: ;
|
||||||
|
--tw-pinch-zoom: ;
|
||||||
|
--tw-scroll-snap-strictness: proximity;
|
||||||
|
--tw-gradient-from-position: ;
|
||||||
|
--tw-gradient-via-position: ;
|
||||||
|
--tw-gradient-to-position: ;
|
||||||
|
--tw-ordinal: ;
|
||||||
|
--tw-slashed-zero: ;
|
||||||
|
--tw-numeric-figure: ;
|
||||||
|
--tw-numeric-spacing: ;
|
||||||
|
--tw-numeric-fraction: ;
|
||||||
|
--tw-ring-inset: ;
|
||||||
|
--tw-ring-offset-width: 0px;
|
||||||
|
--tw-ring-offset-color: #fff;
|
||||||
|
--tw-ring-color: rgb(59 130 246 / 0.5);
|
||||||
|
--tw-ring-offset-shadow: 0 0 #0000;
|
||||||
|
--tw-ring-shadow: 0 0 #0000;
|
||||||
|
--tw-shadow: 0 0 #0000;
|
||||||
|
--tw-shadow-colored: 0 0 #0000;
|
||||||
|
--tw-blur: ;
|
||||||
|
--tw-brightness: ;
|
||||||
|
--tw-contrast: ;
|
||||||
|
--tw-grayscale: ;
|
||||||
|
--tw-hue-rotate: ;
|
||||||
|
--tw-invert: ;
|
||||||
|
--tw-saturate: ;
|
||||||
|
--tw-sepia: ;
|
||||||
|
--tw-drop-shadow: ;
|
||||||
|
--tw-backdrop-blur: ;
|
||||||
|
--tw-backdrop-brightness: ;
|
||||||
|
--tw-backdrop-contrast: ;
|
||||||
|
--tw-backdrop-grayscale: ;
|
||||||
|
--tw-backdrop-hue-rotate: ;
|
||||||
|
--tw-backdrop-invert: ;
|
||||||
|
--tw-backdrop-opacity: ;
|
||||||
|
--tw-backdrop-saturate: ;
|
||||||
|
--tw-backdrop-sepia: ;
|
||||||
|
}
|
755
frontend/priv/static/aurinko_frontend.mjs
Normal file
755
frontend/priv/static/aurinko_frontend.mjs
Normal file
|
@ -0,0 +1,755 @@
|
||||||
|
// build/dev/javascript/prelude.mjs
|
||||||
|
var CustomType = class {
|
||||||
|
withFields(fields) {
|
||||||
|
let properties = Object.keys(this).map(
|
||||||
|
(label) => label in fields ? fields[label] : this[label]
|
||||||
|
);
|
||||||
|
return new this.constructor(...properties);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var List = class {
|
||||||
|
static fromArray(array3, tail) {
|
||||||
|
let t = tail || new Empty();
|
||||||
|
for (let i = array3.length - 1; i >= 0; --i) {
|
||||||
|
t = new NonEmpty(array3[i], t);
|
||||||
|
}
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
[Symbol.iterator]() {
|
||||||
|
return new ListIterator(this);
|
||||||
|
}
|
||||||
|
toArray() {
|
||||||
|
return [...this];
|
||||||
|
}
|
||||||
|
// @internal
|
||||||
|
atLeastLength(desired) {
|
||||||
|
for (let _ of this) {
|
||||||
|
if (desired <= 0)
|
||||||
|
return true;
|
||||||
|
desired--;
|
||||||
|
}
|
||||||
|
return desired <= 0;
|
||||||
|
}
|
||||||
|
// @internal
|
||||||
|
hasLength(desired) {
|
||||||
|
for (let _ of this) {
|
||||||
|
if (desired <= 0)
|
||||||
|
return false;
|
||||||
|
desired--;
|
||||||
|
}
|
||||||
|
return desired === 0;
|
||||||
|
}
|
||||||
|
countLength() {
|
||||||
|
let length2 = 0;
|
||||||
|
for (let _ of this)
|
||||||
|
length2++;
|
||||||
|
return length2;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
function toList(elements, tail) {
|
||||||
|
return List.fromArray(elements, tail);
|
||||||
|
}
|
||||||
|
var ListIterator = class {
|
||||||
|
#current;
|
||||||
|
constructor(current) {
|
||||||
|
this.#current = current;
|
||||||
|
}
|
||||||
|
next() {
|
||||||
|
if (this.#current instanceof Empty) {
|
||||||
|
return { done: true };
|
||||||
|
} else {
|
||||||
|
let { head, tail } = this.#current;
|
||||||
|
this.#current = tail;
|
||||||
|
return { value: head, done: false };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var Empty = class extends List {
|
||||||
|
};
|
||||||
|
var NonEmpty = class extends List {
|
||||||
|
constructor(head, tail) {
|
||||||
|
super();
|
||||||
|
this.head = head;
|
||||||
|
this.tail = tail;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var Result = class _Result extends CustomType {
|
||||||
|
// @internal
|
||||||
|
static isResult(data) {
|
||||||
|
return data instanceof _Result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var Ok = class extends Result {
|
||||||
|
constructor(value) {
|
||||||
|
super();
|
||||||
|
this[0] = value;
|
||||||
|
}
|
||||||
|
// @internal
|
||||||
|
isOk() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var Error = class extends Result {
|
||||||
|
constructor(detail) {
|
||||||
|
super();
|
||||||
|
this[0] = detail;
|
||||||
|
}
|
||||||
|
// @internal
|
||||||
|
isOk() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
function makeError(variant, module, line, fn, message, extra) {
|
||||||
|
let error = new globalThis.Error(message);
|
||||||
|
error.gleam_error = variant;
|
||||||
|
error.module = module;
|
||||||
|
error.line = line;
|
||||||
|
error.fn = fn;
|
||||||
|
for (let k in extra)
|
||||||
|
error[k] = extra[k];
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
// build/dev/javascript/gleam_stdlib/gleam/option.mjs
|
||||||
|
var None = class extends CustomType {
|
||||||
|
};
|
||||||
|
|
||||||
|
// build/dev/javascript/gleam_stdlib/dict.mjs
|
||||||
|
var tempDataView = new DataView(new ArrayBuffer(8));
|
||||||
|
var SHIFT = 5;
|
||||||
|
var BUCKET_SIZE = Math.pow(2, SHIFT);
|
||||||
|
var MASK = BUCKET_SIZE - 1;
|
||||||
|
var MAX_INDEX_NODE = BUCKET_SIZE / 2;
|
||||||
|
var MIN_ARRAY_NODE = BUCKET_SIZE / 4;
|
||||||
|
|
||||||
|
// build/dev/javascript/gleam_stdlib/gleam_stdlib.mjs
|
||||||
|
function to_string3(term) {
|
||||||
|
return term.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// build/dev/javascript/gleam_stdlib/gleam/int.mjs
|
||||||
|
function to_string(x) {
|
||||||
|
return to_string3(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
// build/dev/javascript/gleam_stdlib/gleam/bool.mjs
|
||||||
|
function guard(requirement, consequence, alternative) {
|
||||||
|
if (requirement) {
|
||||||
|
return consequence;
|
||||||
|
} else {
|
||||||
|
return alternative();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// build/dev/javascript/lustre/lustre/effect.mjs
|
||||||
|
var Effect = class extends CustomType {
|
||||||
|
constructor(all) {
|
||||||
|
super();
|
||||||
|
this.all = all;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
function none() {
|
||||||
|
return new Effect(toList([]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// build/dev/javascript/lustre/lustre/internals/vdom.mjs
|
||||||
|
var Text = class extends CustomType {
|
||||||
|
constructor(content) {
|
||||||
|
super();
|
||||||
|
this.content = content;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var Element = class extends CustomType {
|
||||||
|
constructor(key, namespace, tag, attrs, children, self_closing, void$) {
|
||||||
|
super();
|
||||||
|
this.key = key;
|
||||||
|
this.namespace = namespace;
|
||||||
|
this.tag = tag;
|
||||||
|
this.attrs = attrs;
|
||||||
|
this.children = children;
|
||||||
|
this.self_closing = self_closing;
|
||||||
|
this.void = void$;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var Event = class extends CustomType {
|
||||||
|
constructor(x0, x1) {
|
||||||
|
super();
|
||||||
|
this[0] = x0;
|
||||||
|
this[1] = x1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// build/dev/javascript/lustre/lustre/attribute.mjs
|
||||||
|
function on(name, handler) {
|
||||||
|
return new Event("on" + name, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
// build/dev/javascript/lustre/lustre/element.mjs
|
||||||
|
function element(tag, attrs, children) {
|
||||||
|
if (tag === "area") {
|
||||||
|
return new Element("", "", tag, attrs, toList([]), false, true);
|
||||||
|
} else if (tag === "base") {
|
||||||
|
return new Element("", "", tag, attrs, toList([]), false, true);
|
||||||
|
} else if (tag === "br") {
|
||||||
|
return new Element("", "", tag, attrs, toList([]), false, true);
|
||||||
|
} else if (tag === "col") {
|
||||||
|
return new Element("", "", tag, attrs, toList([]), false, true);
|
||||||
|
} else if (tag === "embed") {
|
||||||
|
return new Element("", "", tag, attrs, toList([]), false, true);
|
||||||
|
} else if (tag === "hr") {
|
||||||
|
return new Element("", "", tag, attrs, toList([]), false, true);
|
||||||
|
} else if (tag === "img") {
|
||||||
|
return new Element("", "", tag, attrs, toList([]), false, true);
|
||||||
|
} else if (tag === "input") {
|
||||||
|
return new Element("", "", tag, attrs, toList([]), false, true);
|
||||||
|
} else if (tag === "link") {
|
||||||
|
return new Element("", "", tag, attrs, toList([]), false, true);
|
||||||
|
} else if (tag === "meta") {
|
||||||
|
return new Element("", "", tag, attrs, toList([]), false, true);
|
||||||
|
} else if (tag === "param") {
|
||||||
|
return new Element("", "", tag, attrs, toList([]), false, true);
|
||||||
|
} else if (tag === "source") {
|
||||||
|
return new Element("", "", tag, attrs, toList([]), false, true);
|
||||||
|
} else if (tag === "track") {
|
||||||
|
return new Element("", "", tag, attrs, toList([]), false, true);
|
||||||
|
} else if (tag === "wbr") {
|
||||||
|
return new Element("", "", tag, attrs, toList([]), false, true);
|
||||||
|
} else {
|
||||||
|
return new Element("", "", tag, attrs, children, false, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function text(content) {
|
||||||
|
return new Text(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
// build/dev/javascript/lustre/lustre/internals/runtime.mjs
|
||||||
|
var Debug = class extends CustomType {
|
||||||
|
constructor(x0) {
|
||||||
|
super();
|
||||||
|
this[0] = x0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var Dispatch = class extends CustomType {
|
||||||
|
constructor(x0) {
|
||||||
|
super();
|
||||||
|
this[0] = x0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var Shutdown = class extends CustomType {
|
||||||
|
};
|
||||||
|
var ForceModel = class extends CustomType {
|
||||||
|
constructor(x0) {
|
||||||
|
super();
|
||||||
|
this[0] = x0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// build/dev/javascript/lustre/vdom.ffi.mjs
|
||||||
|
function morph(prev, next, dispatch, isComponent = false) {
|
||||||
|
let out;
|
||||||
|
let stack = [{ prev, next, parent: prev.parentNode }];
|
||||||
|
while (stack.length) {
|
||||||
|
let { prev: prev2, next: next2, parent } = stack.pop();
|
||||||
|
if (next2.subtree !== void 0)
|
||||||
|
next2 = next2.subtree();
|
||||||
|
if (next2.content !== void 0) {
|
||||||
|
if (!prev2) {
|
||||||
|
const created = document.createTextNode(next2.content);
|
||||||
|
parent.appendChild(created);
|
||||||
|
out ??= created;
|
||||||
|
} else if (prev2.nodeType === Node.TEXT_NODE) {
|
||||||
|
if (prev2.textContent !== next2.content)
|
||||||
|
prev2.textContent = next2.content;
|
||||||
|
out ??= prev2;
|
||||||
|
} else {
|
||||||
|
const created = document.createTextNode(next2.content);
|
||||||
|
parent.replaceChild(created, prev2);
|
||||||
|
out ??= created;
|
||||||
|
}
|
||||||
|
} else if (next2.tag !== void 0) {
|
||||||
|
const created = createElementNode({
|
||||||
|
prev: prev2,
|
||||||
|
next: next2,
|
||||||
|
dispatch,
|
||||||
|
stack,
|
||||||
|
isComponent
|
||||||
|
});
|
||||||
|
if (!prev2) {
|
||||||
|
parent.appendChild(created);
|
||||||
|
} else if (prev2 !== created) {
|
||||||
|
parent.replaceChild(created, prev2);
|
||||||
|
}
|
||||||
|
out ??= created;
|
||||||
|
} else if (next2.elements !== void 0) {
|
||||||
|
iterateElement(next2, (fragmentElement) => {
|
||||||
|
stack.unshift({ prev: prev2, next: fragmentElement, parent });
|
||||||
|
prev2 = prev2?.nextSibling;
|
||||||
|
});
|
||||||
|
} else if (next2.subtree !== void 0) {
|
||||||
|
stack.push({ prev: prev2, next: next2, parent });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
function createElementNode({ prev, next, dispatch, stack }) {
|
||||||
|
const namespace = next.namespace || "http://www.w3.org/1999/xhtml";
|
||||||
|
const canMorph = prev && prev.nodeType === Node.ELEMENT_NODE && prev.localName === next.tag && prev.namespaceURI === (next.namespace || "http://www.w3.org/1999/xhtml");
|
||||||
|
const el2 = canMorph ? prev : namespace ? document.createElementNS(namespace, next.tag) : document.createElement(next.tag);
|
||||||
|
let handlersForEl;
|
||||||
|
if (!registeredHandlers.has(el2)) {
|
||||||
|
const emptyHandlers = /* @__PURE__ */ new Map();
|
||||||
|
registeredHandlers.set(el2, emptyHandlers);
|
||||||
|
handlersForEl = emptyHandlers;
|
||||||
|
} else {
|
||||||
|
handlersForEl = registeredHandlers.get(el2);
|
||||||
|
}
|
||||||
|
const prevHandlers = canMorph ? new Set(handlersForEl.keys()) : null;
|
||||||
|
const prevAttributes = canMorph ? new Set(Array.from(prev.attributes, (a) => a.name)) : null;
|
||||||
|
let className = null;
|
||||||
|
let style = null;
|
||||||
|
let innerHTML = null;
|
||||||
|
for (const attr of next.attrs) {
|
||||||
|
const name = attr[0];
|
||||||
|
const value = attr[1];
|
||||||
|
if (attr.as_property) {
|
||||||
|
if (el2[name] !== value)
|
||||||
|
el2[name] = value;
|
||||||
|
if (canMorph)
|
||||||
|
prevAttributes.delete(name);
|
||||||
|
} else if (name.startsWith("on")) {
|
||||||
|
const eventName = name.slice(2);
|
||||||
|
const callback = dispatch(value);
|
||||||
|
if (!handlersForEl.has(eventName)) {
|
||||||
|
el2.addEventListener(eventName, lustreGenericEventHandler);
|
||||||
|
}
|
||||||
|
handlersForEl.set(eventName, callback);
|
||||||
|
if (canMorph)
|
||||||
|
prevHandlers.delete(eventName);
|
||||||
|
} else if (name.startsWith("data-lustre-on-")) {
|
||||||
|
const eventName = name.slice(15);
|
||||||
|
const callback = dispatch(lustreServerEventHandler);
|
||||||
|
if (!handlersForEl.has(eventName)) {
|
||||||
|
el2.addEventListener(eventName, lustreGenericEventHandler);
|
||||||
|
}
|
||||||
|
handlersForEl.set(eventName, callback);
|
||||||
|
el2.setAttribute(name, value);
|
||||||
|
} else if (name === "class") {
|
||||||
|
className = className === null ? value : className + " " + value;
|
||||||
|
} else if (name === "style") {
|
||||||
|
style = style === null ? value : style + value;
|
||||||
|
} else if (name === "dangerous-unescaped-html") {
|
||||||
|
innerHTML = value;
|
||||||
|
} else {
|
||||||
|
if (el2.getAttribute(name) !== value)
|
||||||
|
el2.setAttribute(name, value);
|
||||||
|
if (name === "value" || name === "selected")
|
||||||
|
el2[name] = value;
|
||||||
|
if (canMorph)
|
||||||
|
prevAttributes.delete(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (className !== null) {
|
||||||
|
el2.setAttribute("class", className);
|
||||||
|
if (canMorph)
|
||||||
|
prevAttributes.delete("class");
|
||||||
|
}
|
||||||
|
if (style !== null) {
|
||||||
|
el2.setAttribute("style", style);
|
||||||
|
if (canMorph)
|
||||||
|
prevAttributes.delete("style");
|
||||||
|
}
|
||||||
|
if (canMorph) {
|
||||||
|
for (const attr of prevAttributes) {
|
||||||
|
el2.removeAttribute(attr);
|
||||||
|
}
|
||||||
|
for (const eventName of prevHandlers) {
|
||||||
|
handlersForEl.delete(eventName);
|
||||||
|
el2.removeEventListener(eventName, lustreGenericEventHandler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (next.key !== void 0 && next.key !== "") {
|
||||||
|
el2.setAttribute("data-lustre-key", next.key);
|
||||||
|
} else if (innerHTML !== null) {
|
||||||
|
el2.innerHTML = innerHTML;
|
||||||
|
return el2;
|
||||||
|
}
|
||||||
|
let prevChild = el2.firstChild;
|
||||||
|
let seenKeys = null;
|
||||||
|
let keyedChildren = null;
|
||||||
|
let incomingKeyedChildren = null;
|
||||||
|
let firstChild = next.children[Symbol.iterator]().next().value;
|
||||||
|
if (canMorph && firstChild !== void 0 && // Explicit checks are more verbose but truthy checks force a bunch of comparisons
|
||||||
|
// we don't care about: it's never gonna be a number etc.
|
||||||
|
firstChild.key !== void 0 && firstChild.key !== "") {
|
||||||
|
seenKeys = /* @__PURE__ */ new Set();
|
||||||
|
keyedChildren = getKeyedChildren(prev);
|
||||||
|
incomingKeyedChildren = getKeyedChildren(next);
|
||||||
|
}
|
||||||
|
for (const child of next.children) {
|
||||||
|
iterateElement(child, (currElement) => {
|
||||||
|
if (currElement.key !== void 0 && seenKeys !== null) {
|
||||||
|
prevChild = diffKeyedChild(
|
||||||
|
prevChild,
|
||||||
|
currElement,
|
||||||
|
el2,
|
||||||
|
stack,
|
||||||
|
incomingKeyedChildren,
|
||||||
|
keyedChildren,
|
||||||
|
seenKeys
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
stack.unshift({ prev: prevChild, next: currElement, parent: el2 });
|
||||||
|
prevChild = prevChild?.nextSibling;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
while (prevChild) {
|
||||||
|
const next2 = prevChild.nextSibling;
|
||||||
|
el2.removeChild(prevChild);
|
||||||
|
prevChild = next2;
|
||||||
|
}
|
||||||
|
return el2;
|
||||||
|
}
|
||||||
|
var registeredHandlers = /* @__PURE__ */ new WeakMap();
|
||||||
|
function lustreGenericEventHandler(event2) {
|
||||||
|
const target = event2.currentTarget;
|
||||||
|
if (!registeredHandlers.has(target)) {
|
||||||
|
target.removeEventListener(event2.type, lustreGenericEventHandler);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const handlersForEventTarget = registeredHandlers.get(target);
|
||||||
|
if (!handlersForEventTarget.has(event2.type)) {
|
||||||
|
target.removeEventListener(event2.type, lustreGenericEventHandler);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handlersForEventTarget.get(event2.type)(event2);
|
||||||
|
}
|
||||||
|
function lustreServerEventHandler(event2) {
|
||||||
|
const el2 = event2.currentTarget;
|
||||||
|
const tag = el2.getAttribute(`data-lustre-on-${event2.type}`);
|
||||||
|
const data = JSON.parse(el2.getAttribute("data-lustre-data") || "{}");
|
||||||
|
const include = JSON.parse(el2.getAttribute("data-lustre-include") || "[]");
|
||||||
|
switch (event2.type) {
|
||||||
|
case "input":
|
||||||
|
case "change":
|
||||||
|
include.push("target.value");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
tag,
|
||||||
|
data: include.reduce(
|
||||||
|
(data2, property) => {
|
||||||
|
const path = property.split(".");
|
||||||
|
for (let i = 0, o = data2, e = event2; i < path.length; i++) {
|
||||||
|
if (i === path.length - 1) {
|
||||||
|
o[path[i]] = e[path[i]];
|
||||||
|
} else {
|
||||||
|
o[path[i]] ??= {};
|
||||||
|
e = e[path[i]];
|
||||||
|
o = o[path[i]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data2;
|
||||||
|
},
|
||||||
|
{ data }
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function getKeyedChildren(el2) {
|
||||||
|
const keyedChildren = /* @__PURE__ */ new Map();
|
||||||
|
if (el2) {
|
||||||
|
for (const child of el2.children) {
|
||||||
|
iterateElement(child, (currElement) => {
|
||||||
|
const key = currElement?.key || currElement?.getAttribute?.("data-lustre-key");
|
||||||
|
if (key)
|
||||||
|
keyedChildren.set(key, currElement);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return keyedChildren;
|
||||||
|
}
|
||||||
|
function diffKeyedChild(prevChild, child, el2, stack, incomingKeyedChildren, keyedChildren, seenKeys) {
|
||||||
|
while (prevChild && !incomingKeyedChildren.has(prevChild.getAttribute("data-lustre-key"))) {
|
||||||
|
const nextChild = prevChild.nextSibling;
|
||||||
|
el2.removeChild(prevChild);
|
||||||
|
prevChild = nextChild;
|
||||||
|
}
|
||||||
|
if (keyedChildren.size === 0) {
|
||||||
|
iterateElement(child, (currChild) => {
|
||||||
|
stack.unshift({ prev: prevChild, next: currChild, parent: el2 });
|
||||||
|
prevChild = prevChild?.nextSibling;
|
||||||
|
});
|
||||||
|
return prevChild;
|
||||||
|
}
|
||||||
|
if (seenKeys.has(child.key)) {
|
||||||
|
console.warn(`Duplicate key found in Lustre vnode: ${child.key}`);
|
||||||
|
stack.unshift({ prev: null, next: child, parent: el2 });
|
||||||
|
return prevChild;
|
||||||
|
}
|
||||||
|
seenKeys.add(child.key);
|
||||||
|
const keyedChild = keyedChildren.get(child.key);
|
||||||
|
if (!keyedChild && !prevChild) {
|
||||||
|
stack.unshift({ prev: null, next: child, parent: el2 });
|
||||||
|
return prevChild;
|
||||||
|
}
|
||||||
|
if (!keyedChild && prevChild !== null) {
|
||||||
|
const placeholder = document.createTextNode("");
|
||||||
|
el2.insertBefore(placeholder, prevChild);
|
||||||
|
stack.unshift({ prev: placeholder, next: child, parent: el2 });
|
||||||
|
return prevChild;
|
||||||
|
}
|
||||||
|
if (!keyedChild || keyedChild === prevChild) {
|
||||||
|
stack.unshift({ prev: prevChild, next: child, parent: el2 });
|
||||||
|
prevChild = prevChild?.nextSibling;
|
||||||
|
return prevChild;
|
||||||
|
}
|
||||||
|
el2.insertBefore(keyedChild, prevChild);
|
||||||
|
stack.unshift({ prev: keyedChild, next: child, parent: el2 });
|
||||||
|
return prevChild;
|
||||||
|
}
|
||||||
|
function iterateElement(element2, processElement) {
|
||||||
|
if (element2.elements !== void 0) {
|
||||||
|
for (const currElement of element2.elements) {
|
||||||
|
processElement(currElement);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
processElement(element2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// build/dev/javascript/lustre/client-runtime.ffi.mjs
|
||||||
|
var LustreClientApplication2 = class _LustreClientApplication {
|
||||||
|
#root = null;
|
||||||
|
#queue = [];
|
||||||
|
#effects = [];
|
||||||
|
#didUpdate = false;
|
||||||
|
#isComponent = false;
|
||||||
|
#model = null;
|
||||||
|
#update = null;
|
||||||
|
#view = null;
|
||||||
|
static start(flags, selector, init3, update3, view2) {
|
||||||
|
if (!is_browser())
|
||||||
|
return new Error(new NotABrowser());
|
||||||
|
const root2 = selector instanceof HTMLElement ? selector : document.querySelector(selector);
|
||||||
|
if (!root2)
|
||||||
|
return new Error(new ElementNotFound(selector));
|
||||||
|
const app = new _LustreClientApplication(init3(flags), update3, view2, root2);
|
||||||
|
return new Ok((msg) => app.send(msg));
|
||||||
|
}
|
||||||
|
constructor([model, effects], update3, view2, root2 = document.body, isComponent = false) {
|
||||||
|
this.#model = model;
|
||||||
|
this.#update = update3;
|
||||||
|
this.#view = view2;
|
||||||
|
this.#root = root2;
|
||||||
|
this.#effects = effects.all.toArray();
|
||||||
|
this.#didUpdate = true;
|
||||||
|
this.#isComponent = isComponent;
|
||||||
|
window.requestAnimationFrame(() => this.#tick());
|
||||||
|
}
|
||||||
|
send(action) {
|
||||||
|
switch (true) {
|
||||||
|
case action instanceof Dispatch: {
|
||||||
|
this.#queue.push(action[0]);
|
||||||
|
this.#tick();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case action instanceof Shutdown: {
|
||||||
|
this.#shutdown();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case action instanceof Debug: {
|
||||||
|
this.#debug(action[0]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
emit(event2, data) {
|
||||||
|
this.#root.dispatchEvent(
|
||||||
|
new CustomEvent(event2, {
|
||||||
|
bubbles: true,
|
||||||
|
detail: data,
|
||||||
|
composed: true
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#tick() {
|
||||||
|
this.#flush_queue();
|
||||||
|
if (this.#didUpdate) {
|
||||||
|
const vdom = this.#view(this.#model);
|
||||||
|
const dispatch = (handler) => (e) => {
|
||||||
|
const result = handler(e);
|
||||||
|
if (result instanceof Ok) {
|
||||||
|
this.send(new Dispatch(result[0]));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.#didUpdate = false;
|
||||||
|
this.#root = morph(this.#root, vdom, dispatch, this.#isComponent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#flush_queue(iterations = 0) {
|
||||||
|
while (this.#queue.length) {
|
||||||
|
const [next, effects] = this.#update(this.#model, this.#queue.shift());
|
||||||
|
this.#didUpdate ||= this.#model !== next;
|
||||||
|
this.#model = next;
|
||||||
|
this.#effects = this.#effects.concat(effects.all.toArray());
|
||||||
|
}
|
||||||
|
while (this.#effects.length) {
|
||||||
|
this.#effects.shift()(
|
||||||
|
(msg) => this.send(new Dispatch(msg)),
|
||||||
|
(event2, data) => this.emit(event2, data)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (this.#queue.length) {
|
||||||
|
if (iterations < 5) {
|
||||||
|
this.#flush_queue(++iterations);
|
||||||
|
} else {
|
||||||
|
window.requestAnimationFrame(() => this.#tick());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#debug(action) {
|
||||||
|
switch (true) {
|
||||||
|
case action instanceof ForceModel: {
|
||||||
|
const vdom = this.#view(action[0]);
|
||||||
|
const dispatch = (handler) => (e) => {
|
||||||
|
const result = handler(e);
|
||||||
|
if (result instanceof Ok) {
|
||||||
|
this.send(new Dispatch(result[0]));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.#queue = [];
|
||||||
|
this.#effects = [];
|
||||||
|
this.#didUpdate = false;
|
||||||
|
this.#root = morph(this.#root, vdom, dispatch, this.#isComponent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#shutdown() {
|
||||||
|
this.#root.remove();
|
||||||
|
this.#root = null;
|
||||||
|
this.#model = null;
|
||||||
|
this.#queue = [];
|
||||||
|
this.#effects = [];
|
||||||
|
this.#didUpdate = false;
|
||||||
|
this.#update = () => {
|
||||||
|
};
|
||||||
|
this.#view = () => {
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var start = (app, selector, flags) => LustreClientApplication2.start(
|
||||||
|
flags,
|
||||||
|
selector,
|
||||||
|
app.init,
|
||||||
|
app.update,
|
||||||
|
app.view
|
||||||
|
);
|
||||||
|
var is_browser = () => globalThis.window && window.document;
|
||||||
|
|
||||||
|
// build/dev/javascript/lustre/lustre.mjs
|
||||||
|
var App = class extends CustomType {
|
||||||
|
constructor(init3, update3, view2, on_attribute_change) {
|
||||||
|
super();
|
||||||
|
this.init = init3;
|
||||||
|
this.update = update3;
|
||||||
|
this.view = view2;
|
||||||
|
this.on_attribute_change = on_attribute_change;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var ElementNotFound = class extends CustomType {
|
||||||
|
constructor(selector) {
|
||||||
|
super();
|
||||||
|
this.selector = selector;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var NotABrowser = class extends CustomType {
|
||||||
|
};
|
||||||
|
function application(init3, update3, view2) {
|
||||||
|
return new App(init3, update3, view2, new None());
|
||||||
|
}
|
||||||
|
function simple(init3, update3, view2) {
|
||||||
|
let init$1 = (flags) => {
|
||||||
|
return [init3(flags), none()];
|
||||||
|
};
|
||||||
|
let update$1 = (model, msg) => {
|
||||||
|
return [update3(model, msg), none()];
|
||||||
|
};
|
||||||
|
return application(init$1, update$1, view2);
|
||||||
|
}
|
||||||
|
function start3(app, selector, flags) {
|
||||||
|
return guard(
|
||||||
|
!is_browser(),
|
||||||
|
new Error(new NotABrowser()),
|
||||||
|
() => {
|
||||||
|
return start(app, selector, flags);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// build/dev/javascript/lustre/lustre/element/html.mjs
|
||||||
|
function div(attrs, children) {
|
||||||
|
return element("div", attrs, children);
|
||||||
|
}
|
||||||
|
function p(attrs, children) {
|
||||||
|
return element("p", attrs, children);
|
||||||
|
}
|
||||||
|
function button(attrs, children) {
|
||||||
|
return element("button", attrs, children);
|
||||||
|
}
|
||||||
|
|
||||||
|
// build/dev/javascript/lustre/lustre/event.mjs
|
||||||
|
function on2(name, handler) {
|
||||||
|
return on(name, handler);
|
||||||
|
}
|
||||||
|
function on_click(msg) {
|
||||||
|
return on2("click", (_) => {
|
||||||
|
return new Ok(msg);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// build/dev/javascript/aurinko_frontend/aurinko_frontend.mjs
|
||||||
|
var Incr = class extends CustomType {
|
||||||
|
};
|
||||||
|
var Decr = class extends CustomType {
|
||||||
|
};
|
||||||
|
function init2(_) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
function update2(model, msg) {
|
||||||
|
if (msg instanceof Incr) {
|
||||||
|
return model + 1;
|
||||||
|
} else {
|
||||||
|
return model - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function view(model) {
|
||||||
|
let count = to_string(model);
|
||||||
|
return div(
|
||||||
|
toList([]),
|
||||||
|
toList([
|
||||||
|
button(toList([on_click(new Incr())]), toList([text(" + ")])),
|
||||||
|
p(toList([]), toList([text(count)])),
|
||||||
|
button(toList([on_click(new Decr())]), toList([text(" - ")]))
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
function main() {
|
||||||
|
let app = simple(init2, update2, view);
|
||||||
|
let $ = start3(app, "#app", void 0);
|
||||||
|
if (!$.isOk()) {
|
||||||
|
throw makeError(
|
||||||
|
"assignment_no_match",
|
||||||
|
"aurinko_frontend",
|
||||||
|
9,
|
||||||
|
"main",
|
||||||
|
"Assignment pattern did not match",
|
||||||
|
{ value: $ }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return void 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// build/.lustre/entry.mjs
|
||||||
|
main();
|
1
frontend/priv/static/lustre-ui.css
Symbolic link
1
frontend/priv/static/lustre-ui.css
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../../build/packages/lustre_ui/priv/static/lustre-ui.css
|
38
frontend/src/aurinko_frontend.gleam
Normal file
38
frontend/src/aurinko_frontend.gleam
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import gleam/int
|
||||||
|
import lustre
|
||||||
|
import lustre/element.{text}
|
||||||
|
import lustre/element/html.{button, div, p}
|
||||||
|
import lustre/event.{on_click}
|
||||||
|
|
||||||
|
pub fn main() {
|
||||||
|
let app = lustre.simple(init, update, view)
|
||||||
|
let assert Ok(_) = lustre.start(app, "#app", Nil)
|
||||||
|
|
||||||
|
Nil
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(_flags) {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
type Msg {
|
||||||
|
Incr
|
||||||
|
Decr
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(model, msg) {
|
||||||
|
case msg {
|
||||||
|
Incr -> model + 1
|
||||||
|
Decr -> model - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(model) {
|
||||||
|
let count = int.to_string(model)
|
||||||
|
|
||||||
|
div([], [
|
||||||
|
button([on_click(Incr)], [text(" + ")]),
|
||||||
|
p([], [text(count)]),
|
||||||
|
button([on_click(Decr)], [text(" - ")]),
|
||||||
|
])
|
||||||
|
}
|
7
frontend/tailwind.config.js
Normal file
7
frontend/tailwind.config.js
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
module.exports = {
|
||||||
|
content: ["./index.html", "./src/**/*.{gleam,mjs}"],
|
||||||
|
theme: {
|
||||||
|
extend: {},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
};
|
Loading…
Reference in a new issue