Compare commits
414 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9490a06303 | |||
| 5fb1ae0678 | |||
| 97c03e216b | |||
|
|
bab4490847 | ||
|
8e824d4afa
|
|||
|
|
777cf1a31d | ||
|
efacb2a332
|
|||
|
|
17c4e9dd22 | ||
| 503ccbb2ad | |||
|
54c40d7acc
|
|||
|
d8d5e076ba
|
|||
|
|
fd2b3fe691 | ||
| 6ae7a7dac8 | |||
|
e429a855e5
|
|||
|
|
9ed60b7cc8 | ||
|
|
175df0ce33 | ||
|
|
d5cde19250 | ||
| 2e1165d99c | |||
| a100ffa77b | |||
| 95c3608d85 | |||
| 15d91d87bf | |||
| f6ec692ebf | |||
| f41617c08e | |||
| 2bf0d82a5b | |||
| 3e61cfb939 | |||
| 21ec3a04ab | |||
| 5d6fe5572b | |||
| 0a543c7b21 | |||
| b1ba2effe3 | |||
| 626ce34dc0 | |||
| b879555e6a | |||
| ed08980df3 | |||
| 6aee2bbc60 | |||
| d4701c958c | |||
| 446d4f4171 | |||
| 1ed2a15c4c | |||
| 42b746871a | |||
| 6c20b01cc2 | |||
| 4f879252a0 | |||
|
|
11724987b0 | ||
| d90703453f | |||
|
eaea4b2e21
|
|||
|
|
68efc76e8e | ||
| 6df17c88c7 | |||
|
ec109328fb
|
|||
|
|
f4943a148b | ||
|
15e348c17b
|
|||
|
6cf5262dd5
|
|||
|
|
4b229cd7d7 | ||
| e372be192a | |||
|
ab37e88bb0
|
|||
|
|
d38a784326 | ||
| 4d18b105c8 | |||
|
492651e0f3
|
|||
|
cd678a41f6
|
|||
| 03fbc14b72 | |||
|
d86db7a66c
|
|||
|
7182f7c9f0
|
|||
|
eac23e7d1a
|
|||
|
b500fdb211
|
|||
|
180aba4fa5
|
|||
|
|
d9d7221e90 | ||
| b3fe6f8b70 | |||
| 4792720d74 | |||
| 46e86eb5f9 | |||
| 9a890f30fc | |||
| 66c44976d8 | |||
| 608caeeda2 | |||
| 55bcaaf963 | |||
|
|
ce10ea93db | ||
|
e513d87d24
|
|||
|
|
08061bc6ce | ||
| dd55a0c9df | |||
| 89f0f768e3 | |||
|
5315a05fa2
|
|||
| 57b28daf4e | |||
|
|
0ba060d78c | ||
|
|
b2077ae317 | ||
|
4cd3673d15
|
|||
|
|
771712ad9a | ||
| 1d6941ecc6 | |||
|
56d34767d7
|
|||
| 5fbd914e24 | |||
| 5f193c559f | |||
| a998483d2c | |||
|
|
a4159f0fff | ||
| 866f3a317b | |||
|
4eac05cbb7
|
|||
|
efb3292d9f
|
|||
|
6e822bd5d1
|
|||
|
d8bf174d3a
|
|||
|
2f7be7b051
|
|||
| 18bb207e4a | |||
|
c914f4a477
|
|||
|
2da1be0c6b
|
|||
|
eb00b8c19d
|
|||
|
09a9e47348
|
|||
|
156ae2315a
|
|||
|
|
cf6bedbd9b | ||
| 36aa90519e | |||
|
14c1a57331
|
|||
| d7f0630693 | |||
| 3b7149f161 | |||
| 5545e90160 | |||
| da5b38d1ea | |||
|
65928c4064
|
|||
|
|
033b61dd4f | ||
| 5725d43b11 | |||
|
a8a187a412
|
|||
|
|
f30e16b15e | ||
| bd745042df | |||
|
70878e1423
|
|||
|
937b2c367b
|
|||
| faa0a8533e | |||
|
1cb9d455db
|
|||
|
|
453c9d234c | ||
| 369127e081 | |||
|
|
563faa6c0b | ||
|
|
17163ab002 | ||
|
|
6293e9e67a | ||
|
|
a68ef32614 | ||
| 4de10614be | |||
|
|
2887e6a909 | ||
|
|
e75ffc41e5 | ||
|
|
a620c26812 | ||
|
|
f2bb57b50d | ||
| 8e0cb2105a | |||
|
|
048e80356b | ||
|
|
a47fb89143 | ||
|
|
1b8167c66e | ||
|
|
48d46eda62 | ||
|
|
36307c822e | ||
|
|
dca32db800 | ||
|
|
a153238f79 | ||
|
|
c1fa85fd1b | ||
|
|
69b380e665 | ||
|
|
afc888ab60 | ||
|
|
9a4ef08060 | ||
|
f68b7f68c8
|
|||
|
|
d8143a6b8d | ||
|
e04d36ca12
|
|||
|
|
275ec44a97 | ||
|
fff7913cd5
|
|||
|
|
23ba2efe96 | ||
|
759bbc6f60
|
|||
|
3140c07ad0
|
|||
|
b109dbdcbd
|
|||
|
c1be7c468d
|
|||
|
|
d3797115f7 | ||
|
6d04af6230
|
|||
|
7cf50641f9
|
|||
|
dd398bd96b
|
|||
|
|
63d782ade4 | ||
| e3a1f56b87 | |||
|
|
2f18948cce | ||
|
|
4786850431 | ||
|
|
7584bf661f | ||
|
|
f23b3f1821 | ||
|
|
5bb2ffd67c | ||
| c3c9e8e4e2 | |||
| b03908f93e | |||
| c2e7762df8 | |||
|
|
c83beadd45 | ||
| 46d6b1277b | |||
|
|
10a8c42319
|
||
| 9ed4165ebc | |||
|
|
8cc840adc5 | ||
|
|
79c6e2abd0 | ||
|
|
3093757454 | ||
|
028945bfca
|
|||
|
|
09556bc5df | ||
|
|
94b7f25852 | ||
|
|
2831c8a5cb | ||
|
|
1fed7adf80 | ||
|
|
a9145f6f79 | ||
|
|
ccbe07619f | ||
| 95e6096fbb | |||
|
|
65c29879ab | ||
| b794fc3b68 | |||
| df7be8d894 | |||
|
a474e7cbd4
|
|||
| 5aa83c4bf2 | |||
| 210a04c24d | |||
| 6a35b374c2 | |||
|
785d0d57ae
|
|||
| 7e376f3609 | |||
| ae233cb764 | |||
| 0b9751a97b | |||
| c6c5659b2c | |||
| e1d67df304 | |||
| 7b00f80ac9 | |||
| 92be7a0201 | |||
| 7700924d0e | |||
| 6620c44202 | |||
|
ca5c6791d3
|
|||
|
|
d80797bb17 | ||
|
|
4d9e78cd69 | ||
|
|
4980a41a44
|
||
|
|
d5047b0189 | ||
|
|
8c17a644d5 | ||
|
|
c77ce73a69 | ||
|
|
020a2f2e00 | ||
|
847270877a
|
|||
| d4dd7945cb | |||
| 4626333c74 | |||
| d5341acd28 | |||
| 3c2da99235 | |||
| 013b5fea91 | |||
| 7ea657b582 | |||
| d1e416c850 | |||
| 232c095954 | |||
|
|
f5a3920eea | ||
|
|
51cb94a2e0 | ||
|
|
e0b5522b8f | ||
|
|
19827701aa | ||
| 48dcee7d7f | |||
| 02b6b36f95 | |||
| 8f38f19dd0 | |||
| 04bde9e221 | |||
| 3d948d3ba9 | |||
| 67c23b8707 | |||
| fbe0e59175 | |||
| 1f86f950d6 | |||
| ffce84bb37 | |||
| 2308d50310 | |||
| 1f5dd53673 | |||
|
f8940c9220
|
|||
| 9566bce0bd | |||
|
|
2f9b292ee4 | ||
|
54d8c3fe09
|
|||
| ddd84b2af3 | |||
|
f8ab41bbda
|
|||
| 2f4a458964 | |||
|
|
799fbe67e5 | ||
| 33d94c5d94 | |||
|
|
beedeb836b | ||
| d4766f249b | |||
|
|
fc41c91d41 | ||
| 679ab2f945 | |||
| 13b257f7ff | |||
| bfc25a2894 | |||
| 9f112aedd8 | |||
|
|
61b4323803 | ||
|
|
271f40cdbc | ||
|
|
2bf628103c | ||
|
|
5be346e0fc | ||
|
|
d8f38c496d | ||
|
|
917058b7be | ||
|
|
1b4f4ef360 | ||
|
|
da6a1995c1 | ||
| b030e8eeb7 | |||
| 6c8bb8b95a | |||
|
|
a9c6ebc0de | ||
| b50fe38a06 | |||
|
a627b08df2
|
|||
|
b062f66cf9
|
|||
| 99c0279a1f | |||
| ba3906da71 | |||
|
|
db87ef5df1 | ||
|
|
dacc30da0d | ||
|
|
6d5a72412b | ||
|
|
748ba286ab | ||
|
|
49d50b4bb1 | ||
|
|
039df744d3 | ||
|
|
3a9c616f6b | ||
| 570a2237f1 | |||
| 50b5524c13 | |||
| b2c475101c | |||
|
|
187a5e36e3 | ||
| cde779ea00 | |||
| 8106bb9380 | |||
| df56303949 | |||
| 79238c2a8f | |||
|
|
637d22e0f0 | ||
| ac82e2c7ba | |||
| 5b429bd94f | |||
|
754477b679
|
|||
| c9bed6623c | |||
|
993e5680bb
|
|||
| cf8ec277cb | |||
|
|
9b6a3ef133 | ||
|
|
2476d9f83c | ||
|
|
e9c21c1292 | ||
|
|
c6eee07317 | ||
| aaa56271b0 | |||
| 4280431d0f | |||
|
|
e85a114a23 | ||
|
|
1d1ddf2fef | ||
|
|
0bd66f89fd | ||
|
|
e886589332 | ||
|
|
a7cb34d7ba | ||
|
|
1edf1c2e55 | ||
|
|
234cd19af7 | ||
|
|
40414db1f6 | ||
|
|
f160e341ce | ||
|
5a4ef2bd70
|
|||
|
0473f7b765
|
|||
|
|
7fb92d18c7 | ||
|
|
7054ade55d | ||
|
|
0a54ae3dcc | ||
| 6016cda9ef | |||
|
|
4257ec5598 | ||
| 009008e0d7 | |||
|
|
554bb6b184 | ||
|
b5c21bb62c
|
|||
| 3b0de79bd8 | |||
| ed916c1362 | |||
|
|
77c8b95af4 | ||
|
8482921420
|
|||
| d3d1851142 | |||
| 7a93caecb1 | |||
| 7ebaaa419f | |||
| 4850f9d4c3 | |||
| 7075cc50b9 | |||
| 2a1761e5eb | |||
| c6c4eb57a9 | |||
| 6d92b9b929 | |||
| 90a5c83ca1 | |||
| aadb8d6b50 | |||
|
90bab5ecc0
|
|||
| 0cf2b641cd | |||
| 6183d235ac | |||
| 79ae757e7d | |||
| 4782cde597 | |||
| e934101db7 | |||
| dbea593b1c | |||
| 8f0c414775 | |||
| 17181f6ca2 | |||
| 8792637774 | |||
| 506b76fc00 | |||
| edfd519b8e | |||
| 5520ab8d30 | |||
| 8f06be26ef | |||
| a627d509ac | |||
| 1fa65def25 | |||
| d805997470 | |||
| 1e1e429c9a | |||
| 9eed6af711 | |||
| a046e7d849 | |||
| db37040209 | |||
| 217fd54ec4 | |||
| 17c8c12f55 | |||
| 3ff2a0f63c | |||
| edcf95f71e | |||
| aafc0d55bf | |||
| 215409c7c8 | |||
| 59d5557bdd | |||
| 22ea4b29b8 | |||
| 59ce5b98b8 | |||
| 51fc7503b5 | |||
| 143b55634e | |||
| 3ba59a39bf | |||
| 0dbd11ed68 | |||
| f7fbc5d70d | |||
| 7e3a520dad | |||
| 9a26a258b6 | |||
| 94432247b3 | |||
| 86425a2292 | |||
| 9bd0562b47 | |||
| 1a7777acd1 | |||
| 214fb650c1 | |||
| e9b3c4adba | |||
| fa63ade2e1 | |||
| eab1b42f76 | |||
| c5b85e9ad4 | |||
| 79daa83aca | |||
| 761946660c | |||
| 723064ae52 | |||
| 7db499b1c7 | |||
| c28664b9ee | |||
| f250e8bf16 | |||
| b6818cd7fa | |||
| 3055b8d489 | |||
| 819964ec87 | |||
| 0898faec4d | |||
| 3b4ec021af | |||
| 3ddd22edd1 | |||
| cd4431db1b | |||
| b7ba8eb771 | |||
| f74aacf53b | |||
| cacff8d4df | |||
| b028e54c52 | |||
| 34774e57de | |||
| 9181b43732 | |||
| 1e12fa52aa | |||
| 7176919e94 | |||
| 5ee01e58d8 | |||
| ce495ebcb8 | |||
| b91a528108 | |||
| 333ddd0ba3 | |||
| 291c077536 | |||
| 362718dd9c | |||
| 7e12df8cc0 | |||
| c862cb172f | |||
| 6cdafc22b4 | |||
| 750d1a0803 | |||
| ff7ce98672 | |||
|
|
03eab47d23 | ||
| 1b12faa61a | |||
|
9388d3758d
|
|||
|
7f6ad2b853
|
|||
|
|
4467957b8f | ||
| cafe1204ef | |||
|
16f968657b
|
|||
|
|
8274bb982a | ||
| b3da5b2a96 | |||
|
|
b293044258 | ||
|
|
4db102157f | ||
| 16a4ccbc6b | |||
|
|
407a278e28 | ||
|
|
8134999600 | ||
|
|
f29aafe943 | ||
|
d64ee45d9e
|
|||
|
48b5da9da3
|
@@ -1,37 +0,0 @@
|
||||
open Fake.Core
|
||||
open Fake.IO
|
||||
open Farmer
|
||||
open Farmer.Builders
|
||||
|
||||
open Helpers
|
||||
|
||||
initializeContext()
|
||||
|
||||
let packPath = Path.getFullName "packages"
|
||||
|
||||
Target.create "Clean" (fun _ -> Shell.cleanDir packPath)
|
||||
|
||||
Target.create "InstallClient" (fun _ ->
|
||||
run dotnet "tool restore" "."
|
||||
)
|
||||
|
||||
Target.create "Run" ignore
|
||||
|
||||
Target.create "Format" (fun _ ->
|
||||
run dotnet "fantomas . -r" "src"
|
||||
)
|
||||
|
||||
open Fake.Core.TargetOperators
|
||||
|
||||
let dependencies = [
|
||||
"Clean"
|
||||
==> "InstallClient"
|
||||
|
||||
"Run"
|
||||
==> "InstallClient"
|
||||
|
||||
"Format"
|
||||
]
|
||||
|
||||
[<EntryPoint>]
|
||||
let main args = runOrDefault args
|
||||
@@ -1,129 +0,0 @@
|
||||
module Helpers
|
||||
|
||||
open Fake.Core
|
||||
|
||||
let initializeContext () =
|
||||
let execContext = Context.FakeExecutionContext.Create false "build.fsx" [ ]
|
||||
Context.setExecutionContext (Context.RuntimeContext.Fake execContext)
|
||||
|
||||
module Proc =
|
||||
module Parallel =
|
||||
open System
|
||||
|
||||
let locker = obj()
|
||||
|
||||
let colors =
|
||||
[| ConsoleColor.Blue
|
||||
ConsoleColor.Yellow
|
||||
ConsoleColor.Magenta
|
||||
ConsoleColor.Cyan
|
||||
ConsoleColor.DarkBlue
|
||||
ConsoleColor.DarkYellow
|
||||
ConsoleColor.DarkMagenta
|
||||
ConsoleColor.DarkCyan |]
|
||||
|
||||
let print color (colored: string) (line: string) =
|
||||
lock locker
|
||||
(fun () ->
|
||||
let currentColor = Console.ForegroundColor
|
||||
Console.ForegroundColor <- color
|
||||
Console.Write colored
|
||||
Console.ForegroundColor <- currentColor
|
||||
Console.WriteLine line)
|
||||
|
||||
let onStdout index name (line: string) =
|
||||
let color = colors.[index % colors.Length]
|
||||
if isNull line then
|
||||
print color $"{name}: --- END ---" ""
|
||||
else if String.isNotNullOrEmpty line then
|
||||
print color $"{name}: " line
|
||||
|
||||
let onStderr name (line: string) =
|
||||
let color = ConsoleColor.Red
|
||||
if isNull line |> not then
|
||||
print color $"{name}: " line
|
||||
|
||||
let redirect (index, (name, createProcess)) =
|
||||
createProcess
|
||||
|> CreateProcess.redirectOutputIfNotRedirected
|
||||
|> CreateProcess.withOutputEvents (onStdout index name) (onStderr name)
|
||||
|
||||
let printStarting indexed =
|
||||
for (index, (name, c: CreateProcess<_>)) in indexed do
|
||||
let color = colors.[index % colors.Length]
|
||||
let wd =
|
||||
c.WorkingDirectory
|
||||
|> Option.defaultValue ""
|
||||
let exe = c.Command.Executable
|
||||
let args = c.Command.Arguments.ToStartInfo
|
||||
print color $"{name}: {wd}> {exe} {args}" ""
|
||||
|
||||
let run cs =
|
||||
cs
|
||||
|> Seq.toArray
|
||||
|> Array.indexed
|
||||
|> fun x -> printStarting x; x
|
||||
|> Array.map redirect
|
||||
|> Array.Parallel.map Proc.run
|
||||
|
||||
let createProcess exe arg dir =
|
||||
CreateProcess.fromRawCommandLine exe arg
|
||||
|> CreateProcess.withWorkingDirectory dir
|
||||
|> CreateProcess.ensureExitCode
|
||||
|
||||
let dotnet = createProcess "dotnet"
|
||||
|
||||
// NOTE: Uses dotnet-tools from nixpkgs
|
||||
let fable = createProcess "fable"
|
||||
let fantomas = createProcess "fantomas"
|
||||
|
||||
let bun =
|
||||
let bunPath =
|
||||
match ProcessUtils.tryFindFileOnPath "bun" with
|
||||
| Some path -> path
|
||||
| None ->
|
||||
"bun was not found in path. Please install it and make sure it's available from your path. " +
|
||||
"See https://safe-stack.github.io/docs/quickstart/#install-pre-requisites for more info"
|
||||
|> failwith
|
||||
|
||||
createProcess bunPath
|
||||
|
||||
let bunx = createProcess "bunx"
|
||||
|
||||
type BundleMode =
|
||||
| Prod
|
||||
| Devel
|
||||
| Watch
|
||||
with
|
||||
override this.ToString() =
|
||||
match this with
|
||||
| Prod -> "production"
|
||||
| Devel -> "development"
|
||||
| Watch -> "watch"
|
||||
|
||||
let viteCmd (m: BundleMode) outDir =
|
||||
match m with
|
||||
| Prod -> $"vite build -c ../../vite.config.js -m {m} --emptyOutDir --outDir {outDir}/public"
|
||||
| Devel -> $"vite build -c ../../vite.config.js -m {m} --minify false --sourcemap true --emptyOutDir --outDir {outDir}/public"
|
||||
| Watch -> "vite -c ../../vite.config.js"
|
||||
|
||||
let run proc arg dir =
|
||||
proc arg dir
|
||||
|> Proc.run
|
||||
|> ignore
|
||||
|
||||
let runParallel processes =
|
||||
processes
|
||||
|> Proc.Parallel.run
|
||||
|> ignore
|
||||
|
||||
let runOrDefault args =
|
||||
try
|
||||
match args with
|
||||
| [| target |] -> Target.runOrDefault target
|
||||
| _ ->
|
||||
Target.runOrDefault "Run"
|
||||
0
|
||||
with e ->
|
||||
printfn "%A" e
|
||||
1
|
||||
@@ -9,8 +9,23 @@ insert_final_newline = false
|
||||
|
||||
[*.js]
|
||||
indent_size = 2
|
||||
max_line_length = 80
|
||||
|
||||
[*.scss]
|
||||
indent_size = 2
|
||||
max_line_length = 80
|
||||
|
||||
[*.nix]
|
||||
indent_size = 2
|
||||
max_line_length= 80
|
||||
|
||||
[*.scss]
|
||||
indent_size = 2
|
||||
max_line_length = 80
|
||||
|
||||
[*.yaml]
|
||||
indent_size = 2
|
||||
|
||||
[*.fs]
|
||||
max_line_length= 120
|
||||
|
||||
|
||||
13
.envrc
13
.envrc
@@ -1,17 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
export NPINS_DIRECTORY="nix"
|
||||
export APP_ENV=$USER
|
||||
|
||||
# the shebang is ignored, but nice for editors
|
||||
watch_file lon.lock
|
||||
watch_file nix/sources.json
|
||||
|
||||
# Load .env file if it exists
|
||||
dotenv_if_exists
|
||||
|
||||
# Activate development shell
|
||||
if type -P lorri &>/dev/null; then
|
||||
eval "$(lorri direnv)"
|
||||
else
|
||||
echo 'while direnv evaluated .envrc, could not find the command "lorri" [https://github.com/nix-community/lorri]'
|
||||
use nix
|
||||
fi
|
||||
use nix
|
||||
|
||||
# HACK: Workaround for direnv bug
|
||||
unset TMP TMPDIR TEMP TEMPDIR
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -30,7 +30,6 @@ _*.yaml
|
||||
tilt/base/_manifest.yaml
|
||||
NuGet.Config
|
||||
sync.list
|
||||
package-lock.json
|
||||
*.nupkg
|
||||
*.fable-temp*
|
||||
.env
|
||||
.env
|
||||
@@ -1,38 +1,51 @@
|
||||
# yaml-language-server: $schema=https://gitlab.com/gitlab-org/gitlab/-/raw/master/app/assets/javascripts/editor/schema/ci.json
|
||||
variables:
|
||||
SDK_VERSION: 9.0
|
||||
SKIP_TESTS: "true"
|
||||
SKIP_SINGULARITY: "true"
|
||||
SKIP_TESTS: "true"
|
||||
|
||||
default:
|
||||
tags:
|
||||
- nix
|
||||
|
||||
include:
|
||||
- project: oceanbox/gitlab-ci
|
||||
ref: v4.2
|
||||
file: template/Base.gitlab-ci.yml
|
||||
- local: '/src/Atlantis/.gitlab-ci.yml'
|
||||
rules:
|
||||
- changes:
|
||||
- 'src/Atlantis/**/*'
|
||||
- 'nix/packages/atlantis.nix'
|
||||
- 'nix/packages/atlantis-client.nix'
|
||||
- 'nix/containers.nix'
|
||||
- local: '/src/Sorcerer/.gitlab-ci.yml'
|
||||
rules:
|
||||
- changes:
|
||||
- 'src/Sorcerer/**/*'
|
||||
- 'nix/packages/sorcerer.nix'
|
||||
- 'nix/containers.nix'
|
||||
- local: '/src/Interfaces/.gitlab-ci.yml'
|
||||
rules:
|
||||
- changes:
|
||||
- 'src/Interfaces/**/*'
|
||||
- 'nix/packages/api.nix'
|
||||
- local: '/src/DataAgent/.gitlab-ci.yml'
|
||||
rules:
|
||||
- changes:
|
||||
- 'src/DataAgent/**/*'
|
||||
- 'nix/packages/dataagent.nix'
|
||||
- local: '/src/ServerPack/.gitlab-ci.yml'
|
||||
rules:
|
||||
- changes:
|
||||
- 'src/ServerPack/**/*'
|
||||
- 'nix/packages/serverpack.nix'
|
||||
- project: oceanbox/gitlab-ci
|
||||
ref: v4.5
|
||||
file: template/Base.gitlab-ci.yml
|
||||
- local: "/src/Atlantis/.gitlab-ci.yml"
|
||||
rules:
|
||||
- changes:
|
||||
- "src/Atlantis/**/*"
|
||||
- "nix/packages/atlantis.nix"
|
||||
- "nix/packages/atlantis-client.nix"
|
||||
- "nix/containers.nix"
|
||||
- local: "/src/Sorcerer/.gitlab-ci.yml"
|
||||
rules:
|
||||
- changes:
|
||||
- "src/Sorcerer/**/*"
|
||||
- "nix/packages/sorcerer.nix"
|
||||
- "nix/containers.nix"
|
||||
- local: "/src/Archivist/.gitlab-ci.yml"
|
||||
rules:
|
||||
- changes:
|
||||
- "src/Archivist/**/*"
|
||||
- "nix/packages/archivist.nix"
|
||||
- local: "/src/Interfaces/.gitlab-ci.yml"
|
||||
rules:
|
||||
- changes:
|
||||
- "src/Interfaces/**/*"
|
||||
- "nix/packages/api.nix"
|
||||
- local: "/src/DataAgent/.gitlab-ci.yml"
|
||||
rules:
|
||||
- changes:
|
||||
- "src/DataAgent/**/*"
|
||||
- "nix/packages/dataagent.nix"
|
||||
- local: "/src/ServerPack/.gitlab-ci.yml"
|
||||
rules:
|
||||
- changes:
|
||||
- "src/ServerPack/**/*"
|
||||
- "nix/packages/serverpack.nix"
|
||||
- local: "/src/Codex/.gitlab-ci.yml"
|
||||
rules:
|
||||
- changes:
|
||||
- "src/Codex/**/*"
|
||||
- "nix/packages/node-modules.nix"
|
||||
- "nix/packages/sources.nix"
|
||||
|
||||
@@ -15,7 +15,7 @@ plugins:
|
||||
- "src/Atlantis/src/**.fsproj"
|
||||
- "src/Sorcerer/src/**.fsproj"
|
||||
- "src/DataAgent/src/**.fsproj"
|
||||
- "src/ServerPack/src/*.fsproj"
|
||||
- "src/ServerPack/src/**.fsproj"
|
||||
- "src/Interfaces/**.fsproj"
|
||||
- - '@semantic-release/exec'
|
||||
- generateNotesCmd: "echo ${nextRelease.version} > VERSION"
|
||||
@@ -29,7 +29,7 @@ plugins:
|
||||
- "src/Atlantis/src/**.fsproj"
|
||||
- "src/Sorcerer/src/**.fsproj"
|
||||
- "src/DataAgent/src/**.fsproj"
|
||||
- "src/ServerPack/src/*.fsproj"
|
||||
- "src/ServerPack/src/**.fsproj"
|
||||
- "src/Interfaces/**.fsproj"
|
||||
|
||||
analyzeCommits:
|
||||
|
||||
17
Build.fsproj
17
Build.fsproj
@@ -1,17 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include=".build/Helpers.fs" />
|
||||
<Compile Include=".build/Build.fs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Fake.Core.Target" Version="6.1.3" />
|
||||
<PackageReference Include="Fake.DotNet.Cli" Version="6.1.3" />
|
||||
<PackageReference Include="Fake.IO.FileSystem" Version="6.1.3" />
|
||||
<PackageReference Include="Farmer" Version="1.9.6" />
|
||||
<PackageReference Update="FSharp.Core" Version="9.0.100" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
35
Directory.Build.props
Normal file
35
Directory.Build.props
Normal file
@@ -0,0 +1,35 @@
|
||||
<Project>
|
||||
<!-- Make F# support Central Package Management -->
|
||||
<PropertyGroup>
|
||||
<DisableImplicitSystemValueTupleReference>true</DisableImplicitSystemValueTupleReference>
|
||||
<DisableImplicitFSharpCoreReference>true</DisableImplicitFSharpCoreReference>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<!-- <DebugType Condition=" '$(DebugType)' == '' ">embedded</DebugType> -->
|
||||
<!-- <DebugType>embedded</DebugType> -->
|
||||
<!-- <Deterministic>true</Deterministic> -->
|
||||
<!-- <NetCoreTargetingPackRoot>[UNDEFINED]</NetCoreTargetingPackRoot> -->
|
||||
|
||||
<!-- Warnings and Errors -->
|
||||
<!--
|
||||
FS0025: Incomplete pattern matches on this expression.
|
||||
FS1182: Unused variables
|
||||
FS1178: Does not support structural equality
|
||||
FS3390: Malformed XML doc comment
|
||||
-->
|
||||
<!-- <WarnOn>FS3388,FS3559</WarnOn> -->
|
||||
<!-- <WarnOn>1182;3390;1178;$(WarnOn)</WarnOn> -->
|
||||
<!-- <TreatWarningsAsErrors>true</TreatWarningsAsErrors> -->
|
||||
<WarningsAsErrors>FS0025</WarningsAsErrors>
|
||||
<NoWarn>NU1603;MSB3277</NoWarn>
|
||||
|
||||
<!-- Restore with Lockfiles -->
|
||||
<!-- https://www.gresearch.co.uk/blog/article/improve-nuget-restores-with-static-graph-evaluation/ -->
|
||||
<RestoreUseStaticGraphEvaluation>true</RestoreUseStaticGraphEvaluation>
|
||||
<DisableImplicitNuGetFallbackFolder>true</DisableImplicitNuGetFallbackFolder>
|
||||
|
||||
<!-- Performance -->
|
||||
<ServerGarbageCollection>true</ServerGarbageCollection>
|
||||
<OtherFlags>$(OtherFlags) --test:GraphBasedChecking --test:ParallelOptimization --test:ParallelIlxGen</OtherFlags>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
109
Directory.Packages.props
Normal file
109
Directory.Packages.props
Normal file
@@ -0,0 +1,109 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<!-- Common -->
|
||||
<PackageVersion Include="FSharp.Core" Version="9.0.303" />
|
||||
<PackageVersion Include="Fargo.CmdLine" Version="1.7.5" />
|
||||
<PackageVersion Include="FSharpPlus" Version="1.7.0" />
|
||||
<PackageVersion Include="FSharp.Data" Version="6.4.1" />
|
||||
<PackageVersion Include="FsToolkit.ErrorHandling" Version="5.0.1" />
|
||||
<PackageVersion Include="Thoth.Json.Giraffe" Version="6.0.0" />
|
||||
<PackageVersion Include="Thoth.Json.Net" Version="12.0.0" />
|
||||
<PackageVersion Include="Serilog" Version="4.2.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.Console" Version="6.0.0"/>
|
||||
<PackageVersion Include="Drifters.Api" Version="6.22.0" />
|
||||
<!-- Client -->
|
||||
<PackageVersion Include="Fable.Browser.IndexedDB" Version="2.2.0" />
|
||||
<PackageVersion Include="Fable.Browser.ResizeObserver" Version="1.0.0" />
|
||||
<PackageVersion Include="Fable.Browser.WebGL" Version="1.3.0" />
|
||||
<PackageVersion Include="Fable.Core" Version="4.4.0"/>
|
||||
<PackageVersion Include="Fable.Elmish" Version="4.2.0" />
|
||||
<PackageVersion Include="Fable.Fetch" Version="2.7.0" />
|
||||
<PackageVersion Include="Fable.FontAwesome.Free" Version="3.0.0"/>
|
||||
<PackageVersion Include="Fable.Lit.Elmish" Version="1.6.2-oceanbox" />
|
||||
<PackageVersion Include="Fable.Lit.React" Version="1.6.2-oceanbox" />
|
||||
<PackageVersion Include="Fable.Lit" Version="1.6.2-oceanbox" />
|
||||
<PackageVersion Include="Fable.OpenLayers" Version="2.19.0" />
|
||||
<PackageVersion Include="Fable.Promise" Version="3.2.0" />
|
||||
<PackageVersion Include="Fable.React" Version="9.4.0" />
|
||||
<PackageVersion Include="Fable.Remoting.Client" Version="7.32.0" />
|
||||
<PackageVersion Include="Fable.Remoting.MsgPack" Version="1.24.0" />
|
||||
<PackageVersion Include="Fable.SignalR.Elmish" Version="2.1.0" />
|
||||
<PackageVersion Include="Fable.SimpleHttp" Version="3.6.0" />
|
||||
<PackageVersion Include="Feliz.Router" Version="4.0.0"/>
|
||||
<PackageVersion Include="Feliz" Version="2.9.0" />
|
||||
<PackageVersion Include="Feliz.UseElmish" Version="2.5.0" />
|
||||
<PackageVersion Include="Feliz.CompilerPlugins" Version="2.2.0" />
|
||||
<PackageVersion Include="Matplotlib.ColorMaps" Version="3.0.1" />
|
||||
<PackageVersion Include="Thoth.Fetch" Version="3.0.1" />
|
||||
<PackageVersion Include="Thoth.Json" Version="10.4.1"/>
|
||||
<PackageVersion Include="FS.FluentUI" Version="3.0.0"/>
|
||||
<!-- Serverpack -->
|
||||
<PackageVersion Include="OpenFga.Sdk" Version="0.7.0"/>
|
||||
<PackageVersion Include="FSharp.SystemTextJson" Version="1.3.13"/>
|
||||
<PackageVersion Include="Saturn.OpenTelemetry" Version="0.6.0-alpha"/>
|
||||
<!-- Atlantis -->
|
||||
<PackageVersion Include="Argu" Version="6.2.5" />
|
||||
<PackageVersion Include="AspNetCore.Serilog.RequestLoggingMiddleware" Version="1.0.2" />
|
||||
<PackageVersion Include="Azure.Extensions.AspNetCore.DataProtection.Blobs" Version="1.5.0" />
|
||||
<PackageVersion Include="Azure.Extensions.AspNetCore.DataProtection.Keys" Version="1.4.0" />
|
||||
<PackageVersion Include="Azure.Identity" Version="1.13.2" />
|
||||
<PackageVersion Include="Azure.Security.KeyVault.Secrets" Version="4.7.0" />
|
||||
<PackageVersion Include="Dapr.Actors" Version="1.16.0" />
|
||||
<PackageVersion Include="Dapr.Actors.AspNetCore" Version="1.16.0" />
|
||||
<PackageVersion Include="Dapr.AspNetCore" Version="1.16.0" />
|
||||
<PackageVersion Include="Dapr.Client" Version="1.16.0" />
|
||||
<PackageVersion Include="Fable.Remoting.DotnetClient" Version="3.35.0" />
|
||||
<PackageVersion Include="Fable.Remoting.Giraffe" Version="5.21.0" />
|
||||
<PackageVersion Include="Fable.Remoting.Server" Version="5.39.0" />
|
||||
<PackageVersion Include="Fable.SignalR.Saturn" Version="2.1.0" />
|
||||
<PackageVersion Include="Giraffe" Version="7.0.2" />
|
||||
<PackageVersion Include="IdentityModel.AspNetCore" Version="4.3.0" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.2" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="9.0.2" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.DataProtection.StackExchangeRedis" Version="9.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Http.Polly" Version="9.0.2" />
|
||||
<PackageVersion Include="Oceanbox.FvcomKit" Version="5.13.0" />
|
||||
<PackageVersion Include="prometheus-net.AspNetCore" Version="8.2.1" />
|
||||
<PackageVersion Include="Saturn" Version="0.17.0" />
|
||||
<PackageVersion Include="SecurityCodeScan" Version="3.5.4">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageVersion>
|
||||
<PackageVersion Include="Sentry.AspNetCore" Version="5.11.0" />
|
||||
<PackageVersion Include="Serilog.AspNetCore" Version="9.0.0" />
|
||||
<PackageVersion Include="Serilog.Enrichers.CorrelationId" Version="3.0.1" />
|
||||
<PackageVersion Include="Serilog.Expressions" Version="5.0.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.OpenTelemetry" Version="4.1.1" />
|
||||
<PackageVersion Include="System.Text.Encodings.Web" Version="9.0.2" />
|
||||
<!-- Sorcerer -->
|
||||
<PackageVersion Include="MessagePack" Version="3.1.3" />
|
||||
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageVersion Include="ProjNet.FSharp" Version="5.2.0" />
|
||||
<!-- Dapperizer -->
|
||||
<PackageVersion Include="Oceanbox.SDSLite" Version="2.8.0" />
|
||||
<PackageVersion Include="Dapper.FSharp" Version="4.9.0"/>
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="9.0.2"/>
|
||||
<PackageVersion Include="NetTopologySuite" Version="2.5.0"/>
|
||||
<PackageVersion Include="Npgsql" Version="9.0.2" />
|
||||
<PackageVersion Include="Npgsql.NetTopologySuite" Version="9.0.2"/>
|
||||
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.2"/>
|
||||
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL.NetTopologySuite" Version="9.0.2"/>
|
||||
<!-- Entity -->
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="9.0.1"/>
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.1">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageVersion>
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.1">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageVersion>
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.1" />
|
||||
<!-- Analyzers -->
|
||||
<PackageVersion Include="G-Research.FSharp.Analyzers" Version="0.19.0" />
|
||||
<PackageVersion Include="Ionide.Analyzers" Version="0.14.9" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -24,6 +24,10 @@
|
||||
<Project Path="src/Atlantis/src/Server/Petimeter/Petimeter.fsproj" />
|
||||
<Project Path="src/Atlantis/src/Server/Server.fsproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Codex/">
|
||||
<Project Path="src\Codex\src\Client\Codex.Client.fsproj" />
|
||||
<Project Path="src\Codex\src\Server\Codex.Server.fsproj" />
|
||||
</Folder>
|
||||
<Folder Name="/DataAgent/">
|
||||
<Project Path="src/DataAgent/src/Entity/Entity.csproj" />
|
||||
</Folder>
|
||||
@@ -31,6 +35,7 @@
|
||||
<Project Path="src/DataAgent/src/DataAgent/Oceanbox.DataAgent.fsproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Interfaces/">
|
||||
<Project Path="src/Interfaces/Api/Poseidon.Api.fsproj" />
|
||||
<Project Path="src/Interfaces/Archmaester/Archmaester.Api.fsproj" />
|
||||
<Project Path="src/Interfaces/Atlantis/Atlantis.Api.fsproj" />
|
||||
<Project Path="src/Interfaces/Hipster/Hipster.Api.fsproj" />
|
||||
@@ -46,7 +51,6 @@
|
||||
<File Path="global.json" />
|
||||
</Folder>
|
||||
<Folder Name="/Sorcerer/">
|
||||
<Project Path="src/Sorcerer/src/Client/Client.fsproj" />
|
||||
<Project Path="src/Sorcerer/src/Server/Sorcerer.fsproj" />
|
||||
</Folder>
|
||||
</Solution>
|
||||
91
README.md
91
README.md
@@ -12,9 +12,10 @@ Oceanbox's comprehensive platform for oceanic data visualization, analysis, and
|
||||
## Bootstrapping Guide
|
||||
|
||||
To bootstrap Atlantis for development, build and run it using Tilt.
|
||||
|
||||
Some setup is required:
|
||||
|
||||
### k8s access
|
||||
### Kubernetes Access
|
||||
|
||||
To run our application on the kubernetes cluster, Tilt needs access.
|
||||
You need to authenticate with `oidc`, using your microsoft account.
|
||||
@@ -45,21 +46,21 @@ Next, configure the required contexts to use `oidc` (also in your `~/.kube/confi
|
||||
```yaml
|
||||
---
|
||||
- context:
|
||||
cluster: ekman
|
||||
namespace: sorcerer
|
||||
user: oidc
|
||||
cluster: ekman
|
||||
namespace: sorcerer
|
||||
user: oidc
|
||||
name: ekman
|
||||
- context:
|
||||
cluster: oceanbox
|
||||
user: oidc
|
||||
cluster: oceanbox
|
||||
namespace: atlantis
|
||||
user: oidc
|
||||
name: oceanbox
|
||||
---
|
||||
```
|
||||
|
||||
Finally, **you must be granted the necessary priveleges in Entra to access the clusters.**
|
||||
Verify that you have access with `kubectl`:
|
||||
|
||||
```sh
|
||||
```shell
|
||||
kubectl --context oceanbox -n default get pods
|
||||
```
|
||||
|
||||
@@ -68,26 +69,7 @@ kubectl --context oceanbox -n default get pods
|
||||
Required helm manifests are hosted in a separate repository: <https://gitlab.com/oceanbox/manifests>.
|
||||
Clone it into a directory _in the same parent directory as this repository._
|
||||
|
||||
The Bitnami respository must also be added to helm:
|
||||
|
||||
```sh
|
||||
helm repo add bitnami https://charts.bitnami.com/bitnami
|
||||
```
|
||||
|
||||
### DNS
|
||||
|
||||
Some DNS masking is required.
|
||||
Add the following to your NixOS configuration:
|
||||
|
||||
```nix
|
||||
services.dnsmasq = {
|
||||
enable = true;
|
||||
settings.address = [
|
||||
"/.local/127.0.0.1"
|
||||
"/.local.oceanbox.io/127.0.0.1"
|
||||
];
|
||||
};
|
||||
```
|
||||
You'll have to run `helm dependency update` in the atlantis directory within the manifest repo to download the charts.
|
||||
|
||||
### NuGet
|
||||
|
||||
@@ -102,28 +84,45 @@ To retrieve packages from the private Oceanbox nuget registry, configure it with
|
||||
</packageSources>
|
||||
<packageSourceCredentials>
|
||||
<oceanbox>
|
||||
<add key="Username" value="oceanbox-nuget" />
|
||||
<add key="ClearTextPassword" value="<...>" />
|
||||
<add key="Username" value="<Your-GitLab-Username>" />
|
||||
<add key="ClearTextPassword" value="<Your-GitLab-PAT>" />
|
||||
</oceanbox>
|
||||
</packageSourceCredentials>
|
||||
<packageSourceMapping>
|
||||
<packageSource key="nuget.org">
|
||||
<package pattern="*" />
|
||||
</packageSource>
|
||||
<packageSource key="oceanbox">
|
||||
<package pattern="Oceanbox.*" />
|
||||
<package pattern="ProjNet.FSharp" />
|
||||
<package pattern="Drifters.Api" />
|
||||
<package pattern="Fable.Lit" />
|
||||
<package pattern="Fable.Lit.*" />
|
||||
<package pattern="Fable.SignalR" />
|
||||
<package pattern="Fable.SignalR.*" />
|
||||
<package pattern="Fable.OpenLayers" />
|
||||
<package pattern="Matplotlib.*" />
|
||||
</packageSource>
|
||||
</packageSourceMapping>
|
||||
</configuration>
|
||||
```
|
||||
|
||||
Substitute `<...>` for the corresponding secret.
|
||||
Substitute with your own gitlab username and PAT in the credentials.
|
||||
|
||||
Now, we should be able to `restore`:
|
||||
|
||||
```sh
|
||||
dotnet tool restore
|
||||
```shell
|
||||
dotnet restore Poseidon.slnx
|
||||
```
|
||||
|
||||
for `dotnet-tools` we use nix, so entering the shell using `nix-shell` or `direnv` is enough.
|
||||
|
||||
### Mkcert
|
||||
|
||||
To generate certificates correctly, vite needs the `mkcert` binary in a predefined path in our home directory.
|
||||
`mkcert` is included in our dev shell, so we can create a symlink to its location in the nix store:
|
||||
|
||||
```sh
|
||||
```fish
|
||||
which mkcert | xargs -I{} ln -s {} ~/.vite-plugin-mkcert
|
||||
```
|
||||
|
||||
@@ -132,12 +131,16 @@ which mkcert | xargs -I{} ln -s {} ~/.vite-plugin-mkcert
|
||||
### Docker Login
|
||||
|
||||
In order for Tilt to push the images it builds to the oceanbox registry, we must use `docker login` to authenticate with it.
|
||||
First, create a personal access token in your gitlab account.
|
||||
It should have the `read_registry` and `write_registry` scopes set.
|
||||
First, create a personal access token in your gitlab account. It should have the `read_registry` and `write_registry` scopes set.
|
||||
|
||||
```fish
|
||||
set -x TOKEN glpat-xxxx
|
||||
```
|
||||
|
||||
Then, supply it to `docker login`:
|
||||
|
||||
```sh
|
||||
docker login registry.gitlab.com/oceanbox
|
||||
```shell
|
||||
echo "$TOKEN" | docker login gitlab.com -u <user> --password-stdin
|
||||
```
|
||||
|
||||
When prompted, authenticate with your gitlab username and the PAT you just created.
|
||||
@@ -146,7 +149,7 @@ When prompted, authenticate with your gitlab username and the PAT you just creat
|
||||
|
||||
A namespace must be created for your tilt application to live in on the cluster.
|
||||
|
||||
```sh
|
||||
```shell
|
||||
kubectl create ns "$APP_NAMESPACE" --context oceanbox
|
||||
```
|
||||
|
||||
@@ -154,7 +157,7 @@ kubectl create ns "$APP_NAMESPACE" --context oceanbox
|
||||
|
||||
In the project root, run tilt with the following command:
|
||||
|
||||
```sh
|
||||
```shell
|
||||
tilt up --context oceanbox
|
||||
```
|
||||
|
||||
@@ -162,6 +165,9 @@ You should now be able to access the Atlantis client (with HMR) on <atlantis.loc
|
||||
|
||||
### Trust Root Certificate
|
||||
|
||||
> [!note]
|
||||
> You'll need to run `just run-client` in `src/Atlantis` to generate the certificates in `~/.vite-plugin-mkcert/certs`
|
||||
|
||||
In order for your browser to allow you to access the web application, you must add the root certificate generated by `mkcert` to the list of trusted authorities in your browser:
|
||||
|
||||
1. In firefox, navigate to settings and search for _"Certificates"._
|
||||
@@ -171,8 +177,9 @@ In order for your browser to allow you to access the web application, you must a
|
||||
|
||||
### Add `user` to OpenFGA
|
||||
|
||||
Ask [sales](moritz.jorg@oceanbox.io) to add your `azure-ad-user` to OpenFGA.
|
||||
Ask [sales](support@oceanbox.io) to add your `azure-ad-user` to OpenFGA.
|
||||
|
||||
### CORS for Sorcerer
|
||||
|
||||
Add the `url` of your instance to the CORS list of Sorcerer [here](https://gitlab.com/oceanbox/manifests/-/blob/main/values/sorcerer/kustomize/prod/appsettings.json?ref_type=heads#L52).
|
||||
Add the `url` of your instance to the CORS list of Sorcerer
|
||||
[here](https://gitlab.com/oceanbox/manifests/-/blob/main/values/sorcerer/kustomize/prod/appsettings.json?ref_type=heads#L52).
|
||||
479
RELEASE_NOTES.md
479
RELEASE_NOTES.md
@@ -1,5 +1,484 @@
|
||||
# Changelog
|
||||
|
||||
## [1.40.5](https://gitlab.com/oceanbox/Poseidon/compare/v1.40.4...v1.40.5) (2026-01-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **xtractor:** Reduce to 4 cores per task ([8e824d4](https://gitlab.com/oceanbox/Poseidon/commit/8e824d4afa0b03f59e006d3a0d50fb216e71483e))
|
||||
|
||||
## [1.40.4](https://gitlab.com/oceanbox/Poseidon/compare/v1.40.3...v1.40.4) (2026-01-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **xtractor:** Reduce core requirement to 8 ([efacb2a](https://gitlab.com/oceanbox/Poseidon/commit/efacb2a3322de0ced45db4eec240846f4e371a75))
|
||||
|
||||
## [1.40.3](https://gitlab.com/oceanbox/Poseidon/compare/v1.40.2...v1.40.3) (2026-01-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **inbox|xtracto:** Delete/Read msg and allow non-ascii xtractor names ([d8d5e07](https://gitlab.com/oceanbox/Poseidon/commit/d8d5e076baf8b559200f2da91237f9874678b216))
|
||||
* **multiauth:** Add clientId to redirect on signout ([54c40d7](https://gitlab.com/oceanbox/Poseidon/commit/54c40d7accc4bbc43f66dda0df647ccac482a2b0))
|
||||
|
||||
## [1.40.2](https://gitlab.com/oceanbox/Poseidon/compare/v1.40.1...v1.40.2) (2026-01-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **xtract:** Disabled if not allowed to simulate transport ([e429a85](https://gitlab.com/oceanbox/Poseidon/commit/e429a855e5bd00493e2f99647092aebce9c99a2a))
|
||||
|
||||
## [1.40.1](https://gitlab.com/oceanbox/Poseidon/compare/v1.40.0...v1.40.1) (2026-01-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix tilt build on net10 ([d5cde19](https://gitlab.com/oceanbox/Poseidon/commit/d5cde19250847f7b091cfa5f65eb703405c202b6))
|
||||
|
||||
# [1.40.0](https://gitlab.com/oceanbox/Poseidon/compare/v1.39.2...v1.40.0) (2026-01-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Codex:** expose days instead of frames in arcive form ([6cf5262](https://gitlab.com/oceanbox/Poseidon/commit/6cf5262dd5c98517a3c767f410c858fe32c07bd5))
|
||||
* **Codex:** only allow inbounds time intervals on edit archive ([eaea4b2](https://gitlab.com/oceanbox/Poseidon/commit/eaea4b2e215669cec19f2a8cec122ba670a7a202))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **Codex:** edit archives ([ec10932](https://gitlab.com/oceanbox/Poseidon/commit/ec109328fbf5f237a52ef77cbd44dff571deee5f))
|
||||
|
||||
## [1.39.2](https://gitlab.com/oceanbox/Poseidon/compare/v1.39.1...v1.39.2) (2026-01-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix net10 issues ([f4943a1](https://gitlab.com/oceanbox/Poseidon/commit/f4943a148b72fb7e10a745cc3e806b9c4bdd76d8))
|
||||
|
||||
## [1.39.1](https://gitlab.com/oceanbox/Poseidon/compare/v1.39.0...v1.39.1) (2026-01-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **ci:** Remove schedule check ([ab37e88](https://gitlab.com/oceanbox/Poseidon/commit/ab37e88bb0f669a7aa94bf831f95f8c60dc28804))
|
||||
|
||||
# [1.39.0](https://gitlab.com/oceanbox/Poseidon/compare/v1.38.5...v1.39.0) (2026-01-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Codex:** add * user in archmeister on public archives ([cd678a4](https://gitlab.com/oceanbox/Poseidon/commit/cd678a41f64a64c6f3616f32bafeaae6715c08a4))
|
||||
* **Codex:** use feliz router guid matching ([7182f7c](https://gitlab.com/oceanbox/Poseidon/commit/7182f7c9f094d65c884e8e02d4aaa89561ca5e82))
|
||||
* **Codex:** utc start_time ([492651e](https://gitlab.com/oceanbox/Poseidon/commit/492651e0f34035d8c61174aa50336222bcfd979c))
|
||||
* **DataAgent:** use files from parent attribs instead of archive_files ([eac23e7](https://gitlab.com/oceanbox/Poseidon/commit/eac23e7d1a541f2e90374a2add689846d9e7b642))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **Codex:** ability to add FVCOM archives ([d86db7a](https://gitlab.com/oceanbox/Poseidon/commit/d86db7a66ca5ecb6a9ad45ce3d47be3a98d56bb8))
|
||||
|
||||
## [1.38.5](https://gitlab.com/oceanbox/Poseidon/compare/v1.38.4...v1.38.5) (2026-01-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Archmaester:** Rollback add archive if openfga fails ([46e86eb](https://gitlab.com/oceanbox/Poseidon/commit/46e86eb5f961a45fba2da1525c1472bdca79ab47))
|
||||
|
||||
## [1.38.4](https://gitlab.com/oceanbox/Poseidon/compare/v1.38.3...v1.38.4) (2026-01-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **nix:** Bump node deps ([e513d87](https://gitlab.com/oceanbox/Poseidon/commit/e513d87d249843423f0e0a62275afe45e6c73a46))
|
||||
|
||||
## [1.38.3](https://gitlab.com/oceanbox/Poseidon/compare/v1.38.2...v1.38.3) (2026-01-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **xtractor:** Set maxEndDate to 1 year and format ([5315a05](https://gitlab.com/oceanbox/Poseidon/commit/5315a05fa255c6164d3ef73c0f5e20cdb4c632d0))
|
||||
|
||||
## [1.38.2](https://gitlab.com/oceanbox/Poseidon/compare/v1.38.1...v1.38.2) (2026-01-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **atlas:** Disable Snowflakes ([4cd3673](https://gitlab.com/oceanbox/Poseidon/commit/4cd3673d15783afa72ca3358e6bd8c3a8cfbfd16))
|
||||
|
||||
## [1.38.1](https://gitlab.com/oceanbox/Poseidon/compare/v1.38.0...v1.38.1) (2026-01-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **xtractor:** Move to short partition and 16 CPU, also set max duration to 1 year ([56d3476](https://gitlab.com/oceanbox/Poseidon/commit/56d34767d7a2e0bc6aadaa5987344c6e7a58698a))
|
||||
|
||||
# [1.38.0](https://gitlab.com/oceanbox/Poseidon/compare/v1.37.1...v1.38.0) (2026-01-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* client layout ([efb3292](https://gitlab.com/oceanbox/Poseidon/commit/efb3292d9f4a8fccf2cebbdd75b3d3ffc186fa11))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add fluent ui to codex ([d8bf174](https://gitlab.com/oceanbox/Poseidon/commit/d8bf174d3aa181169365c178b4335052e13eabc5))
|
||||
|
||||
## [1.37.1](https://gitlab.com/oceanbox/Poseidon/compare/v1.37.0...v1.37.1) (2026-01-05)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **xtractor:** Avoid using union types in Dapr Actors ([14c1a57](https://gitlab.com/oceanbox/Poseidon/commit/14c1a57331f981b1e1e0793426448ea261002e6d))
|
||||
|
||||
# [1.37.0](https://gitlab.com/oceanbox/Poseidon/compare/v1.36.0...v1.37.0) (2025-12-22)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add XtractActor ([a8a187a](https://gitlab.com/oceanbox/Poseidon/commit/a8a187a412c13d3e9d21cbbcfc2e1813c0e38dfe))
|
||||
|
||||
# [1.36.0](https://gitlab.com/oceanbox/Poseidon/compare/v1.35.2...v1.36.0) (2025-12-12)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add Saturn.ReverseProxy middleware ([bd74504](https://gitlab.com/oceanbox/Poseidon/commit/bd745042dfa51fbbef7bf7b55d31b8b57e6ad0a4))
|
||||
|
||||
## [1.35.2](https://gitlab.com/oceanbox/Poseidon/compare/v1.35.1...v1.35.2) (2025-12-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **drifters:** reverse toggle on postdrift analysis ([563faa6](https://gitlab.com/oceanbox/Poseidon/commit/563faa6c0bd20a7d3f184bba66ae5df340e7ef4e))
|
||||
|
||||
## [1.35.1](https://gitlab.com/oceanbox/Poseidon/compare/v1.35.0...v1.35.1) (2025-12-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **stats:** no stats banner showing when stats are available, closing [#87](https://gitlab.com/oceanbox/Poseidon/issues/87) ([e75ffc4](https://gitlab.com/oceanbox/Poseidon/commit/e75ffc41e5d298e2ecf92c5c4d11e0f930f633ab))
|
||||
* **stats:** set priority order of depth plots, closing [#88](https://gitlab.com/oceanbox/Poseidon/issues/88) ([2887e6a](https://gitlab.com/oceanbox/Poseidon/commit/2887e6a90951f4aaa088ad14b3d2bbcb6fd25b93))
|
||||
|
||||
# [1.35.0](https://gitlab.com/oceanbox/Poseidon/compare/v1.34.2...v1.35.0) (2025-12-01)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* ❄️ ([a620c26](https://gitlab.com/oceanbox/Poseidon/commit/a620c26812d3ec7517c34e7931e03b411f725907))
|
||||
|
||||
## [1.34.2](https://gitlab.com/oceanbox/Poseidon/compare/v1.34.1...v1.34.2) (2025-11-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Temporarily use vtn as only source for wind barbs ([048e803](https://gitlab.com/oceanbox/Poseidon/commit/048e80356b59cfcd408bf6784bbc2e22aebe25c6))
|
||||
|
||||
## [1.34.1](https://gitlab.com/oceanbox/Poseidon/compare/v1.34.0...v1.34.1) (2025-11-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **stats:** find available stats archives ([48d46ed](https://gitlab.com/oceanbox/Poseidon/commit/48d46eda62160d3efe1423238a25f26a439c6b88))
|
||||
|
||||
# [1.34.0](https://gitlab.com/oceanbox/Poseidon/compare/v1.33.11...v1.34.0) (2025-11-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* extract data cage interaction matrix, closing [#84](https://gitlab.com/oceanbox/Poseidon/issues/84) ([afc888a](https://gitlab.com/oceanbox/Poseidon/commit/afc888ab604f4759e3787e3feab568a322109b34))
|
||||
* reintroduce active layer selector, closes [#74](https://gitlab.com/oceanbox/Poseidon/issues/74) ([c1fa85f](https://gitlab.com/oceanbox/Poseidon/commit/c1fa85fd1b955e810bd76973e5873a7ab4cb8f18))
|
||||
* remove parameter limitations on particle sims ([69b380e](https://gitlab.com/oceanbox/Poseidon/commit/69b380e6659521c9f9ab489fa75d1221cc9b7db2))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* fly-to coordinate button, closes [#85](https://gitlab.com/oceanbox/Poseidon/issues/85) ([a153238](https://gitlab.com/oceanbox/Poseidon/commit/a153238f79e2b2c87ac3553acce00bb17c1529f4))
|
||||
|
||||
## [1.33.11](https://gitlab.com/oceanbox/Poseidon/compare/v1.33.10...v1.33.11) (2025-11-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **ci:** Run archivist ci on coffee ([f68b7f6](https://gitlab.com/oceanbox/Poseidon/commit/f68b7f68c8fd95d5d593702632cc2e9a7b36007a))
|
||||
|
||||
## [1.33.10](https://gitlab.com/oceanbox/Poseidon/compare/v1.33.9...v1.33.10) (2025-11-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **ci:** Build on Coffee ([e04d36c](https://gitlab.com/oceanbox/Poseidon/commit/e04d36ca124693b471ac973cd4d0f39f42f0fec0))
|
||||
|
||||
## [1.33.9](https://gitlab.com/oceanbox/Poseidon/compare/v1.33.8...v1.33.9) (2025-11-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **nix:** Bump hash for node_modules ([fff7913](https://gitlab.com/oceanbox/Poseidon/commit/fff7913cd5280f0732bb23bc1cb6ed5282631b90))
|
||||
|
||||
## [1.33.8](https://gitlab.com/oceanbox/Poseidon/compare/v1.33.7...v1.33.8) (2025-11-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **ci:** Format .gitlab-ci.yml and move codex to nix runner ([3140c07](https://gitlab.com/oceanbox/Poseidon/commit/3140c07ad078d3b8b3082d2f86eaa73f4ba08969))
|
||||
* **ci:** Remove unsed var ([759bbc6](https://gitlab.com/oceanbox/Poseidon/commit/759bbc6f60e5b364dd83653193d1501ce86c9eef))
|
||||
* **ci:** Try using v4.4 ([c1be7c4](https://gitlab.com/oceanbox/Poseidon/commit/c1be7c468dd9a689eb0b1b61797841c398fadc02))
|
||||
* **ci:** Use 4.4 for check ([b109dbd](https://gitlab.com/oceanbox/Poseidon/commit/b109dbdcbdfa3315e099b2edf72eb10518732c8f))
|
||||
|
||||
## [1.33.7](https://gitlab.com/oceanbox/Poseidon/compare/v1.33.6...v1.33.7) (2025-11-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **ci:** Change name to codex ([6d04af6](https://gitlab.com/oceanbox/Poseidon/commit/6d04af6230b536c1cbadf9abd2b59cf13609b451))
|
||||
* **ci:** Correct tag for codex pipeline ([7cf5064](https://gitlab.com/oceanbox/Poseidon/commit/7cf50641f986ab81b030e678ff7db0de83acae98))
|
||||
* **ci:** Run Codex on Coffee ([dd398bd](https://gitlab.com/oceanbox/Poseidon/commit/dd398bd96b0d79c8585a3156edcd2158982744d9))
|
||||
|
||||
## [1.33.6](https://gitlab.com/oceanbox/Poseidon/compare/v1.33.5...v1.33.6) (2025-11-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **maps:** Make FluentUI DatePicker popup inline; closes [#83](https://gitlab.com/oceanbox/Poseidon/issues/83) ([e3a1f56](https://gitlab.com/oceanbox/Poseidon/commit/e3a1f56b87c20ad9aada6108c92854b9d12671df))
|
||||
|
||||
## [1.33.5](https://gitlab.com/oceanbox/Poseidon/compare/v1.33.4...v1.33.5) (2025-11-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* General fixes "stats" menu ([c3c9e8e](https://gitlab.com/oceanbox/Poseidon/commit/c3c9e8e4e2b473564d1f6434a28dd934457189e9)), closes [#79](https://gitlab.com/oceanbox/Poseidon/issues/79) [#80](https://gitlab.com/oceanbox/Poseidon/issues/80) [#81](https://gitlab.com/oceanbox/Poseidon/issues/81) [#82](https://gitlab.com/oceanbox/Poseidon/issues/82)
|
||||
* no more DateFlicker (thanks Simen) ([7584bf6](https://gitlab.com/oceanbox/Poseidon/commit/7584bf661f2fd1cbf95e473a3334efcd69c77392))
|
||||
* remove goto today in date picker ([f23b3f1](https://gitlab.com/oceanbox/Poseidon/commit/f23b3f18213682e0b41c1db13b72e3c0bb0534b6))
|
||||
* **stats:** alert banner on missing stats ([5bb2ffd](https://gitlab.com/oceanbox/Poseidon/commit/5bb2ffd67c16078854b9f86a6728958c3923626c))
|
||||
|
||||
## [1.33.4](https://gitlab.com/oceanbox/Poseidon/compare/v1.33.3...v1.33.4) (2025-11-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **js:** Remove unused packages ([10a8c42](https://gitlab.com/oceanbox/Poseidon/commit/10a8c42319bbceebbf413d330fc72071f428e2dd))
|
||||
|
||||
## [1.33.3](https://gitlab.com/oceanbox/Poseidon/compare/v1.33.2...v1.33.3) (2025-11-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **nix:** Pre-commit with prek ([3093757](https://gitlab.com/oceanbox/Poseidon/commit/309375745443add8347ad6fca7b2dfe86ed1b7a7))
|
||||
* **nix:** Watch correct lock file ([79c6e2a](https://gitlab.com/oceanbox/Poseidon/commit/79c6e2abd0e26d220c10e532dffe53511f5dea0a))
|
||||
|
||||
## [1.33.2](https://gitlab.com/oceanbox/Poseidon/compare/v1.33.1...v1.33.2) (2025-11-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* create input display modal on demand ([1fed7ad](https://gitlab.com/oceanbox/Poseidon/commit/1fed7adf8009282184cacb4ddabf91274ee22b60))
|
||||
* **drifters:** Add datepickers for drifters sims ([785d0d5](https://gitlab.com/oceanbox/Poseidon/commit/785d0d57ae6cc53b59ad1d8708e5f513aca8dd77))
|
||||
* sim duration with restrictions ([a9145f6](https://gitlab.com/oceanbox/Poseidon/commit/a9145f6f79aa30cbe529d7a797b38dd2abe0a6cd))
|
||||
* **timeline:** display use local time in timeline ([ccbe076](https://gitlab.com/oceanbox/Poseidon/commit/ccbe07619f31c1b7511c8a8f4ca11fe3af8cbd53))
|
||||
* **timeline:** Marker uses UTC instead of CET + DST ([a474e7c](https://gitlab.com/oceanbox/Poseidon/commit/a474e7cbd4a894b9c1f5f20420dc728f93bc2e07))
|
||||
|
||||
## [1.33.1](https://gitlab.com/oceanbox/Poseidon/compare/v1.33.0...v1.33.1) (2025-11-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Add missing * in Drifters ArchiveType FromString ([7054ade](https://gitlab.com/oceanbox/Poseidon/commit/7054ade55dffd058f0d622447da08587633b815c))
|
||||
* **Atlantis:** Add wildcards in allow origin ([cde779e](https://gitlab.com/oceanbox/Poseidon/commit/cde779ea00f0666e6b548c7d711873b64cb294d9))
|
||||
|
||||
# [1.33.0](https://gitlab.com/oceanbox/Poseidon/compare/v1.32.0...v1.33.0) (2025-11-24)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* properly display sim parameters, and fix clone, closing [#70](https://gitlab.com/oceanbox/Poseidon/issues/70) ([4980a41](https://gitlab.com/oceanbox/Poseidon/commit/4980a41a4456be53c8a0193f1fbc40e0238c0111))
|
||||
|
||||
# [1.32.0](https://gitlab.com/oceanbox/Poseidon/compare/v1.31.0...v1.32.0) (2025-11-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* spell out units in plots ([020a2f2](https://gitlab.com/oceanbox/Poseidon/commit/020a2f2e000ed907ad1eb7768e47f6b053038e4e))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add Density profile plot and cancelable jobs ([8472708](https://gitlab.com/oceanbox/Poseidon/commit/847270877abc3bd983a400a29a0b4bebae033b06)), closes [oceanbox/Poseidon#30](https://gitlab.com/oceanbox/Poseidon/issues/30)
|
||||
|
||||
# [1.31.0](https://gitlab.com/oceanbox/Poseidon/compare/v1.30.1...v1.31.0) (2025-11-19)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **DataAgent:** offline eval function when you have a dto ([1982770](https://gitlab.com/oceanbox/Poseidon/commit/19827701aad9625d55f3bab4d48cb44122d512ae))
|
||||
|
||||
## [1.30.1](https://gitlab.com/oceanbox/Poseidon/compare/v1.30.0...v1.30.1) (2025-11-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Remove implicit FSharp.Core, Lib and Nuget for better CPM compat ([f8ab41b](https://gitlab.com/oceanbox/Poseidon/commit/f8ab41bbda9957727de63970a80f3c69e8715098))
|
||||
|
||||
# [1.30.0](https://gitlab.com/oceanbox/Poseidon/compare/v1.29.0...v1.30.0) (2025-11-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* don't fetch tz contour on update timeframe ([beedeb8](https://gitlab.com/oceanbox/Poseidon/commit/beedeb836b4353a5253362c11c3f772f65ad503d))
|
||||
* switch to element path and use haversine distance ([61b4323](https://gitlab.com/oceanbox/Poseidon/commit/61b432380310a9cbc5c1383854d71055034a49dd))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add Chaikin curve smoothing algorithm ([d4766f2](https://gitlab.com/oceanbox/Poseidon/commit/d4766f249b2ec1238a7df61df1a8fd3f3525f797))
|
||||
* sea distance circle, closing [#73](https://gitlab.com/oceanbox/Poseidon/issues/73) ([fc41c91](https://gitlab.com/oceanbox/Poseidon/commit/fc41c91d41ee35036ca6482e0f5128a51e175d7f))
|
||||
|
||||
# [1.29.0](https://gitlab.com/oceanbox/Poseidon/compare/v1.28.0...v1.29.0) (2025-11-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* move map type picker to the usual place ([917058b](https://gitlab.com/oceanbox/Poseidon/commit/917058b7be861a674a975f0ba939f5a227c11cf9))
|
||||
* naming ([5be346e](https://gitlab.com/oceanbox/Poseidon/commit/5be346e0fc7473640f2506a94e81f9e28a95b0c2))
|
||||
* switch to node path and use lonlat coord in api ([d8f38c4](https://gitlab.com/oceanbox/Poseidon/commit/d8f38c496d8fefb575157082ea1d6fc8f03fe806))
|
||||
* update fvcomkit ([da6a199](https://gitlab.com/oceanbox/Poseidon/commit/da6a1995c11702fe29965e77abfa7da525ff9e9c))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* calculation of sea distance, closes [#51](https://gitlab.com/oceanbox/Poseidon/issues/51) ([1b4f4ef](https://gitlab.com/oceanbox/Poseidon/commit/1b4f4ef3606354e09d5c20f8413b939291c6a78d))
|
||||
|
||||
# [1.28.0](https://gitlab.com/oceanbox/Poseidon/compare/v1.27.2...v1.28.0) (2025-11-05)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Don't build Sorcerer Client ([a627b08](https://gitlab.com/oceanbox/Poseidon/commit/a627b08df2fc02e28db105068a9766be5bf6ac10))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Centralize Nuget Package Management ([b062f66](https://gitlab.com/oceanbox/Poseidon/commit/b062f66cf90639ec3cccdbcf54826a622f28bb08))
|
||||
|
||||
## [1.27.2](https://gitlab.com/oceanbox/Poseidon/compare/v1.27.1...v1.27.2) (2025-11-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* cage interaction matrix and labeling, closes [#72](https://gitlab.com/oceanbox/Poseidon/issues/72) ([6d5a724](https://gitlab.com/oceanbox/Poseidon/commit/6d5a72412ba144cb9d948ba0cfdb1d464747e41f))
|
||||
|
||||
## [1.27.1](https://gitlab.com/oceanbox/Poseidon/compare/v1.27.0...v1.27.1) (2025-10-31)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Atlantis:** add sanity check for sim names, closes [#66](https://gitlab.com/oceanbox/Poseidon/issues/66) ([637d22e](https://gitlab.com/oceanbox/Poseidon/commit/637d22e0f094042504b50922932da5d1786f0cfe))
|
||||
* **Atlantis:** minor tweaks in ui naming and layout ([187a5e3](https://gitlab.com/oceanbox/Poseidon/commit/187a5e36e3bd28bf73dde4eaa1634df4fecb5ce1))
|
||||
* **Atlantis:** switch to chart-trend icon ([9b6a3ef](https://gitlab.com/oceanbox/Poseidon/commit/9b6a3ef1334eb0e2f74c1e398f3873a7095e5d43))
|
||||
* **Atlantis:** use Conc instead of activeLayer ([039df74](https://gitlab.com/oceanbox/Poseidon/commit/039df744d3901e3e476c53db2a39daaae0d93855))
|
||||
* **Atlantis:** use tab for statistics ([3a9c616](https://gitlab.com/oceanbox/Poseidon/commit/3a9c616f6b29df39fc679126036a79d76a4c2909))
|
||||
|
||||
# [1.27.0](https://gitlab.com/oceanbox/Poseidon/compare/v1.26.4...v1.27.0) (2025-10-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Atlantis:** fix buttons on sim setup ([c6eee07](https://gitlab.com/oceanbox/Poseidon/commit/c6eee0731705254bd32b68de8ceac7b6a37a5cc6))
|
||||
* **Atlantis:** fix style in point samples section ([e85a114](https://gitlab.com/oceanbox/Poseidon/commit/e85a114a234006c7aa4456ebf383f2dfbb884b65))
|
||||
* **Atlantis:** read sample radius from string ([0bd66f8](https://gitlab.com/oceanbox/Poseidon/commit/0bd66f89fd8539a323a3fcc34aca7a6620b26370))
|
||||
* **Mapster:** Do not disable timeline when not in OceanControls ([aaa5627](https://gitlab.com/oceanbox/Poseidon/commit/aaa56271b0e97132550eb687725d39174d7446b5))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **Atlantis:** add alternative unit for downwelling, closes [#47](https://gitlab.com/oceanbox/Poseidon/issues/47) ([234cd19](https://gitlab.com/oceanbox/Poseidon/commit/234cd19af7021c45cf74e6792b38784f27ca981e))
|
||||
* **Atlantis:** introduce average point values within circle ([a7cb34d](https://gitlab.com/oceanbox/Poseidon/commit/a7cb34d7bad0fcb76b67a36c6292e5d65b878448))
|
||||
* **Atlantis:** time series from point samples, closes [#29](https://gitlab.com/oceanbox/Poseidon/issues/29) ([1d1ddf2](https://gitlab.com/oceanbox/Poseidon/commit/1d1ddf2fef4d8687a1eb9652538e9d2fa2c1a40f))
|
||||
* **Hipster:** add slurm account and comment ([40414db](https://gitlab.com/oceanbox/Poseidon/commit/40414db1f6976fcfd87e55e60b57feec0189e9cf))
|
||||
|
||||
## [1.26.4](https://gitlab.com/oceanbox/Poseidon/compare/v1.26.3...v1.26.4) (2025-10-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Create TMP directory for atlantis and add errorhandler ([0473f7b](https://gitlab.com/oceanbox/Poseidon/commit/0473f7b7656accd8bc493fd12908c2dd4a59a1b9))
|
||||
|
||||
## [1.26.3](https://gitlab.com/oceanbox/Poseidon/compare/v1.26.2...v1.26.3) (2025-10-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Atlas:** Fix top nav logo offset ([c5b85e9](https://gitlab.com/oceanbox/Poseidon/commit/c5b85e9ad44d02edda765ce7a8b6f314adaea669))
|
||||
* **Atlas:** Show loading when waiting for model area archives ([9181b43](https://gitlab.com/oceanbox/Poseidon/commit/9181b43732151fae37f0a837d946458b6544103d))
|
||||
* **Mapster:** Add back stats download button ([86425a2](https://gitlab.com/oceanbox/Poseidon/commit/86425a2292ea413bc9a6b24aab2105c9e85fade1))
|
||||
* **Mapster:** Add timeseries help text on stats ([0dbd11e](https://gitlab.com/oceanbox/Poseidon/commit/0dbd11ed68991a13d94f8a3053794751b5185bc4))
|
||||
* **Mapster:** Catch potential timeout error fetching time series ([214fb65](https://gitlab.com/oceanbox/Poseidon/commit/214fb650c1c06f5fbb9e42d922fb352833f3efec))
|
||||
* **Mapster:** Choose better warn condition for missing post drift ([17181f6](https://gitlab.com/oceanbox/Poseidon/commit/17181f6ca2d5f6a23a8d5f7f8853b1a64c670324))
|
||||
* **Mapster:** Fix plotly chart not auto ranging properly ([c862cb1](https://gitlab.com/oceanbox/Poseidon/commit/c862cb172fd24dbb3cb071e0e223665804965ed3))
|
||||
* **Mapster:** Handle grid search case for sub grids/models ([a046e7d](https://gitlab.com/oceanbox/Poseidon/commit/a046e7d84957cdc31d047b397ddd9d9470fdb847))
|
||||
* **Mapster:** Handle probing errors and show toast ([db37040](https://gitlab.com/oceanbox/Poseidon/commit/db370402090fc78206c31a48347338624fdabbed))
|
||||
* **Mapster:** Make loading clearer when loading ([215409c](https://gitlab.com/oceanbox/Poseidon/commit/215409c7c880e2ca48e8df58e591cbb3f385000c))
|
||||
* **Mapster:** Make notifier list an array ([3b4ec02](https://gitlab.com/oceanbox/Poseidon/commit/3b4ec021af8f1efff3f994a8ce912d8780eb966c))
|
||||
* **Mapster:** More sane stat selection in depth plots ([fa63ade](https://gitlab.com/oceanbox/Poseidon/commit/fa63ade2e1a974cc4cbf2c40b67b999b2040a1af))
|
||||
* **Mapster:** Redirect to /signin if entering map unauthenticated ([7176919](https://gitlab.com/oceanbox/Poseidon/commit/7176919e944fab87f7081887077a25c7ec2a5f33))
|
||||
* **Mapster:** Remove Esc listener from side nav stats controls ([59ce5b9](https://gitlab.com/oceanbox/Poseidon/commit/59ce5b98b83bd53dd47e0a7666fedcaf5b8bad20))
|
||||
* **Mapster:** Show warning when non-transport sims are missing post ([9bd0562](https://gitlab.com/oceanbox/Poseidon/commit/9bd0562b47eac0a36338ca0ce479b9207f142a67))
|
||||
* **Mapster:** Sidebar styling updates ([f7fbc5d](https://gitlab.com/oceanbox/Poseidon/commit/f7fbc5d70dc277f2f9bb9df2fa020b9b49a06416))
|
||||
* **Mapster:** Update field probing data extraction view ([9eed6af](https://gitlab.com/oceanbox/Poseidon/commit/9eed6af711e570fb4b7079686a7e01a592c3f383))
|
||||
* Use custom plotly bundle in react-plotly.js ([51fc750](https://gitlab.com/oceanbox/Poseidon/commit/51fc7503b5fae6c05c233888ece06e314f0555a3))
|
||||
|
||||
## [1.26.2](https://gitlab.com/oceanbox/Poseidon/compare/v1.26.1...v1.26.2) (2025-10-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **tmp:** Add prints to debug ([b5c21bb](https://gitlab.com/oceanbox/Poseidon/commit/b5c21bb62c31c98e21da7a670eda92e1b5c9d02d))
|
||||
|
||||
## [1.26.1](https://gitlab.com/oceanbox/Poseidon/compare/v1.26.0...v1.26.1) (2025-10-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Don't use nodejs in dev ([90bab5e](https://gitlab.com/oceanbox/Poseidon/commit/90bab5ecc0d095f494bb3c65c16c5ad5a36cc8a8)), closes [oceanbox/Poseidon#1](https://gitlab.com/oceanbox/Poseidon/issues/1)
|
||||
* Typo and build archivist with netcdf runtimeDeps ([8482921](https://gitlab.com/oceanbox/Poseidon/commit/84829214204828cbf1fed6f54f4055dc9d456561))
|
||||
|
||||
# [1.26.0](https://gitlab.com/oceanbox/Poseidon/compare/v1.25.5...v1.26.0) (2025-10-23)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **fga:** Add start_time to ReadChanges ([9388d37](https://gitlab.com/oceanbox/Poseidon/commit/9388d3758dc769106c2413686e59821352e74967))
|
||||
|
||||
## [1.25.5](https://gitlab.com/oceanbox/Poseidon/compare/v1.25.4...v1.25.5) (2025-10-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **nix:** Build Archivist SIF in pipeline ([16f9686](https://gitlab.com/oceanbox/Poseidon/commit/16f968657b61cb6ce6cecb12d8016565a25919fc))
|
||||
|
||||
## [1.25.4](https://gitlab.com/oceanbox/Poseidon/compare/v1.25.3...v1.25.4) (2025-10-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* reduce number of cpus per drifter job ([b293044](https://gitlab.com/oceanbox/Poseidon/commit/b293044258ecc9699e62205181e8963f8470d1c2))
|
||||
|
||||
## [1.25.3](https://gitlab.com/oceanbox/Poseidon/compare/v1.25.2...v1.25.3) (2025-10-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Atlantis:** set limitations on simulation parameters ([407a278](https://gitlab.com/oceanbox/Poseidon/commit/407a278e283144ef2d163108c63e8b2a586b560c))
|
||||
|
||||
## [1.25.2](https://gitlab.com/oceanbox/Poseidon/compare/v1.25.1...v1.25.2) (2025-10-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Send Drifters to Inbox instantly and Track Plumes ([48b5da9](https://gitlab.com/oceanbox/Poseidon/commit/48b5da9da3f3fcffc57592ea66f3a3d3c8828761))
|
||||
* Submit as queued if there is a queue ([d64ee45](https://gitlab.com/oceanbox/Poseidon/commit/d64ee45d9e82da86c6ca348d36169dbbdb363edd))
|
||||
|
||||
## [1.25.1](https://gitlab.com/oceanbox/Poseidon/compare/v1.25.0...v1.25.1) (2025-10-12)
|
||||
|
||||
|
||||
|
||||
33
default.nix
33
default.nix
@@ -1,5 +1,5 @@
|
||||
{
|
||||
sources ? import ./lon.nix,
|
||||
sources ? import ./nix,
|
||||
system ? builtins.currentSystem,
|
||||
pkgs ? import sources.nixpkgs {
|
||||
inherit system;
|
||||
@@ -17,8 +17,8 @@ let
|
||||
in
|
||||
clean version;
|
||||
|
||||
dotnet-sdk = pkgs.dotnetCorePackages.sdk_9_0;
|
||||
dotnet-runtime = pkgs.dotnetCorePackages.aspnetcore_9_0;
|
||||
dotnet-sdk = pkgs.dotnetCorePackages.sdk_10_0;
|
||||
dotnet-runtime = pkgs.dotnetCorePackages.aspnetcore_10_0;
|
||||
deps = nix-utils.output.lib.nuget.deps;
|
||||
|
||||
# Usage: export NETRC="$(agenix -d netrc.age)" in `./nix/secrets`
|
||||
@@ -28,25 +28,16 @@ let
|
||||
|
||||
packages = import ./nix/packages {
|
||||
inherit
|
||||
env
|
||||
deps
|
||||
pkgs
|
||||
version
|
||||
dotnet-sdk
|
||||
dotnet-runtime
|
||||
env
|
||||
deps
|
||||
;
|
||||
inherit netrcConfig;
|
||||
};
|
||||
in
|
||||
rec {
|
||||
inherit packages;
|
||||
|
||||
inherit scripts;
|
||||
|
||||
# Expose atlantis as default packages
|
||||
default = packages.atlantis;
|
||||
|
||||
# Docker and Singurlarity images
|
||||
containers = pkgs.callPackage ./nix/containers.nix {
|
||||
inherit (packages)
|
||||
atlantis
|
||||
@@ -58,9 +49,21 @@ rec {
|
||||
version
|
||||
env
|
||||
;
|
||||
codex = packages.codex;
|
||||
};
|
||||
in
|
||||
{
|
||||
inherit packages;
|
||||
|
||||
inherit scripts;
|
||||
|
||||
# Expose atlantis as default packages
|
||||
default = packages.atlantis;
|
||||
|
||||
# Docker and Singurlarity images
|
||||
containers = containers;
|
||||
|
||||
checks = {
|
||||
pre-commit = import ./nix/pre-commit.nix;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "9.0.0",
|
||||
"version": "10.0.100",
|
||||
"rollForward": "latestMinor"
|
||||
}
|
||||
}
|
||||
42
justfile
Normal file
42
justfile
Normal file
@@ -0,0 +1,42 @@
|
||||
# Poseidon build commands
|
||||
# Install just: https://github.com/casey/just
|
||||
#
|
||||
# Sub-projects with justfiles:
|
||||
# - Atlantis (src/Atlantis) - Server + Client application with Fable/Vite
|
||||
# - ServerPack (src/ServerPack) - Server package library
|
||||
# - DataAgent (src/DataAgent) - Data agent library
|
||||
# - Interfaces (src/Interfaces) - API interfaces library
|
||||
# - Archivist (src/Archivist) - CLI tool with client
|
||||
# - Sorcerer (src/Sorcerer) - Server application with client
|
||||
#
|
||||
# Run 'just <project>' to see available commands for each project (e.g., 'just atlantis')
|
||||
|
||||
set dotenv-load
|
||||
|
||||
# Default recipe - show available commands
|
||||
default:
|
||||
@just --list
|
||||
|
||||
# Show available commands for Atlantis (src/Atlantis)
|
||||
atlantis:
|
||||
@just src/Atlantis/
|
||||
|
||||
# Show available commands for ServerPack (src/ServerPack)
|
||||
serverpack:
|
||||
@just src/ServerPack/
|
||||
|
||||
# Show available commands for DataAgent (src/DataAgent)
|
||||
dataagent:
|
||||
@just src/DataAgent/
|
||||
|
||||
# Show available commands for Interfaces (src/Interfaces)
|
||||
interfaces:
|
||||
@just src/Interfaces/
|
||||
|
||||
# Show available commands for Archivist (src/Archivist)
|
||||
archivist:
|
||||
@just src/Archivist/
|
||||
|
||||
# Show available commands for Sorcerer (src/Sorcerer)
|
||||
sorcerer:
|
||||
@just src/Sorcerer/
|
||||
55
lon.lock
55
lon.lock
@@ -1,55 +0,0 @@
|
||||
{
|
||||
"version": "1",
|
||||
"sources": {
|
||||
"agenix": {
|
||||
"type": "GitHub",
|
||||
"fetchType": "tarball",
|
||||
"owner": "ryantm",
|
||||
"repo": "agenix",
|
||||
"branch": "main",
|
||||
"revision": "9edb1787864c4f59ae5074ad498b6272b3ec308d",
|
||||
"url": "https://github.com/ryantm/agenix/archive/9edb1787864c4f59ae5074ad498b6272b3ec308d.tar.gz",
|
||||
"hash": "sha256-NA/FT2hVhKDftbHSwVnoRTFhes62+7dxZbxj5Gxvghs="
|
||||
},
|
||||
"nix-actions": {
|
||||
"type": "Git",
|
||||
"fetchType": "git",
|
||||
"branch": "main",
|
||||
"revision": "06847b3256df402da0475dccb290832ec92a9f8c",
|
||||
"url": "https://git.dgnum.eu/DGNum/nix-actions.git",
|
||||
"hash": "sha256-2xOZdKiUfcriQFKG37vY96dgCJLndhLa7cGacq8+SA8=",
|
||||
"lastModified": 1746294989,
|
||||
"submodules": false
|
||||
},
|
||||
"nix-utils": {
|
||||
"type": "Git",
|
||||
"fetchType": "git",
|
||||
"branch": "trunk",
|
||||
"revision": "098f594425d2b9dde0657becad0f6498d074f8b3",
|
||||
"url": "https://git.sr.ht/~mrtz/nix-utils",
|
||||
"hash": "sha256-y++BijM+FRkKDhVrL7YXZQiJ0DNVMiRN7yHf6QIXBUI=",
|
||||
"lastModified": 1756580332,
|
||||
"submodules": false
|
||||
},
|
||||
"nixpkgs": {
|
||||
"type": "GitHub",
|
||||
"fetchType": "tarball",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"branch": "nixpkgs-unstable",
|
||||
"revision": "625ad6366178f03acd79f9e3822606dd7985b657",
|
||||
"url": "https://github.com/NixOS/nixpkgs/archive/625ad6366178f03acd79f9e3822606dd7985b657.tar.gz",
|
||||
"hash": "sha256-wg1Lz/1FC5Q13R+mM5a2oTV9TA9L/CHHTm3/PiLayfA="
|
||||
},
|
||||
"pre-commit": {
|
||||
"type": "GitHub",
|
||||
"fetchType": "tarball",
|
||||
"owner": "cachix",
|
||||
"repo": "git-hooks.nix",
|
||||
"branch": "master",
|
||||
"revision": "54df955a695a84cd47d4a43e08e1feaf90b1fd9b",
|
||||
"url": "https://github.com/cachix/git-hooks.nix/archive/54df955a695a84cd47d4a43e08e1feaf90b1fd9b.tar.gz",
|
||||
"hash": "sha256-ytw7ROXaWZ7OfwHrQ9xvjpUWeGVm86pwnEd1QhzawIo="
|
||||
}
|
||||
}
|
||||
}
|
||||
53
lon.nix
53
lon.nix
@@ -1,53 +0,0 @@
|
||||
# Generated by lon. Do not modify!
|
||||
let
|
||||
|
||||
lock = builtins.fromJSON (builtins.readFile ./lon.lock);
|
||||
|
||||
# Override with a path defined in an environment variable. If no variable is
|
||||
# set, the original path is used.
|
||||
overrideFromEnv =
|
||||
name: path:
|
||||
let
|
||||
replacement = builtins.getEnv "LON_OVERRIDE_${name}";
|
||||
in
|
||||
if replacement == "" then
|
||||
path
|
||||
else
|
||||
# this turns the string into an actual Nix path (for both absolute and
|
||||
# relative paths)
|
||||
if builtins.substring 0 1 replacement == "/" then
|
||||
/. + replacement
|
||||
else
|
||||
/. + builtins.getEnv "PWD" + "/${replacement}";
|
||||
|
||||
fetchSource =
|
||||
args@{ fetchType, ... }:
|
||||
if fetchType == "git" then
|
||||
builtins.fetchGit (
|
||||
{
|
||||
url = args.url;
|
||||
ref = args.branch;
|
||||
rev = args.revision;
|
||||
narHash = args.hash;
|
||||
submodules = args.submodules;
|
||||
}
|
||||
// (
|
||||
if args ? lastModified then
|
||||
{
|
||||
inherit (args) lastModified;
|
||||
shallow = true;
|
||||
}
|
||||
else
|
||||
{ }
|
||||
)
|
||||
)
|
||||
else if fetchType == "tarball" then
|
||||
builtins.fetchTarball {
|
||||
url = args.url;
|
||||
sha256 = args.hash;
|
||||
}
|
||||
else
|
||||
builtins.throw "Unsupported source type ${fetchType}";
|
||||
|
||||
in
|
||||
builtins.mapAttrs (name: args: overrideFromEnv name (fetchSource args)) lock.sources
|
||||
@@ -1,6 +1,6 @@
|
||||
# Nix
|
||||
|
||||
This directory contains Nix expressions defining the packages, containers and workflows used to run/build Poseidon.
|
||||
This directory contains Nix expressions defining the packages, and containers used to run/build Poseidon.
|
||||
|
||||
## Directory Structure
|
||||
|
||||
@@ -16,9 +16,6 @@ nix/
|
||||
│ ├── atlantis-deps.json # Atlantis backend dependencies metadata
|
||||
│ ├── sorcerer.nix
|
||||
│ └── archivist.nix
|
||||
├── workflows/ # GitHub Actions workflows
|
||||
│ ├── build.nix
|
||||
│ └── update.nix
|
||||
├── secrets/ # Age encrypted files
|
||||
│ ├── secrets.nix
|
||||
│ └── *.age
|
||||
@@ -26,7 +23,6 @@ nix/
|
||||
│ ├── atlantis
|
||||
│ ├── sorcerer
|
||||
│ └── archivist
|
||||
├── workflows.nix # GitHub Actions workflow orchestration
|
||||
└── pre-commit.nix # Pre-commit hooks for code quality
|
||||
```
|
||||
|
||||
@@ -85,12 +81,6 @@ $ nix-shell
|
||||
$ nix-shell -A packages.atlantis
|
||||
```
|
||||
|
||||
### Generating Github Actions
|
||||
```bash
|
||||
# Updates the generate workflows in `.githhub/workflows`
|
||||
$ nix-shell -A workflows --run "echo Generate"
|
||||
```
|
||||
|
||||
### Running Services
|
||||
```bash
|
||||
# Run Atlantis server
|
||||
@@ -100,12 +90,20 @@ $ ./result/bin/atlantis
|
||||
$ ./result/bin/sorcerer
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
### Update dependencies
|
||||
|
||||
The build system automatically handles:
|
||||
- .NET SDK and runtime dependencies
|
||||
- JavaScript packages via Bun
|
||||
- Private NuGet packages from the Oceanbox registry
|
||||
- System libraries (NetCDF, etc.)
|
||||
When updating the `npm` dependencies, the `outputHash` in `atlantis-client.nix` needs to be updated. Simply run
|
||||
|
||||
For updating dependencies, see the [scripts documentation](../scripts/README.md).
|
||||
```bash
|
||||
nix-build -A packages.atlantis-client # in the root
|
||||
```
|
||||
|
||||
It will then fail on the wrong hash and provide the correct one.
|
||||
|
||||
#### Deterministic builds, vendor hashes, and lock files
|
||||
|
||||
Nix aims for deterministic (reproducible) builds. A key part of this is **fixed-output derivations (FODs)** such as `fetchurl`, `buildDotnetModule`’s vendor step, `fetchgit`, etc. For any FOD, Nix requires a **content hash** up front. After the build/fetch runs, Nix verifies that the resulting output’s hash exactly matches what was declared; if it doesn’t, the build fails with a “hash mismatch in fixed-output derivation” error. This protects you from drifting dependencies and ensures that CI and local builds use the exact same inputs.
|
||||
|
||||
#### Why vendor hashes need to be pinned
|
||||
|
||||
Language ecosystems resolve and download a lot of upstream content (Go modules, npm/yarn, cargo crates, vendored tarballs…). To make those fetches deterministic, Nix needs the **expected content hash**. For example, with `makeDerivation`, you must set `outputHash` so Nix knows what the fully-resolved module tree should hash to. If you bump a version or change dependencies, the **content changes** and the old hash becomes invalid—Nix will (correctly) refuse the build until you update the pinned hash.
|
||||
|
||||
@@ -6,7 +6,16 @@
|
||||
atlantis-client,
|
||||
sorcerer,
|
||||
archivist,
|
||||
codex,
|
||||
}:
|
||||
let
|
||||
# Entrypoints
|
||||
startArchivist = pkgs.writeScriptBin "entrypoint" ''
|
||||
#!${pkgs.runtimeShell}
|
||||
set -euo pipefail
|
||||
exec archivist "$@"
|
||||
'';
|
||||
in
|
||||
{
|
||||
atlantis = pkgs.dockerTools.buildLayeredImage {
|
||||
name = "Atlantis";
|
||||
@@ -22,13 +31,18 @@
|
||||
];
|
||||
|
||||
extraCommands = ''
|
||||
mkdir -m 0777 tmp
|
||||
mkdir -p ./app
|
||||
cp -r ${atlantis}/lib/Atlantis/* ./app/
|
||||
cp -r ${atlantis-client}/public ./app/
|
||||
'';
|
||||
|
||||
config = {
|
||||
cmd = [ "Server" ];
|
||||
workingDir = "/app";
|
||||
env = [
|
||||
"SERVER_CONTENT_ROOT=/app/public"
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
@@ -54,21 +68,35 @@
|
||||
};
|
||||
};
|
||||
|
||||
archivist = pkgs.singularity-tools.buildImage {
|
||||
archivist = pkgs.dockerTools.buildLayeredImage {
|
||||
name = "archivist";
|
||||
tag = archivist.version;
|
||||
created = "now";
|
||||
|
||||
contents = [
|
||||
archivist
|
||||
pkgs.netcdf
|
||||
pkgs.coreutils
|
||||
pkgs.bash
|
||||
]
|
||||
++ pkgs.lib.optionals (env == "Debug") [
|
||||
pkgs.busybox
|
||||
pkgs.dockerTools.binSh
|
||||
];
|
||||
diskSize = 2048; # MB
|
||||
# TODO: Add memorysize
|
||||
runScript = ''
|
||||
#!/bin/bash
|
||||
export ARCHMAESTER_URL="https://maps.oceanbox.io"
|
||||
export ARCHMAESTER_AUTH="admin:en-to-tre-fire"
|
||||
exec ${archivist}/bin/archivist "$@"
|
||||
|
||||
compressor = "none";
|
||||
|
||||
extraCommands = ''
|
||||
mkdir -p ./app
|
||||
cp -r ${archivist}/lib/Archivist/* ./app/
|
||||
'';
|
||||
|
||||
config = {
|
||||
entrypoint = [ "${startArchivist}/bin/entrypoint" ];
|
||||
workingDir = "/app";
|
||||
env = [
|
||||
"ARCHMAESTER_URL=https://maps.oceanbox.io"
|
||||
"ARCHMAESTER_AUTH=admin:en-to-tre-fire"
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
codex = pkgs.callPackage ../src/Codex/container.nix { server = codex; };
|
||||
}
|
||||
249
nix/default.nix
Normal file
249
nix/default.nix
Normal file
@@ -0,0 +1,249 @@
|
||||
/*
|
||||
This file is provided under the MIT licence:
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
# Generated by npins. Do not modify; will be overwritten regularly
|
||||
let
|
||||
# Backwards-compatibly make something that previously didn't take any arguments take some
|
||||
# The function must return an attrset, and will unfortunately be eagerly evaluated
|
||||
# Same thing, but it catches eval errors on the default argument so that one may still call it with other arguments
|
||||
mkFunctor =
|
||||
fn:
|
||||
let
|
||||
e = builtins.tryEval (fn { });
|
||||
in
|
||||
(if e.success then e.value else { error = fn { }; }) // { __functor = _self: fn; };
|
||||
|
||||
# https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/lists.nix#L295
|
||||
range =
|
||||
first: last: if first > last then [ ] else builtins.genList (n: first + n) (last - first + 1);
|
||||
|
||||
# https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L257
|
||||
stringToCharacters = s: map (p: builtins.substring p 1 s) (range 0 (builtins.stringLength s - 1));
|
||||
|
||||
# https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L269
|
||||
stringAsChars = f: s: concatStrings (map f (stringToCharacters s));
|
||||
concatStrings = builtins.concatStringsSep "";
|
||||
|
||||
# If the environment variable NPINS_OVERRIDE_${name} is set, then use
|
||||
# the path directly as opposed to the fetched source.
|
||||
# (Taken from Niv for compatibility)
|
||||
mayOverride =
|
||||
name: path:
|
||||
let
|
||||
envVarName = "NPINS_OVERRIDE_${saneName}";
|
||||
saneName = stringAsChars (c: if (builtins.match "[a-zA-Z0-9]" c) == null then "_" else c) name;
|
||||
ersatz = builtins.getEnv envVarName;
|
||||
in
|
||||
if ersatz == "" then
|
||||
path
|
||||
else
|
||||
# this turns the string into an actual Nix path (for both absolute and
|
||||
# relative paths)
|
||||
builtins.trace "Overriding path of \"${name}\" with \"${ersatz}\" due to set \"${envVarName}\"" (
|
||||
if builtins.substring 0 1 ersatz == "/" then
|
||||
/. + ersatz
|
||||
else
|
||||
/. + builtins.getEnv "PWD" + "/${ersatz}"
|
||||
);
|
||||
|
||||
mkSource =
|
||||
name: spec:
|
||||
{
|
||||
pkgs ? null,
|
||||
}:
|
||||
assert spec ? type;
|
||||
let
|
||||
# Unify across builtin and pkgs fetchers.
|
||||
# `fetchGit` requires a wrapper because of slight API differences.
|
||||
fetchers =
|
||||
if pkgs == null then
|
||||
{
|
||||
inherit (builtins) fetchTarball fetchurl;
|
||||
# For some fucking reason, fetchGit has a different signature than the other builtin fetchers …
|
||||
fetchGit = args: (builtins.fetchGit args).outPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
fetchTarball =
|
||||
{
|
||||
url,
|
||||
sha256,
|
||||
}:
|
||||
pkgs.fetchzip {
|
||||
inherit url sha256;
|
||||
extension = "tar";
|
||||
};
|
||||
inherit (pkgs) fetchurl;
|
||||
fetchGit =
|
||||
{
|
||||
url,
|
||||
submodules,
|
||||
rev,
|
||||
name,
|
||||
narHash,
|
||||
}:
|
||||
pkgs.fetchgit {
|
||||
inherit url rev name;
|
||||
fetchSubmodules = submodules;
|
||||
hash = narHash;
|
||||
};
|
||||
};
|
||||
|
||||
# Dispatch to the correct code path based on the type
|
||||
path =
|
||||
if spec.type == "Git" then
|
||||
mkGitSource fetchers spec
|
||||
else if spec.type == "GitRelease" then
|
||||
mkGitSource fetchers spec
|
||||
else if spec.type == "PyPi" then
|
||||
mkPyPiSource fetchers spec
|
||||
else if spec.type == "Channel" then
|
||||
mkChannelSource fetchers spec
|
||||
else if spec.type == "Tarball" then
|
||||
mkTarballSource fetchers spec
|
||||
else if spec.type == "Container" then
|
||||
mkContainerSource pkgs spec
|
||||
else
|
||||
builtins.throw "Unknown source type ${spec.type}";
|
||||
in
|
||||
spec // { outPath = mayOverride name path; };
|
||||
|
||||
mkGitSource =
|
||||
{
|
||||
fetchTarball,
|
||||
fetchGit,
|
||||
...
|
||||
}:
|
||||
{
|
||||
repository,
|
||||
revision,
|
||||
url ? null,
|
||||
submodules,
|
||||
hash,
|
||||
...
|
||||
}:
|
||||
assert repository ? type;
|
||||
# At the moment, either it is a plain git repository (which has an url), or it is a GitHub/GitLab repository
|
||||
# In the latter case, there we will always be an url to the tarball
|
||||
if url != null && !submodules then
|
||||
fetchTarball {
|
||||
inherit url;
|
||||
sha256 = hash;
|
||||
}
|
||||
else
|
||||
let
|
||||
url =
|
||||
if repository.type == "Git" then
|
||||
repository.url
|
||||
else if repository.type == "GitHub" then
|
||||
"https://github.com/${repository.owner}/${repository.repo}.git"
|
||||
else if repository.type == "GitLab" then
|
||||
"${repository.server}/${repository.repo_path}.git"
|
||||
else if repository.type == "Forgejo" then
|
||||
"${repository.server}/${repository.owner}/${repository.repo}.git"
|
||||
else
|
||||
throw "Unrecognized repository type ${repository.type}";
|
||||
urlToName =
|
||||
url: rev:
|
||||
let
|
||||
matched = builtins.match "^.*/([^/]*)(\\.git)?$" url;
|
||||
|
||||
short = builtins.substring 0 7 rev;
|
||||
|
||||
appendShort = if (builtins.match "[a-f0-9]*" rev) != null then "-${short}" else "";
|
||||
in
|
||||
"${if matched == null then "source" else builtins.head matched}${appendShort}";
|
||||
name = urlToName url revision;
|
||||
in
|
||||
fetchGit {
|
||||
rev = revision;
|
||||
narHash = hash;
|
||||
|
||||
inherit name submodules url;
|
||||
};
|
||||
|
||||
mkPyPiSource =
|
||||
{ fetchurl, ... }:
|
||||
{
|
||||
url,
|
||||
hash,
|
||||
...
|
||||
}:
|
||||
fetchurl {
|
||||
inherit url;
|
||||
sha256 = hash;
|
||||
};
|
||||
|
||||
mkChannelSource =
|
||||
{ fetchTarball, ... }:
|
||||
{
|
||||
url,
|
||||
hash,
|
||||
...
|
||||
}:
|
||||
fetchTarball {
|
||||
inherit url;
|
||||
sha256 = hash;
|
||||
};
|
||||
|
||||
mkTarballSource =
|
||||
{ fetchTarball, ... }:
|
||||
{
|
||||
url,
|
||||
locked_url ? url,
|
||||
hash,
|
||||
...
|
||||
}:
|
||||
fetchTarball {
|
||||
url = locked_url;
|
||||
sha256 = hash;
|
||||
};
|
||||
|
||||
mkContainerSource =
|
||||
pkgs:
|
||||
{
|
||||
image_name,
|
||||
image_tag,
|
||||
image_digest,
|
||||
...
|
||||
}:
|
||||
if pkgs == null then
|
||||
builtins.throw "container sources require passing in a Nixpkgs value: https://github.com/andir/npins/blob/master/README.md#using-the-nixpkgs-fetchers"
|
||||
else
|
||||
pkgs.dockerTools.pullImage {
|
||||
imageName = image_name;
|
||||
imageDigest = image_digest;
|
||||
finalImageTag = image_tag;
|
||||
};
|
||||
in
|
||||
mkFunctor (
|
||||
{
|
||||
input ? ./sources.json,
|
||||
}:
|
||||
let
|
||||
data =
|
||||
if builtins.isPath input then
|
||||
# while `readFile` will throw an error anyways if the path doesn't exist,
|
||||
# we still need to check beforehand because *our* error can be caught but not the one from the builtin
|
||||
# *piegames sighs*
|
||||
if builtins.pathExists input then
|
||||
builtins.fromJSON (builtins.readFile input)
|
||||
else
|
||||
throw "Input path ${toString input} does not exist"
|
||||
else if builtins.isAttrs input then
|
||||
input
|
||||
else
|
||||
throw "Unsupported input type ${builtins.typeOf input}, must be a path or an attrset";
|
||||
version = data.version;
|
||||
in
|
||||
if version == 7 then
|
||||
builtins.mapAttrs (name: spec: mkFunctor (mkSource name spec)) data.pins
|
||||
else
|
||||
throw "Unsupported format version ${toString version} in sources.json. Try running `npins upgrade`"
|
||||
)
|
||||
@@ -40,8 +40,8 @@ buildDotnetModule rec {
|
||||
};
|
||||
doCheck = false;
|
||||
buildType = env;
|
||||
packNupkg = true;
|
||||
# packNupkg = true;
|
||||
# NOTE(mrtz): Can't package nuget without it
|
||||
# [ref](https://github.com/dotnet/fsharp/issues/12320)
|
||||
dotnetFlags = "--property:TargetsForTfmSpecificContentInPackage=";
|
||||
# dotnetFlags = "--property:TargetsForTfmSpecificContentInPackage=";
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
env,
|
||||
pkgs,
|
||||
deps,
|
||||
netrcConfig,
|
||||
nix-gitignore,
|
||||
packageSources,
|
||||
dotnet-sdk,
|
||||
dotnet-runtime,
|
||||
buildDotnetModule,
|
||||
}:
|
||||
let
|
||||
@@ -19,13 +19,13 @@ in
|
||||
buildDotnetModule rec {
|
||||
inherit
|
||||
dotnet-sdk
|
||||
dotnet-runtime
|
||||
version
|
||||
src
|
||||
projectFile
|
||||
;
|
||||
name = "Archivist";
|
||||
pname = "Archivist";
|
||||
pname = name;
|
||||
dotnet-runtime = pkgs.dotnetCorePackages.runtime_10_0;
|
||||
dotnetRestoreFlags = "--force-evaluate";
|
||||
nugetDeps = deps {
|
||||
inherit
|
||||
@@ -38,8 +38,9 @@ buildDotnetModule rec {
|
||||
../../src/Archivist/src/Cli/packages.lock.json
|
||||
];
|
||||
};
|
||||
doCheck = false;
|
||||
nativeBuildInputs = [
|
||||
runtimeDeps = [
|
||||
pkgs.netcdf
|
||||
];
|
||||
buildType = env;
|
||||
doCheck = false;
|
||||
}
|
||||
|
||||
@@ -5,14 +5,13 @@
|
||||
deps,
|
||||
fable,
|
||||
version,
|
||||
dotnet-sdk,
|
||||
netrcConfig,
|
||||
stdenvNoCC,
|
||||
nodeModules,
|
||||
nix-gitignore,
|
||||
packageSources,
|
||||
dotnet-sdk,
|
||||
dotnet-runtime,
|
||||
buildDotnetModule,
|
||||
writableTmpDirAsHomeHook,
|
||||
}:
|
||||
let
|
||||
root = ../../.;
|
||||
@@ -22,124 +21,69 @@ let
|
||||
|
||||
pname = "Atlantis";
|
||||
|
||||
nodeDeps = stdenvNoCC.mkDerivation {
|
||||
inherit version;
|
||||
pname = "${pname}-node-deps";
|
||||
|
||||
nativeBuildInputs = [
|
||||
bun
|
||||
writableTmpDirAsHomeHook
|
||||
];
|
||||
|
||||
src = lib.fileset.toSource {
|
||||
inherit root;
|
||||
fileset = lib.fileset.unions [
|
||||
../../package.json
|
||||
../../bun.lock
|
||||
];
|
||||
};
|
||||
|
||||
dontConfigure = true;
|
||||
|
||||
# Only install dependencies, don't build
|
||||
buildPhase = ''
|
||||
runHook preBuild
|
||||
|
||||
export BUN_INSTALL_CACHE_DIR=$(mktemp -d)
|
||||
|
||||
# Disable post-install scripts to avoid shebang issues
|
||||
bun install \
|
||||
--frozen-lockfile \
|
||||
--ignore-scripts \
|
||||
--backend copyfile \
|
||||
--no-progress \
|
||||
--force
|
||||
|
||||
runHook postBuild
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
|
||||
mkdir -p $out
|
||||
cp -r node_modules $out/
|
||||
|
||||
runHook postInstall
|
||||
'';
|
||||
|
||||
# Required else we get errors that our fixed-output derivation references store paths
|
||||
dontFixup = true;
|
||||
|
||||
outputHashMode = "recursive";
|
||||
outputHashAlgo = "sha256";
|
||||
# NOTE: Empty this when a new dependency is added
|
||||
outputHash = "sha256-POQ6VXKnFYy8Vd9s6tTLCJXRo4m3leQuvHHKDBLVs2s=";
|
||||
};
|
||||
|
||||
atlantis-client = buildDotnetModule {
|
||||
inherit dotnet-sdk dotnet-runtime;
|
||||
inherit src version;
|
||||
pname = "${pname}-Client";
|
||||
|
||||
projectFile = "src/Atlantis/src/Client/Client.fsproj";
|
||||
dotnetRestoreFlags = "--force-evaluate";
|
||||
# nugetDeps = ./atlantis-client.json; # nix-build -A packages.atlantis-client.fetch-deps && ./result src/Atlantis/nix/atlantis-client.json
|
||||
nugetDeps = deps {
|
||||
inherit
|
||||
pkgs
|
||||
netrcConfig
|
||||
packageSources
|
||||
;
|
||||
name = "${pname}-Client";
|
||||
lockfiles = [
|
||||
../../src/Atlantis/src/Client/packages.lock.json
|
||||
];
|
||||
};
|
||||
|
||||
# Skip the default dotnet build since we're using Fable
|
||||
dontDotnetBuild = true;
|
||||
|
||||
buildInputs = [
|
||||
fable
|
||||
bun
|
||||
];
|
||||
|
||||
buildPhase = ''
|
||||
runHook preBuild
|
||||
|
||||
export HOME=$TMPDIR
|
||||
|
||||
cp -r ${nodeDeps}/node_modules ./.
|
||||
chmod -R u+rw node_modules
|
||||
|
||||
chmod -R u+x node_modules/.bin
|
||||
patchShebangs node_modules/.bin
|
||||
export PATH="./node_modules/.bin:$PATH"
|
||||
|
||||
cd src/Atlantis/src/Client
|
||||
|
||||
# NOTE(mrtz): Uses fable from nixpkgs instead of dotnet (Could be out of sync). --MSBuildCracker
|
||||
${lib.getExe fable} -e .jsx -o build
|
||||
|
||||
# Run vite from the Atlantis directory with proper config, always bundle for prod
|
||||
${lib.getExe bun} ../../../../node_modules/.bin/vite build -c ../../vite.config.js --outDir dist/public --mode Production
|
||||
|
||||
runHook postBuild
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
|
||||
# Copy output (*.js, *.css and *.html) to `/public`.
|
||||
mkdir -p $out/public
|
||||
cp -r dist/public/* $out/public/
|
||||
|
||||
runHook postInstall
|
||||
'';
|
||||
|
||||
dontFixup = true;
|
||||
dontPatchELF = true;
|
||||
dontStrip = true;
|
||||
};
|
||||
in
|
||||
atlantis-client
|
||||
buildDotnetModule {
|
||||
inherit dotnet-sdk dotnet-runtime;
|
||||
inherit src version;
|
||||
pname = "${pname}-Client";
|
||||
|
||||
projectFile = "src/Atlantis/src/Client/Client.fsproj";
|
||||
dotnetRestoreFlags = "--force-evaluate";
|
||||
# nugetDeps = ./atlantis-client.json; # nix-build -A packages.atlantis-client.fetch-deps && ./result src/Atlantis/nix/atlantis-client.json
|
||||
nugetDeps = deps {
|
||||
inherit
|
||||
pkgs
|
||||
netrcConfig
|
||||
packageSources
|
||||
;
|
||||
name = "${pname}-Client";
|
||||
lockfiles = [
|
||||
../../src/Atlantis/src/Client/packages.lock.json
|
||||
];
|
||||
};
|
||||
|
||||
# Skip the default dotnet build since we're using Fable
|
||||
dontDotnetBuild = true;
|
||||
|
||||
buildInputs = [
|
||||
fable
|
||||
bun
|
||||
];
|
||||
|
||||
buildPhase = ''
|
||||
runHook preBuild
|
||||
|
||||
export HOME=$TMPDIR
|
||||
|
||||
cp -r ${nodeModules}/node_modules ./.
|
||||
chmod -R u+rw node_modules
|
||||
|
||||
chmod -R u+x node_modules/.bin
|
||||
patchShebangs node_modules/.bin
|
||||
export PATH="./node_modules/.bin:$PATH"
|
||||
|
||||
cd src/Atlantis/src/Client
|
||||
|
||||
# NOTE(mrtz): Uses fable from nixpkgs instead of dotnet (Could be out of sync). --MSBuildCracker
|
||||
${lib.getExe fable} -e .jsx -o build
|
||||
|
||||
# Run vite from the Atlantis directory with proper config, always bundle for prod
|
||||
${lib.getExe bun} ../../../../node_modules/.bin/vite build -c ../../vite.config.js --outDir dist/public --mode Production
|
||||
|
||||
runHook postBuild
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
|
||||
# Copy output (*.js, *.css and *.html) to `/public`.
|
||||
mkdir -p $out/public
|
||||
cp -r dist/public/* $out/public/
|
||||
|
||||
runHook postInstall
|
||||
'';
|
||||
|
||||
dontFixup = true;
|
||||
dontPatchELF = true;
|
||||
dontStrip = true;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ buildDotnetModule rec {
|
||||
pname = "Atlantis";
|
||||
# NOTE(mrtz): Ensures reproducibility and reduces closure size,
|
||||
# by filtering out irrelevant files and `.git` which changes between commits.
|
||||
src = nix-gitignore.gitignoreSource [ ] ../../.;
|
||||
src = nix-gitignore.gitignoreSource [ ] ../..;
|
||||
projectFile = "src/Atlantis/src/Server/Server.fsproj";
|
||||
dotnetRestoreFlags = "--force-evaluate";
|
||||
nugetDeps = deps {
|
||||
@@ -38,6 +38,7 @@ buildDotnetModule rec {
|
||||
runtimeDeps = [
|
||||
pkgs.netcdf
|
||||
];
|
||||
# NOTE: Add back when we have tests
|
||||
doCheck = false;
|
||||
buildType = env;
|
||||
# Copy `appsettings` for local build
|
||||
|
||||
@@ -44,6 +44,6 @@ buildDotnetModule rec {
|
||||
];
|
||||
};
|
||||
doCheck = false;
|
||||
packNupkg = true;
|
||||
dotnetFlags = [ "--property:TargetsForTfmSpecificContentInPackage="];
|
||||
# packNupkg = true;
|
||||
# dotnetFlags = [ "--property:TargetsForTfmSpecificContentInPackage="];
|
||||
}
|
||||
|
||||
@@ -9,23 +9,18 @@
|
||||
}:
|
||||
let
|
||||
# NOTE(mrtz): Gitlab Nuget Registry does not support groupwide fetches :/
|
||||
packageSources = {
|
||||
"Oceanbox.FvcomKit" = "https://gitlab.com/api/v4/projects/35569541/packages/nuget/download";
|
||||
"ProjNet.FSharp" = "https://gitlab.com/api/v4/projects/35009572/packages/nuget/download";
|
||||
"SDSLite.Oceanbox" = "https://gitlab.com/api/v4/projects/34025102/packages/nuget/download";
|
||||
"Oceanbox.ServerPack" = "https://gitlab.com/api/v4/projects/67427353/packages/nuget/download";
|
||||
"Oceanbox.DataAgent" = "https://gitlab.com/api/v4/projects/37541600/packages/nuget/download";
|
||||
"Drifters.Api" = "https://gitlab.com/api/v4/projects/37086336/packages/nuget/download";
|
||||
"Fable.SignalR.AspNetCore" = "https://gitlab.com/api/v4/projects/40255650/packages/nuget/download";
|
||||
"Fable.SignalR.Saturn" = "https://gitlab.com/api/v4/projects/40255650/packages/nuget/download";
|
||||
"Fable.SignalR.Shared" = "https://gitlab.com/api/v4/projects/40255650/packages/nuget/download";
|
||||
"Fable.SignalR" = "https://gitlab.com/api/v4/projects/40255650/packages/nuget/download";
|
||||
"Fable.SignalR.Elmish" = "https://gitlab.com/api/v4/projects/40255650/packages/nuget/download";
|
||||
"Fable.Lit" = "https://gitlab.com/api/v4/projects/61744837/packages/nuget/download";
|
||||
"Fable.Lit.Elmish" = "https://gitlab.com/api/v4/projects/61744837/packages/nuget/download";
|
||||
"Fable.Lit.React" = "https://gitlab.com/api/v4/projects/61744837/packages/nuget/download";
|
||||
"Fable.OpenLayers" = "https://gitlab.com/api/v4/projects/36202053/packages/nuget/download";
|
||||
"Matplotlib.ColorMaps" = "https://gitlab.com/api/v4/projects/36675671/packages/nuget/download";
|
||||
packageSources = import ./sources.nix;
|
||||
|
||||
nodeModules = pkgs.callPackage ./node-modules.nix {};
|
||||
|
||||
codex-client = pkgs.callPackage ../../src/Codex/src/Client {
|
||||
inherit
|
||||
deps
|
||||
dotnet-sdk
|
||||
netrcConfig
|
||||
nodeModules
|
||||
packageSources
|
||||
;
|
||||
};
|
||||
in
|
||||
{
|
||||
@@ -38,6 +33,7 @@ in
|
||||
packageSources
|
||||
;
|
||||
};
|
||||
|
||||
# NOTE(mrtz): It's acutally Oceanbox.DataAgent
|
||||
archmaester = pkgs.callPackage ./dataagent.nix {
|
||||
inherit
|
||||
@@ -48,6 +44,7 @@ in
|
||||
packageSources
|
||||
;
|
||||
};
|
||||
|
||||
# NOTE(mrtz): It's acutally Poseidon.Api
|
||||
interfaces = pkgs.callPackage ./api.nix {
|
||||
inherit
|
||||
@@ -59,6 +56,7 @@ in
|
||||
packageSources
|
||||
;
|
||||
};
|
||||
|
||||
atlantis = pkgs.callPackage ./atlantis.nix {
|
||||
inherit
|
||||
env
|
||||
@@ -70,6 +68,7 @@ in
|
||||
packageSources
|
||||
;
|
||||
};
|
||||
|
||||
sorcerer = pkgs.callPackage ./sorcerer.nix {
|
||||
inherit
|
||||
env
|
||||
@@ -80,25 +79,35 @@ in
|
||||
packageSources
|
||||
;
|
||||
};
|
||||
|
||||
archivist = pkgs.callPackage ./archivist.nix {
|
||||
inherit
|
||||
env
|
||||
deps
|
||||
netrcConfig
|
||||
dotnet-sdk
|
||||
dotnet-runtime
|
||||
packageSources
|
||||
;
|
||||
};
|
||||
|
||||
atlantis-client = pkgs.callPackage ./atlantis-client.nix {
|
||||
inherit
|
||||
deps
|
||||
netrcConfig
|
||||
version
|
||||
dotnet-sdk
|
||||
netrcConfig
|
||||
nodeModules
|
||||
dotnet-runtime
|
||||
packageSources
|
||||
;
|
||||
};
|
||||
}
|
||||
|
||||
codex = pkgs.callPackage ../../src/Codex/src/Server {
|
||||
inherit
|
||||
deps
|
||||
dotnet-sdk
|
||||
netrcConfig
|
||||
dotnet-runtime
|
||||
packageSources
|
||||
;
|
||||
client = codex-client;
|
||||
};
|
||||
}
|
||||
52
nix/packages/node-modules.nix
Normal file
52
nix/packages/node-modules.nix
Normal file
@@ -0,0 +1,52 @@
|
||||
{
|
||||
bun,
|
||||
stdenvNoCC,
|
||||
nix-gitignore,
|
||||
writableTmpDirAsHomeHook,
|
||||
}:
|
||||
stdenvNoCC.mkDerivation {
|
||||
name = "node-modules";
|
||||
|
||||
nativeBuildInputs = [
|
||||
bun
|
||||
writableTmpDirAsHomeHook
|
||||
];
|
||||
|
||||
src = nix-gitignore.gitignoreSource [ ] ../../.;
|
||||
|
||||
dontConfigure = true;
|
||||
|
||||
# Required else we get errors that our fixed-output derivation references store paths
|
||||
dontFixup = true;
|
||||
|
||||
# Only install dependencies, don't build
|
||||
buildPhase = ''
|
||||
runHook preBuild
|
||||
|
||||
export BUN_INSTALL_CACHE_DIR=$(mktemp -d)
|
||||
|
||||
# Disable post-install scripts to avoid shebang issues
|
||||
bun install \
|
||||
--frozen-lockfile \
|
||||
--ignore-scripts \
|
||||
--backend copyfile \
|
||||
--no-progress \
|
||||
--force
|
||||
|
||||
runHook postBuild
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
|
||||
mkdir -p $out
|
||||
cp -r node_modules $out/
|
||||
|
||||
runHook postInstall
|
||||
'';
|
||||
|
||||
outputHashMode = "recursive";
|
||||
outputHashAlgo = "sha256";
|
||||
# NOTE: Empty this when a new dependency is added
|
||||
outputHash = "sha256-bbCaGoZRE7vRuAS3eRyP8yHANYXBJVaHmuL99BAovjY=";
|
||||
}
|
||||
@@ -27,7 +27,7 @@ buildDotnetModule rec {
|
||||
projectFile
|
||||
;
|
||||
name = "Sorcerer";
|
||||
pname = "Sorcerer";
|
||||
pname = name;
|
||||
dotnetRestoreFlags = "--force-evaluate";
|
||||
nugetDeps = deps {
|
||||
inherit
|
||||
@@ -44,6 +44,7 @@ buildDotnetModule rec {
|
||||
runtimeDeps = [
|
||||
pkgs.netcdf
|
||||
];
|
||||
buildType = env;
|
||||
# TODO: Add back when we have tests
|
||||
doCheck = false;
|
||||
buildType = env;
|
||||
}
|
||||
|
||||
19
nix/packages/sources.nix
Normal file
19
nix/packages/sources.nix
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"Drifters.Api" = "https://gitlab.com/api/v4/projects/37086336/packages/nuget/download";
|
||||
"Fable.Lit" = "https://gitlab.com/api/v4/projects/61744837/packages/nuget/download";
|
||||
"Fable.Lit.Elmish" = "https://gitlab.com/api/v4/projects/61744837/packages/nuget/download";
|
||||
"Fable.Lit.React" = "https://gitlab.com/api/v4/projects/61744837/packages/nuget/download";
|
||||
"Fable.OpenLayers" = "https://gitlab.com/api/v4/projects/36202053/packages/nuget/download";
|
||||
"Fable.SignalR" = "https://gitlab.com/api/v4/projects/40255650/packages/nuget/download";
|
||||
"Fable.SignalR.AspNetCore" = "https://gitlab.com/api/v4/projects/40255650/packages/nuget/download";
|
||||
"Fable.SignalR.Elmish" = "https://gitlab.com/api/v4/projects/40255650/packages/nuget/download";
|
||||
"Fable.SignalR.Saturn" = "https://gitlab.com/api/v4/projects/40255650/packages/nuget/download";
|
||||
"Fable.SignalR.Shared" = "https://gitlab.com/api/v4/projects/40255650/packages/nuget/download";
|
||||
"Matplotlib.ColorMaps" = "https://gitlab.com/api/v4/projects/36675671/packages/nuget/download";
|
||||
"Oceanbox.DataAgent" = "https://gitlab.com/api/v4/projects/37541600/packages/nuget/download";
|
||||
"Oceanbox.FvcomKit" = "https://gitlab.com/api/v4/projects/35569541/packages/nuget/download";
|
||||
"Oceanbox.ServerPack" = "https://gitlab.com/api/v4/projects/67427353/packages/nuget/download";
|
||||
"ProjNet.FSharp" = "https://gitlab.com/api/v4/projects/35009572/packages/nuget/download";
|
||||
"Oceanbox.SDSLite" = "https://gitlab.com/api/v4/projects/34025102/packages/nuget/download";
|
||||
}
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
let
|
||||
sources = import ../lon.nix;
|
||||
sources = import ./default.nix;
|
||||
pkgs = import sources.nixpkgs { };
|
||||
pre-commit = import sources.pre-commit;
|
||||
in
|
||||
pre-commit.run {
|
||||
src = ./.;
|
||||
# TODO: Do not run at pre-commit time
|
||||
# default_stages = [
|
||||
# "pre-push"
|
||||
# ];
|
||||
# NOTE: Do not run at pre-commit time
|
||||
default_stages = [
|
||||
"pre-push"
|
||||
];
|
||||
package = pkgs.prek;
|
||||
hooks = {
|
||||
nixfmt-rfc-style = {
|
||||
enable = true;
|
||||
@@ -22,7 +23,7 @@ pre-commit.run {
|
||||
# statix = {
|
||||
# enable = true;
|
||||
# package = pkgs.statix;
|
||||
# settings.ignore = [ "lon.nix" ];
|
||||
# settings.ignore = [ "../nix/default.nix" ];
|
||||
# };
|
||||
# TODO(mrtz): Format manually for now
|
||||
# fantomas = {
|
||||
|
||||
49
nix/sources.json
Normal file
49
nix/sources.json
Normal file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"pins": {
|
||||
"agenix": {
|
||||
"type": "Git",
|
||||
"repository": {
|
||||
"type": "GitHub",
|
||||
"owner": "ryantm",
|
||||
"repo": "agenix"
|
||||
},
|
||||
"branch": "main",
|
||||
"submodules": false,
|
||||
"revision": "fcdea223397448d35d9b31f798479227e80183f6",
|
||||
"url": "https://github.com/ryantm/agenix/archive/fcdea223397448d35d9b31f798479227e80183f6.tar.gz",
|
||||
"hash": "sha256-wyT7Pl6tMFbFrs8Lk/TlEs81N6L+VSybPfiIgzU8lbQ="
|
||||
},
|
||||
"nix-utils": {
|
||||
"type": "Git",
|
||||
"repository": {
|
||||
"type": "Git",
|
||||
"url": "https://git.sr.ht/~mrtz/nix-utils"
|
||||
},
|
||||
"branch": "trunk",
|
||||
"submodules": false,
|
||||
"revision": "098f594425d2b9dde0657becad0f6498d074f8b3",
|
||||
"url": null,
|
||||
"hash": "sha256-y++BijM+FRkKDhVrL7YXZQiJ0DNVMiRN7yHf6QIXBUI="
|
||||
},
|
||||
"nixpkgs": {
|
||||
"type": "Channel",
|
||||
"name": "nixpkgs-unstable",
|
||||
"url": "https://releases.nixos.org/nixpkgs/nixpkgs-26.05pre930822.ed142ab1b3a0/nixexprs.tar.xz",
|
||||
"hash": "sha256-XH6awru9NnBc/m+2YhRNT8r1PAKEiPGF3gs//F3ods0="
|
||||
},
|
||||
"pre-commit": {
|
||||
"type": "Git",
|
||||
"repository": {
|
||||
"type": "GitHub",
|
||||
"owner": "cachix",
|
||||
"repo": "git-hooks.nix"
|
||||
},
|
||||
"branch": "master",
|
||||
"submodules": false,
|
||||
"revision": "a1ef738813b15cf8ec759bdff5761b027e3e1d23",
|
||||
"url": "https://github.com/cachix/git-hooks.nix/archive/a1ef738813b15cf8ec759bdff5761b027e3e1d23.tar.gz",
|
||||
"hash": "sha256-Efs3VUPelRduf3PpfPP2ovEB4CXT7vHf8W+xc49RL/U="
|
||||
}
|
||||
},
|
||||
"version": 7
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
let
|
||||
sources = import ../lon.nix;
|
||||
system = builtins.currentSystem;
|
||||
pkgs = import sources.nixpkgs {
|
||||
inherit system;
|
||||
config = { };
|
||||
overlays = [ ];
|
||||
};
|
||||
nix-actions = import sources.nix-actions { inherit pkgs; };
|
||||
inherit (pkgs) lib;
|
||||
in
|
||||
nix-actions.install {
|
||||
src = ../.;
|
||||
platform = "github";
|
||||
workflows = lib.mapAttrs' (
|
||||
name: _:
|
||||
lib.nameValuePair (lib.removeSuffix ".nix" name) (
|
||||
let
|
||||
w = import ./workflows/${name};
|
||||
args = {
|
||||
inherit nix-actions;
|
||||
inherit (pkgs) lib;
|
||||
};
|
||||
in
|
||||
if (lib.isFunction w) then (w args) else w
|
||||
)
|
||||
) (builtins.readDir ./workflows);
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
{ nix-actions, ... }:
|
||||
let
|
||||
inherit (nix-actions.lib) expr secret;
|
||||
in
|
||||
{
|
||||
name = "Build and Checks";
|
||||
on = {
|
||||
push = {
|
||||
branches = [
|
||||
"main"
|
||||
"master"
|
||||
"review/**"
|
||||
];
|
||||
tags = [
|
||||
"v*.*.*"
|
||||
"*.*.*"
|
||||
];
|
||||
};
|
||||
pull_request = {
|
||||
branches = [
|
||||
"main"
|
||||
"master"
|
||||
"review/**"
|
||||
];
|
||||
};
|
||||
workflow_dispatch = { };
|
||||
};
|
||||
env = {
|
||||
FORCE_COLOR = "1";
|
||||
NETRC = secret "NETRC";
|
||||
};
|
||||
jobs = {
|
||||
build-nix = {
|
||||
runs-on = "ubuntu-latest";
|
||||
"if" =
|
||||
"!contains(github.event.head_commit.message, 'chore(release):') && !startsWith(github.event.head_commit.message, 'WIP:') && !startsWith(github.event.head_commit.message, 'wip:') && !contains(github.event.head_commit.message, '[ci skip]') && !startsWith(github.event.head_commit.message, 'skip:') && !startsWith(github.event.head_commit.message, 'ci skip:')";
|
||||
strategy = {
|
||||
matrix = {
|
||||
package = [
|
||||
"containers.atlantis"
|
||||
"containers.sorcerer"
|
||||
];
|
||||
};
|
||||
};
|
||||
steps = [
|
||||
{
|
||||
name = "Checkout";
|
||||
uses = "actions/checkout@v4";
|
||||
"with".fetch-depth = "0"; # Fetch all history for all branches and tags
|
||||
}
|
||||
{
|
||||
name = "Set up Netrc for Package Registry";
|
||||
run = "sudo mkdir -p /etc/nix && echo ${secret "NETRC"} | sudo tee /etc/nix/netrc > /dev/null";
|
||||
}
|
||||
{
|
||||
name = "Install Nix";
|
||||
uses = "DeterminateSystems/nix-installer-action@main";
|
||||
"with" = {
|
||||
github-token = secret "GITHUB_TOKEN";
|
||||
diagnostic-endpoint = "";
|
||||
source-url = "https://install.lix.systems/lix/lix-installer-x86_64-linux";
|
||||
extra-conf = ''
|
||||
experimental-features = pipe-operator
|
||||
'';
|
||||
};
|
||||
}
|
||||
{
|
||||
name = "Set up Cache";
|
||||
uses = "DeterminateSystems/magic-nix-cache-action@main";
|
||||
}
|
||||
{
|
||||
name = "Build ${expr "matrix.package"}";
|
||||
run = "nix-build default.nix -A ${expr "matrix.package"}";
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
{ nix-actions, ... }:
|
||||
let
|
||||
inherit (nix-actions.lib) nix-shell secret;
|
||||
in
|
||||
{
|
||||
name = "Update dependencies";
|
||||
on = {
|
||||
schedule = [
|
||||
# Run at 06:06 on Wednesday
|
||||
# This should avoid spikes in usage caused by other scheduled jobs
|
||||
{ cron = "6 6 * * 3"; }
|
||||
];
|
||||
# Allow manual trigger
|
||||
workflow_dispatch = { };
|
||||
};
|
||||
env = {
|
||||
FORCE_COLOR = "1";
|
||||
};
|
||||
jobs = {
|
||||
update = {
|
||||
runs-on = "ubuntu-latest";
|
||||
permissions = {
|
||||
contents = "write";
|
||||
pull-requests = "write";
|
||||
issues = "write";
|
||||
};
|
||||
steps = [
|
||||
{
|
||||
uses = "actions/checkout@v4";
|
||||
"with".fetch-depth = 0;
|
||||
}
|
||||
{
|
||||
name = "Set up Netrc for Package Registry";
|
||||
run = "sudo mkdir -p /etc/nix && echo ${secret "NETRC"} | sudo tee /etc/nix/netrc > /dev/null";
|
||||
}
|
||||
{
|
||||
name = "Install Nix";
|
||||
uses = "DeterminateSystems/nix-installer-action@main";
|
||||
"with" = {
|
||||
github-token = secret "GITHUB_TOKEN";
|
||||
diagnostic-endpoint = "";
|
||||
source-url = "https://install.lix.systems/lix/lix-installer-x86_64-linux";
|
||||
extra-conf = ''
|
||||
experimental-features = pipe-operator
|
||||
'';
|
||||
};
|
||||
}
|
||||
{
|
||||
env = {
|
||||
LON_TOKEN = secret "GITHUB_TOKEN";
|
||||
LON_USER_NAME = "Oceanbox [bot]";
|
||||
LON_USER_EMAIL = "bot@oceanbox.io";
|
||||
LON_LABELS = "bot";
|
||||
LON_LIST_COMMITS = true;
|
||||
};
|
||||
run = nix-shell {
|
||||
script = "lon bot github";
|
||||
shell = "lon-update";
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
81
package.json
81
package.json
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"version": "1.0.0",
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-node-resolve": "^16.0.1",
|
||||
"@semantic-release/changelog": "^6.0.3",
|
||||
@@ -8,7 +9,7 @@
|
||||
"@semantic-release/git": "^10.0.1",
|
||||
"@semantic-release/gitlab": "^13.2.8",
|
||||
"@sentry/vite-plugin": "^4.3.0",
|
||||
"@vitejs/plugin-react": "^5.0.0",
|
||||
"@vitejs/plugin-react": "^5.0.4",
|
||||
"rollup-plugin-scss": "^4.0.1",
|
||||
"sass": "^1.93.0",
|
||||
"semantic-release": "^24.2.9",
|
||||
@@ -17,46 +18,54 @@
|
||||
"vite-plugin-mkcert": "^1.17.8"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fluentui/react-components": "^9.72.9",
|
||||
"@fluentui/react-datepicker-compat": "^0.6.20",
|
||||
"@fluentui/react-calendar-compat": "^0.3.15",
|
||||
"@fluentui/react-timepicker-compat": "^0.4.26",
|
||||
"@fluentui-contrib/react-data-grid-react-window": "^1.4.2",
|
||||
"@fluentui/react-icons": "^2.0.316",
|
||||
"react-window": "^2.2.3",
|
||||
"@fortawesome/fontawesome-free": "^6.7.2",
|
||||
"@lit-labs/motion": "^1.0.9",
|
||||
"@lit/context": "^1.1.6",
|
||||
"@microsoft/signalr": "^8.0.17",
|
||||
"@sentry/browser": "^9.46.0",
|
||||
"@shoelace-style/shoelace": "^2.20.1",
|
||||
"@spectrum-web-components/accordion": "^1.7.0",
|
||||
"@spectrum-web-components/action-button": "^1.6.0",
|
||||
"@spectrum-web-components/action-group": "^1.7.0",
|
||||
"@spectrum-web-components/action-menu": "^1.7.0",
|
||||
"@spectrum-web-components/button": "^1.6.0",
|
||||
"@spectrum-web-components/card": "^1.7.0",
|
||||
"@spectrum-web-components/checkbox": "^1.6.0",
|
||||
"@spectrum-web-components/color-slider": "^1.7.0",
|
||||
"@spectrum-web-components/dialog": "^1.7.0",
|
||||
"@spectrum-web-components/divider": "^1.6.0",
|
||||
"@spectrum-web-components/field-group": "^1.6.0",
|
||||
"@spectrum-web-components/field-label": "^1.6.0",
|
||||
"@spectrum-web-components/icon": "^1.6.0",
|
||||
"@spectrum-web-components/menu": "^1.6.0",
|
||||
"@spectrum-web-components/number-field": "^1.6.0",
|
||||
"@spectrum-web-components/overlay": "^1.6.0",
|
||||
"@spectrum-web-components/popover": "^1.6.0",
|
||||
"@spectrum-web-components/progress-bar": "^1.7.0",
|
||||
"@spectrum-web-components/progress-circle": "^1.6.0",
|
||||
"@spectrum-web-components/radio": "^1.7.0",
|
||||
"@spectrum-web-components/slider": "^1.7.0",
|
||||
"@spectrum-web-components/split-view": "^1.7.0",
|
||||
"@spectrum-web-components/status-light": "^1.7.0",
|
||||
"@spectrum-web-components/styles": "^1.6.0",
|
||||
"@spectrum-web-components/switch": "^1.7.0",
|
||||
"@spectrum-web-components/table": "^1.7.0",
|
||||
"@spectrum-web-components/tabs": "^1.6.0",
|
||||
"@spectrum-web-components/theme": "^1.6.0",
|
||||
"@spectrum-web-components/toast": "^1.7.0",
|
||||
"@spectrum-web-components/tooltip": "^1.6.0",
|
||||
"@spectrum-web-components/top-nav": "^1.7.0",
|
||||
"@spectrum-web-components/underlay": "^1.6.0",
|
||||
"@spectrum-web-components/accordion": "^1.8.0",
|
||||
"@spectrum-web-components/action-button": "^1.8.0",
|
||||
"@spectrum-web-components/action-group": "^1.8.0",
|
||||
"@spectrum-web-components/action-menu": "^1.8.0",
|
||||
"@spectrum-web-components/alert-banner": "^1.8.0",
|
||||
"@spectrum-web-components/button": "^1.8.0",
|
||||
"@spectrum-web-components/card": "^1.8.0",
|
||||
"@spectrum-web-components/checkbox": "^1.8.0",
|
||||
"@spectrum-web-components/color-slider": "^1.8.0",
|
||||
"@spectrum-web-components/combobox": "^1.8.0",
|
||||
"@spectrum-web-components/contextual-help": "^1.8.0",
|
||||
"@spectrum-web-components/dialog": "^1.8.0",
|
||||
"@spectrum-web-components/divider": "^1.8.0",
|
||||
"@spectrum-web-components/field-group": "^1.8.0",
|
||||
"@spectrum-web-components/field-label": "^1.8.0",
|
||||
"@spectrum-web-components/icon": "^1.8.0",
|
||||
"@spectrum-web-components/menu": "^1.8.0",
|
||||
"@spectrum-web-components/number-field": "^1.8.0",
|
||||
"@spectrum-web-components/overlay": "^1.8.0",
|
||||
"@spectrum-web-components/popover": "^1.8.0",
|
||||
"@spectrum-web-components/progress-bar": "^1.8.0",
|
||||
"@spectrum-web-components/progress-circle": "^1.8.0",
|
||||
"@spectrum-web-components/radio": "^1.8.0",
|
||||
"@spectrum-web-components/slider": "^1.8.0",
|
||||
"@spectrum-web-components/split-view": "^1.8.0",
|
||||
"@spectrum-web-components/status-light": "^1.8.0",
|
||||
"@spectrum-web-components/styles": "^1.8.0",
|
||||
"@spectrum-web-components/switch": "^1.8.0",
|
||||
"@spectrum-web-components/table": "^1.8.0",
|
||||
"@spectrum-web-components/tabs": "^1.8.0",
|
||||
"@spectrum-web-components/theme": "^1.8.0",
|
||||
"@spectrum-web-components/toast": "^1.8.0",
|
||||
"@spectrum-web-components/tooltip": "^1.8.0",
|
||||
"@spectrum-web-components/top-nav": "^1.8.0",
|
||||
"@spectrum-web-components/underlay": "^1.8.0",
|
||||
"@turf/bezier-spline": "^7.2.0",
|
||||
"@vaadin/login": "^24.9.0",
|
||||
"lit": "^3.3.1",
|
||||
"lit-html": "^3.3.1",
|
||||
"ol": "^10.6.1",
|
||||
@@ -65,6 +74,6 @@
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-plotly.js": "^2.6.0",
|
||||
"vis-timeline": "^7.7.4"
|
||||
"vis-timeline": "^8.4.0"
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
Development helper scripts.
|
||||
|
||||
```
|
||||
```shell
|
||||
├── update-deps.sh
|
||||
│ └─ Updates dependencies for the Poseidon project, including both .NET and npm.
|
||||
├── configure-manifests.sh
|
||||
|
||||
101
scripts/update-rider.sh
Executable file
101
scripts/update-rider.sh
Executable file
@@ -0,0 +1,101 @@
|
||||
#!/usr/bin/env nix-shell
|
||||
#! nix-shell -i bash --pure
|
||||
#! nix-shell -p bash which xmlstarlet
|
||||
|
||||
if [[ ! $# -eq 1 ]]; then
|
||||
echo "Usage: $0 <dotnet-path>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
dotnet_path=$1
|
||||
|
||||
function stderr() {
|
||||
echo "$@" 1>&2;
|
||||
}
|
||||
|
||||
function create_settings_file() {
|
||||
cat << EOF
|
||||
<?xml version="1.0"?>
|
||||
<wpf:ResourceDictionary
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:s="clr-namespace:System;assembly=mscorlib"
|
||||
xmlns:ss="urn:schemas-jetbrains-com:settings-storage-xaml"
|
||||
xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xml:space="preserve"
|
||||
>
|
||||
<s:String x:Key="/Default/Environment/Hierarchy/Build/BuildTool/DotNetCliExePath/@EntryValue">${1}</s:String>
|
||||
<s:String x:Key="/Default/Environment/Hierarchy/Build/BuildTool/CustomBuildToolPath/@EntryValue">${2}</s:String>
|
||||
</wpf:ResourceDictionary>
|
||||
EOF
|
||||
}
|
||||
|
||||
# HACK: Configure Rider to use the correct .NET paths from an ambient .NET
|
||||
function use_rider_dotnet() {
|
||||
local solution_file=$(find . -maxdepth 1 -type f -name '*.slnx' | cut -d'.' -f2 | cut -d'/' -f2)
|
||||
local settings_file=$(find . -maxdepth 1 -type f -name '*.sln.DotSettings.user')
|
||||
# Get paths
|
||||
local cli_path=$(realpath "$dotnet_path")
|
||||
local dir=$(dirname $cli_path)
|
||||
local msbuild_path=$(find "$dir" -maxdepth 3 -type f -name MSBuild.dll)
|
||||
|
||||
# stderr "dotnet path is $dir"
|
||||
# stderr "Found msbuild: $msbuild_path"
|
||||
|
||||
if [ -f "$settings_file" ] ; then
|
||||
# stderr "Updating rider settings file: $settings_file"
|
||||
# stderr "Setting DotNetCliExePath to $cli_path"
|
||||
|
||||
# NOTE: check if dotnet binary in share folder settings exists
|
||||
xml sel -t -v "wpf:ResourceDictionary/s:String[@x:Key='/Default/Environment/Hierarchy/Build/BuildTool/DotNetCliExePath/@EntryValue']" "$settings_file"
|
||||
if [[ $? -eq 0 ]]; then
|
||||
xml ed --inplace \
|
||||
-N wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation" \
|
||||
-N x="http://schemas.microsoft.com/winfx/2006/xaml" \
|
||||
-N s="clr-namespace:System;assembly=mscorlib" \
|
||||
-N ss="urn:schemas-jetbrains-com:settings-storage-xaml" \
|
||||
--update "//s:String[@x:Key='/Default/Environment/Hierarchy/Build/BuildTool/DotNetCliExePath/@EntryValue']" \
|
||||
--value "$cli_path" \
|
||||
"$settings_file"
|
||||
else
|
||||
xml ed --inplace \
|
||||
-N wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation" \
|
||||
-N x="http://schemas.microsoft.com/winfx/2006/xaml" \
|
||||
-N s="clr-namespace:System;assembly=mscorlib" \
|
||||
-N ss="urn:schemas-jetbrains-com:settings-storage-xaml" \
|
||||
-s /wpf:ResourceDictionary -t elem -n s:String -v "$cli_path" \
|
||||
--var new_node '$prev' \
|
||||
-i '$new_node' -t attr -n "x:Key" -v "/Default/Environment/Hierarchy/Build/BuildTool/DotNetCliExePath/@EntryValue" \
|
||||
"$settings_file"
|
||||
fi
|
||||
|
||||
xml sel -t -v "wpf:ResourceDictionary/s:String[@x:Key='/Default/Environment/Hierarchy/Build/BuildTool/CustomBuildToolPath/@EntryValue']" "$settings_file"
|
||||
if [[ $? -eq 0 ]]; then
|
||||
xmlstarlet ed --inplace \
|
||||
-N wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation" \
|
||||
-N x="http://schemas.microsoft.com/winfx/2006/xaml" \
|
||||
-N s="clr-namespace:System;assembly=mscorlib" \
|
||||
-N ss="urn:schemas-jetbrains-com:settings-storage-xaml" \
|
||||
--update "//s:String[@x:Key='/Default/Environment/Hierarchy/Build/BuildTool/CustomBuildToolPath/@EntryValue']" \
|
||||
--value "$msbuild_path" \
|
||||
"$settings_file"
|
||||
else
|
||||
xml ed --inplace \
|
||||
-N wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation" \
|
||||
-N x="http://schemas.microsoft.com/winfx/2006/xaml" \
|
||||
-N s="clr-namespace:System;assembly=mscorlib" \
|
||||
-N ss="urn:schemas-jetbrains-com:settings-storage-xaml" \
|
||||
-s /wpf:ResourceDictionary -t elem -n s:String -v "$cli_path" \
|
||||
--var new_node '$prev' \
|
||||
-i '$new_node' -t attr -n "x:Key" -v "/Default/Environment/Hierarchy/Build/BuildTool/CustomBuildToolPath/@EntryValue" \
|
||||
"$settings_file"
|
||||
fi
|
||||
else
|
||||
create_settings_file $cli_path $msbuild_path > "$solution_file.sln.DotSettings.user"
|
||||
fi
|
||||
}
|
||||
|
||||
function main() {
|
||||
use_rider_dotnet
|
||||
}
|
||||
|
||||
main
|
||||
60
shell.nix
60
shell.nix
@@ -1,56 +1,68 @@
|
||||
{
|
||||
sources ? import ./lon.nix,
|
||||
sources ? import ./nix,
|
||||
pkgs ? import sources.nixpkgs { },
|
||||
pre-commit ? import ./nix/pre-commit.nix,
|
||||
workflows ? import ./nix/workflows.nix,
|
||||
}:
|
||||
let
|
||||
# NOTE(mrtz): Should match the version in `default.nix`
|
||||
dotnet-sdk = pkgs.dotnetCorePackages.sdk_9_0;
|
||||
|
||||
dotnet-sdk = pkgs.dotnetCorePackages.sdk_10_0;
|
||||
agenix = pkgs.callPackage "${sources.agenix}/pkgs/agenix.nix" { };
|
||||
fable = pkgs.buildDotnetGlobalTool {
|
||||
pname = "fable";
|
||||
version = "4.24.0";
|
||||
nugetHash = "sha256-ERewWqfEyyZKpHFFALpMGJT0fDWywBYY5buU/wTZZTg=";
|
||||
};
|
||||
in
|
||||
pkgs.mkShellNoCC {
|
||||
buildInputs = [ dotnet-sdk ];
|
||||
|
||||
packages = with pkgs; [
|
||||
# FSharp
|
||||
packages = [
|
||||
# F#
|
||||
fable
|
||||
dotnet-outdated
|
||||
fantomas
|
||||
fsautocomplete
|
||||
pkgs.dotnet-outdated
|
||||
pkgs.fantomas
|
||||
pkgs.fsautocomplete
|
||||
|
||||
# JavaScript
|
||||
bun
|
||||
nodejs-slim
|
||||
pkgs.bun
|
||||
pkgs.nodejs_25
|
||||
|
||||
# Devlopment tools
|
||||
lon
|
||||
mkcert
|
||||
dive
|
||||
nix-output-monitor
|
||||
pkgs.npins
|
||||
pkgs.mkcert
|
||||
pkgs.dive
|
||||
pkgs.nix-output-monitor
|
||||
pkgs.just
|
||||
pkgs.skopeo
|
||||
|
||||
# Secret management with agenix
|
||||
agenix
|
||||
|
||||
# Kubernetes tools
|
||||
tilt
|
||||
dapr-cli
|
||||
kustomize
|
||||
kubernetes-helm
|
||||
pkgs.tilt
|
||||
pkgs.dapr-cli
|
||||
pkgs.kustomize
|
||||
pkgs.kubernetes-helm
|
||||
];
|
||||
|
||||
# Environment variables
|
||||
DOTNET_ROOT = "${dotnet-sdk}/share/dotnet";
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT = "true";
|
||||
LOG_LEVEL = "verbose";
|
||||
|
||||
shellHook = ''
|
||||
scripts/update-rider.sh ${dotnet-sdk}/bin/dotnet
|
||||
'';
|
||||
|
||||
# Alternative shells
|
||||
passthru = pkgs.lib.mapAttrs (name: value: pkgs.mkShellNoCC (value // { inherit name; })) {
|
||||
pre-commit.shellHook = pre-commit.shellHook;
|
||||
workflows.shellHook = workflows.shellHook;
|
||||
lon-update.packages = [ pkgs.lon ];
|
||||
dotnet-shell.packages = [ dotnet-sdk ];
|
||||
ci-shell = {
|
||||
packages = [
|
||||
pkgs.npins
|
||||
];
|
||||
shellHook = ''
|
||||
export NPINS_DIRECTORY="nix"
|
||||
'';
|
||||
};
|
||||
agenix-gen = {
|
||||
packages = [ agenix ];
|
||||
shellHook = ''
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
open Fake.Core
|
||||
open Fake.IO
|
||||
open Farmer
|
||||
open Farmer.Builders
|
||||
|
||||
open Helpers
|
||||
|
||||
initializeContext()
|
||||
|
||||
let clientPath = Path.getFullName "src/Client"
|
||||
let cliPath = Path.getFullName "src/Cli"
|
||||
let testPath = Path.getFullName "tests"
|
||||
|
||||
let distPath = Path.getFullName "dist"
|
||||
|
||||
let vite = $"bunx --bun vite -c ../../vite.config.js"
|
||||
let viteBundle = $"{vite} build --outDir {distPath}/public"
|
||||
|
||||
Target.create "Clean" (fun _ -> Shell.cleanDir distPath)
|
||||
|
||||
// Target.create "Bundle" (fun _ ->
|
||||
// let vite = $"{viteBundle} -m production"
|
||||
// run dotnet $"publish -c Release -o \"{distPath}\"" serverPath
|
||||
// run fable $"-o build/client --run {vite}" clientPath
|
||||
// )
|
||||
|
||||
// Target.create "BundleDebug" (fun _ ->
|
||||
// let vite = $"{viteBundle} -m development --minify false"
|
||||
// run dotnet $"publish -c Debug -o \"{distPath}\"" serverPath
|
||||
// run fable $"-o build/client --run {vite}" clientPath
|
||||
// )
|
||||
|
||||
Target.create "Bundle" (fun _ ->
|
||||
run dotnet $"publish -c Release -o \"{distPath}\"" cliPath
|
||||
)
|
||||
|
||||
Target.create "BundleDebug" (fun _ ->
|
||||
run dotnet $"publish -c Debug -o \"{distPath}\"" cliPath
|
||||
)
|
||||
|
||||
Target.create "Format" (fun _ ->
|
||||
run fantomas ". -r" "src"
|
||||
)
|
||||
|
||||
Target.create "Test" (fun _ ->
|
||||
if System.IO.Directory.Exists testPath then
|
||||
run dotnet "run" testPath
|
||||
else ()
|
||||
)
|
||||
|
||||
Target.create "Run" (fun _ -> Target.runOrDefault "Bundle")
|
||||
|
||||
open Fake.Core.TargetOperators
|
||||
|
||||
let dependencies = [
|
||||
"Clean"
|
||||
==> "Bundle"
|
||||
|
||||
"Clean"
|
||||
==> "BundleDebug"
|
||||
|
||||
"Clean"
|
||||
==> "Test"
|
||||
]
|
||||
|
||||
[<EntryPoint>]
|
||||
let main args = runOrDefault args
|
||||
@@ -1,116 +0,0 @@
|
||||
module Helpers
|
||||
|
||||
open Fake.Core
|
||||
|
||||
let initializeContext () =
|
||||
let execContext = Context.FakeExecutionContext.Create false "build.fsx" [ ]
|
||||
Context.setExecutionContext (Context.RuntimeContext.Fake execContext)
|
||||
|
||||
module Proc =
|
||||
module Parallel =
|
||||
open System
|
||||
|
||||
let locker = obj()
|
||||
|
||||
let colors =
|
||||
[| ConsoleColor.Blue
|
||||
ConsoleColor.Yellow
|
||||
ConsoleColor.Magenta
|
||||
ConsoleColor.Cyan
|
||||
ConsoleColor.DarkBlue
|
||||
ConsoleColor.DarkYellow
|
||||
ConsoleColor.DarkMagenta
|
||||
ConsoleColor.DarkCyan |]
|
||||
|
||||
let print color (colored: string) (line: string) =
|
||||
lock locker
|
||||
(fun () ->
|
||||
let currentColor = Console.ForegroundColor
|
||||
Console.ForegroundColor <- color
|
||||
Console.Write colored
|
||||
Console.ForegroundColor <- currentColor
|
||||
Console.WriteLine line)
|
||||
|
||||
let onStdout index name (line: string) =
|
||||
let color = colors.[index % colors.Length]
|
||||
if isNull line then
|
||||
print color $"{name}: --- END ---" ""
|
||||
else if String.isNotNullOrEmpty line then
|
||||
print color $"{name}: " line
|
||||
|
||||
let onStderr name (line: string) =
|
||||
let color = ConsoleColor.Red
|
||||
if isNull line |> not then
|
||||
print color $"{name}: " line
|
||||
|
||||
let redirect (index, (name, createProcess)) =
|
||||
createProcess
|
||||
|> CreateProcess.redirectOutputIfNotRedirected
|
||||
|> CreateProcess.withOutputEvents (onStdout index name) (onStderr name)
|
||||
|
||||
let printStarting indexed =
|
||||
for (index, (name, c: CreateProcess<_>)) in indexed do
|
||||
let color = colors.[index % colors.Length]
|
||||
let wd =
|
||||
c.WorkingDirectory
|
||||
|> Option.defaultValue ""
|
||||
let exe = c.Command.Executable
|
||||
let args = c.Command.Arguments.ToStartInfo
|
||||
print color $"{name}: {wd}> {exe} {args}" ""
|
||||
|
||||
let run cs =
|
||||
cs
|
||||
|> Seq.toArray
|
||||
|> Array.indexed
|
||||
|> fun x -> printStarting x; x
|
||||
|> Array.map redirect
|
||||
|> Array.Parallel.map Proc.run
|
||||
|
||||
let createProcess exe arg dir =
|
||||
CreateProcess.fromRawCommandLine exe arg
|
||||
|> CreateProcess.withWorkingDirectory dir
|
||||
|> CreateProcess.ensureExitCode
|
||||
|
||||
let dotnet = createProcess "dotnet"
|
||||
|
||||
let fable = createProcess "fable"
|
||||
|
||||
let fantomas = createProcess "fantomas"
|
||||
|
||||
type BundleMode =
|
||||
| Prod
|
||||
| Devel
|
||||
| Watch
|
||||
with
|
||||
override this.ToString() =
|
||||
match this with
|
||||
| Prod -> "production"
|
||||
| Devel -> "development"
|
||||
| Watch -> "watch"
|
||||
|
||||
let viteCmd (m: BundleMode) outDir =
|
||||
match m with
|
||||
| Prod -> $"vite build -c ../../vite.config.js -m {m} --emptyOutDir --outDir {outDir}/public"
|
||||
| Devel -> $"vite build -c ../../vite.config.js -m {m} --minify false --sourcemap true --emptyOutDir --outDir {outDir}/public"
|
||||
| Watch -> "vite -c ../../vite.config.js"
|
||||
|
||||
let run proc arg dir =
|
||||
proc arg dir
|
||||
|> Proc.run
|
||||
|> ignore
|
||||
|
||||
let runParallel processes =
|
||||
processes
|
||||
|> Proc.Parallel.run
|
||||
|> ignore
|
||||
|
||||
let runOrDefault args =
|
||||
try
|
||||
match args with
|
||||
| [| target |] -> Target.runOrDefault target
|
||||
| _ ->
|
||||
Target.runOrDefault "Run"
|
||||
0
|
||||
with e ->
|
||||
printfn "%A" e
|
||||
1
|
||||
15
src/Archivist/.gitlab-ci.yml
Normal file
15
src/Archivist/.gitlab-ci.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
# yaml-language-server: $schema=https://gitlab.com/gitlab-org/gitlab/-/raw/master/app/assets/javascripts/editor/schema/ci.json
|
||||
variables:
|
||||
SKIP_TESTS: "true"
|
||||
|
||||
include:
|
||||
- project: oceanbox/gitlab-ci
|
||||
ref: v4.5
|
||||
file: DotnetDeployment.gitlab-ci.yml
|
||||
inputs:
|
||||
project-name: archivist
|
||||
project-dir: src/Cli
|
||||
|
||||
dockerize-archivist:
|
||||
tags:
|
||||
- nix
|
||||
@@ -1,17 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include=".build/Helpers.fs" />
|
||||
<Compile Include=".build/Build.fs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Fake.Core.Target" Version="6.1.3" />
|
||||
<PackageReference Include="Fake.DotNet.Cli" Version="6.1.3" />
|
||||
<PackageReference Include="Fake.IO.FileSystem" Version="6.1.3" />
|
||||
<PackageReference Include="Farmer" Version="1.9.10" />
|
||||
<PackageReference Update="FSharp.Core" Version="9.0.201" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
40
src/Archivist/justfile
Normal file
40
src/Archivist/justfile
Normal file
@@ -0,0 +1,40 @@
|
||||
# Archivist build commands
|
||||
# Install just: https://github.com/casey/just
|
||||
|
||||
set dotenv-load
|
||||
|
||||
src_path := "src"
|
||||
client_path := "src/Client"
|
||||
cli_path := "src/Cli"
|
||||
test_path := "tests"
|
||||
dist_path := "dist"
|
||||
|
||||
# Default recipe - show available commands
|
||||
default:
|
||||
@just --list
|
||||
|
||||
# Clean build artifacts
|
||||
clean:
|
||||
rm -rf {{dist_path}}
|
||||
|
||||
# Build production bundle
|
||||
bundle: clean
|
||||
dotnet publish -c Release -o {{dist_path}} {{cli_path}}
|
||||
|
||||
# Build debug bundle
|
||||
bundle-debug: clean
|
||||
dotnet publish -c Debug -o {{dist_path}} {{cli_path}}
|
||||
|
||||
# Format code with Fantomas
|
||||
format:
|
||||
fantomas {{src_path}} -r
|
||||
|
||||
# Run tests
|
||||
test: clean
|
||||
#!/usr/bin/env bash
|
||||
if [ -d "{{test_path}}" ]; then
|
||||
dotnet run {{test_path}}
|
||||
fi
|
||||
|
||||
# Run (builds bundle)
|
||||
run: bundle
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
sources ? import ./../../lon.nix,
|
||||
sources ? import ./../../nix,
|
||||
pkgs ? import sources.nixpkgs { },
|
||||
}:
|
||||
let
|
||||
@@ -29,7 +29,7 @@ pkgs.mkShellNoCC {
|
||||
SERVER_PORT = port + 85;
|
||||
TILT_PORT = port + 50;
|
||||
|
||||
DOTNET_ROOT = "${pkgs.dotnetCorePackages.sdk_9_0}/share/dotnet";
|
||||
DOTNET_ROOT = "${pkgs.dotnetCorePackages.sdk_10_0}/share/dotnet";
|
||||
|
||||
shellHook = ''
|
||||
export PATH="$PWD/src/Cli/bin/Release/net9.0/linux-x64/:$PATH"
|
||||
|
||||
@@ -74,7 +74,11 @@ let addUsers (args: PrincipalArgs) =
|
||||
|
||||
async {
|
||||
try
|
||||
match! aclApi.addUsers users with
|
||||
let req : Archmaester.AddUsersRequest = {
|
||||
group = ""
|
||||
users = users
|
||||
}
|
||||
match! aclApi.addUsers req with
|
||||
| Ok _ ->
|
||||
Log.Information $"Added users %A{users}"
|
||||
return Ok ()
|
||||
|
||||
@@ -631,7 +631,6 @@ let instantiateArchiveDto (idx, modelArea, basePath, files, reverse, json, publi
|
||||
}
|
||||
|
||||
let retireArchive (archive: string) =
|
||||
// TODO: retire all dependent archies
|
||||
let aid =
|
||||
try
|
||||
Guid.Parse archive
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
||||
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
|
||||
<AssemblyName>archivist</AssemblyName>
|
||||
@@ -19,23 +19,22 @@
|
||||
<Compile Include="Main.fs"/>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Fargo.CmdLine" Version="1.7.5"/>
|
||||
<PackageReference Include="FSharp.Data" Version="6.4.1"/>
|
||||
<PackageReference Include="FSharpPlus" Version="1.7.0"/>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.1"/>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.1">
|
||||
<PackageReference Include="Fargo.CmdLine" />
|
||||
<PackageReference Include="FSharp.Data" />
|
||||
<PackageReference Include="FSharpPlus" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" >
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.1">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" >
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Serilog" Version="4.2.0"/>
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0"/>
|
||||
<PackageReference Include="Serilog.Sinks.Seq" Version="9.0.0"/>
|
||||
<PackageReference Include="Thoth.Json.Net" Version="12.0.0"/>
|
||||
<PackageReference Update="FSharp.Core" Version="9.0.201"/>
|
||||
<PackageReference Include="Serilog" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" />
|
||||
<PackageReference Include="Thoth.Json.Net" />
|
||||
<PackageReference Include="FSharp.Core" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\DataAgent\src\DataAgent\Oceanbox.DataAgent.fsproj"/>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"version": 1,
|
||||
"version": 2,
|
||||
"dependencies": {
|
||||
"net9.0": {
|
||||
"net10.0": {
|
||||
"Fargo.CmdLine": {
|
||||
"type": "Direct",
|
||||
"requested": "[1.7.5, )",
|
||||
@@ -13,9 +13,9 @@
|
||||
},
|
||||
"FSharp.Core": {
|
||||
"type": "Direct",
|
||||
"requested": "[9.0.201, )",
|
||||
"resolved": "9.0.201",
|
||||
"contentHash": "Ozq4T0ISTkqTYJ035XW/JkdDDaXofbykvfyVwkjLSqaDZ/4uNXfpf92cjcMI9lf9CxWqmlWHScViPh/4AvnWcw=="
|
||||
"requested": "[9.0.303, )",
|
||||
"resolved": "9.0.303",
|
||||
"contentHash": "6JlV8aD8qQvcmfoe/PMOxCHXc0uX4lR23u0fAyQtnVQxYULLoTZgwgZHSnRcuUHOvS3wULFWcwdnP1iwslH60g=="
|
||||
},
|
||||
"FSharp.Data": {
|
||||
"type": "Direct",
|
||||
@@ -71,8 +71,7 @@
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "9.0.1",
|
||||
"Microsoft.Extensions.DependencyModel": "9.0.1",
|
||||
"Microsoft.Extensions.Logging": "9.0.1",
|
||||
"Mono.TextTemplating": "3.0.0",
|
||||
"System.Text.Json": "9.0.1"
|
||||
"Mono.TextTemplating": "3.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.EntityFrameworkCore.Tools": {
|
||||
@@ -99,16 +98,6 @@
|
||||
"Serilog": "4.0.0"
|
||||
}
|
||||
},
|
||||
"Serilog.Sinks.Seq": {
|
||||
"type": "Direct",
|
||||
"requested": "[9.0.0, )",
|
||||
"resolved": "9.0.0",
|
||||
"contentHash": "aNU8A0K322q7+voPNmp1/qNPH+9QK8xvM1p72sMmCG0wGlshFzmtDW9QnVSoSYCj0MgQKcMOlgooovtBhRlNHw==",
|
||||
"dependencies": {
|
||||
"Serilog": "4.2.0",
|
||||
"Serilog.Sinks.File": "6.0.0"
|
||||
}
|
||||
},
|
||||
"Thoth.Json.Net": {
|
||||
"type": "Direct",
|
||||
"requested": "[12.0.0, )",
|
||||
@@ -125,55 +114,6 @@
|
||||
"resolved": "2.1.35",
|
||||
"contentHash": "YKRwjVfrG7GYOovlGyQoMvr1/IJdn+7QzNXJxyMh0YfFF5yvDmTYaJOVYWsckreNjGsGSEtrMTpnzxTUq/tZQw=="
|
||||
},
|
||||
"Dapper.FSharp": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.9.0",
|
||||
"contentHash": "wqMi/wHSQV9v79/u8OELxO+lmUOxk3J5CAUuAmWbltbIYH0A64CV1z1RG+9EVpyAAD9bovKYAnQ2wNwDoPxTxA==",
|
||||
"dependencies": {
|
||||
"Dapper": "2.1.35",
|
||||
"FSharp.Core": "8.0.200"
|
||||
}
|
||||
},
|
||||
"Dapr.Actors": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.16.0",
|
||||
"contentHash": "s9v6VofXXYoRqZJQlQbvNYYSlGhkL+Z+bpqrx1TRo06kLhANeDmXA9yeVaD+1KwJIO1chUFj5O4iKuTxIkg1sA==",
|
||||
"dependencies": {
|
||||
"Dapr.Client": "1.16.0",
|
||||
"Dapr.Common": "1.16.0",
|
||||
"Google.Api.CommonProtos": "2.17.0",
|
||||
"Google.Protobuf": "3.32.0",
|
||||
"Grpc.Net.Client": "2.71.0",
|
||||
"Microsoft.Extensions.Configuration": "9.0.8",
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "9.0.8",
|
||||
"Microsoft.Extensions.DependencyInjection": "9.0.8",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.8",
|
||||
"Microsoft.Extensions.Http": "9.0.8",
|
||||
"Microsoft.Extensions.Logging": "9.0.8",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.8",
|
||||
"Microsoft.Extensions.Options": "9.0.8"
|
||||
}
|
||||
},
|
||||
"Dapr.Client": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.16.0",
|
||||
"contentHash": "dFDKol+mtQrk1lIKlEyCx3k6W0Pf+0wC6xcsaDqa0Bg+XCWDc4juROuDcSb0/L1Y+Ev6LSLDMC/FgzNWMw9YtQ==",
|
||||
"dependencies": {
|
||||
"Dapr.Common": "1.16.0",
|
||||
"Dapr.Protos": "1.16.0",
|
||||
"Google.Api.CommonProtos": "2.17.0",
|
||||
"Google.Protobuf": "3.32.0",
|
||||
"Grpc.Net.Client": "2.71.0",
|
||||
"Microsoft.Extensions.Configuration": "9.0.8",
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "9.0.8",
|
||||
"Microsoft.Extensions.DependencyInjection": "9.0.8",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.8",
|
||||
"Microsoft.Extensions.Http": "9.0.8",
|
||||
"Microsoft.Extensions.Logging": "9.0.8",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.8",
|
||||
"Microsoft.Extensions.Options": "9.0.8"
|
||||
}
|
||||
},
|
||||
"Dapr.Common": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.16.0",
|
||||
@@ -210,24 +150,6 @@
|
||||
"resolved": "0.9.1",
|
||||
"contentHash": "n21+Hd+tceX8lgaOosPV+Pne+YqnZUd5RLW3OhnsVxWRzYXiAIAKmKweHIePYeY+fmcn3N5tjkJyQUccFuL3bg=="
|
||||
},
|
||||
"Fable.Core": {
|
||||
"type": "Transitive",
|
||||
"resolved": "3.1.6",
|
||||
"contentHash": "w6M1F0zoLk4kTFc1Lx6x1Ft6BD3QwRe0eaLiinAqbjVkcF+iK+NiXGJO+a6q9RAF9NCg0vI48Xku7aNeqG4JVw==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "4.7.1"
|
||||
}
|
||||
},
|
||||
"Fable.Remoting.DotnetClient": {
|
||||
"type": "Transitive",
|
||||
"resolved": "3.35.0",
|
||||
"contentHash": "xaxt9nKfqIWh30+cOrG9GNl06+7yTy5htrcF5eXsZ7QJLLy7T5ZD3xeGpAb0xbh+TZTVQGluSQzxh/opIZm2PQ==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "6.0.0",
|
||||
"Fable.Remoting.Json": "2.25.0",
|
||||
"Fable.Remoting.MsgPack": "1.24.0"
|
||||
}
|
||||
},
|
||||
"Fable.Remoting.Json": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.25.0",
|
||||
@@ -237,14 +159,6 @@
|
||||
"Newtonsoft.Json": "13.0.3"
|
||||
}
|
||||
},
|
||||
"Fable.Remoting.MsgPack": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.24.0",
|
||||
"contentHash": "Bn3nzoZbib6lPk70bIJumEu2wFMxciB4o8k0Zw6tRfAOpNKvUsi79OOll2nW3FU1P6MVBepT43m+R8JvfYnNiw==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "4.7.2"
|
||||
}
|
||||
},
|
||||
"FSharp.Data.Csv.Core": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.4.1",
|
||||
@@ -318,8 +232,7 @@
|
||||
"resolved": "5.3.2",
|
||||
"contentHash": "LFtxXpQNor8az1ez3rN9oz2cqf/06i9yTrPyJ9R83qLEpFAU7Of0WL2hoSXzLHer4lh+6mO1NV4VQFiBzNRtjw==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "4.3.2",
|
||||
"System.Reflection.Emit.Lightweight": "4.3.0"
|
||||
"FSharp.Core": "4.3.2"
|
||||
}
|
||||
},
|
||||
"Google.Api.CommonProtos": {
|
||||
@@ -381,16 +294,6 @@
|
||||
"MathNet.Numerics": "5.0.0"
|
||||
}
|
||||
},
|
||||
"MessagePack": {
|
||||
"type": "Transitive",
|
||||
"resolved": "3.1.3",
|
||||
"contentHash": "UiNv3fknvPzh5W+S0VV96R17RBZQQU71qgmsMnjjRZU2rtQM/XcTnOB+klT2dA6T1mxjnNKYrEm164AoXvGmYg==",
|
||||
"dependencies": {
|
||||
"MessagePack.Annotations": "3.1.3",
|
||||
"MessagePackAnalyzer": "3.1.3",
|
||||
"Microsoft.NET.StringTools": "17.11.4"
|
||||
}
|
||||
},
|
||||
"MessagePack.Annotations": {
|
||||
"type": "Transitive",
|
||||
"resolved": "3.1.3",
|
||||
@@ -426,10 +329,7 @@
|
||||
"resolved": "4.8.0",
|
||||
"contentHash": "/jR+e/9aT+BApoQJABlVCKnnggGQbvGh7BKq2/wI1LamxC+LbzhcLj4Vj7gXCofl1n4E521YfF9w0WcASGg/KA==",
|
||||
"dependencies": {
|
||||
"Microsoft.CodeAnalysis.Analyzers": "3.3.4",
|
||||
"System.Collections.Immutable": "7.0.0",
|
||||
"System.Reflection.Metadata": "7.0.0",
|
||||
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
|
||||
"Microsoft.CodeAnalysis.Analyzers": "3.3.4"
|
||||
}
|
||||
},
|
||||
"Microsoft.CodeAnalysis.CSharp": {
|
||||
@@ -459,9 +359,7 @@
|
||||
"Humanizer.Core": "2.14.1",
|
||||
"Microsoft.Bcl.AsyncInterfaces": "7.0.0",
|
||||
"Microsoft.CodeAnalysis.Common": "[4.8.0]",
|
||||
"System.Composition": "7.0.0",
|
||||
"System.IO.Pipelines": "7.0.0",
|
||||
"System.Threading.Channels": "7.0.0"
|
||||
"System.Composition": "7.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.CodeAnalysis.Workspaces.MSBuild": {
|
||||
@@ -471,8 +369,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Build.Framework": "16.10.0",
|
||||
"Microsoft.CodeAnalysis.Common": "[4.8.0]",
|
||||
"Microsoft.CodeAnalysis.Workspaces.Common": "[4.8.0]",
|
||||
"System.Text.Json": "7.0.3"
|
||||
"Microsoft.CodeAnalysis.Workspaces.Common": "[4.8.0]"
|
||||
}
|
||||
},
|
||||
"Microsoft.EntityFrameworkCore.Abstractions": {
|
||||
@@ -485,17 +382,6 @@
|
||||
"resolved": "9.0.1",
|
||||
"contentHash": "c6ZZJZhPKrXFkE2z/81PmuT69HBL6Y68Cl0xJ5SRrDjJyq5Aabkq15yCqPg9RQ3R0aFLVaJok2DA8R3TKpejDQ=="
|
||||
},
|
||||
"Microsoft.EntityFrameworkCore.Relational": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.1",
|
||||
"contentHash": "7Iu0h4oevRvH4IwPzmxuIJGYRt55TapoREGlluk75KCO7lenN0+QnzCl6cQDY48uDoxAUpJbpK2xW7o8Ix69dw==",
|
||||
"dependencies": {
|
||||
"Microsoft.EntityFrameworkCore": "9.0.1",
|
||||
"Microsoft.Extensions.Caching.Memory": "9.0.1",
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "9.0.1",
|
||||
"Microsoft.Extensions.Logging": "9.0.1"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Caching.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.1",
|
||||
@@ -640,16 +526,6 @@
|
||||
"resolved": "17.11.4",
|
||||
"contentHash": "mudqUHhNpeqIdJoUx2YDWZO/I9uEDYVowan89R6wsomfnUJQk6HteoQTlNjZDixhT2B4IXMkMtgZtoceIjLRmA=="
|
||||
},
|
||||
"Microsoft.NETCore.Platforms": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.1.0",
|
||||
"contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A=="
|
||||
},
|
||||
"Microsoft.NETCore.Targets": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.1.0",
|
||||
"contentHash": "aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg=="
|
||||
},
|
||||
"Mono.TextTemplating": {
|
||||
"type": "Transitive",
|
||||
"resolved": "3.0.0",
|
||||
@@ -658,14 +534,6 @@
|
||||
"System.CodeDom": "6.0.0"
|
||||
}
|
||||
},
|
||||
"NetTopologySuite": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.5.0",
|
||||
"contentHash": "5/+2O2ADomEdUn09mlSigACdqvAf0m/pVPGtIPEPQWnyrVykYY0NlfXLIdkMgi41kvH9kNrPqYaFBTZtHYH7Xw==",
|
||||
"dependencies": {
|
||||
"System.Memory": "4.5.4"
|
||||
}
|
||||
},
|
||||
"NetTopologySuite.IO.PostGis": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.1.0",
|
||||
@@ -674,95 +542,10 @@
|
||||
"NetTopologySuite": "[2.0.0, 3.0.0-A)"
|
||||
}
|
||||
},
|
||||
"Newtonsoft.Json": {
|
||||
"type": "Transitive",
|
||||
"resolved": "13.0.3",
|
||||
"contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ=="
|
||||
},
|
||||
"Npgsql": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.2",
|
||||
"contentHash": "hCbO8box7i/XXiTFqCJ3GoowyLqx3JXxyrbOJ6om7dr+eAknvBNhhUHeJVGAQo44sySZTfdVffp4BrtPeLZOAA==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Logging.Abstractions": "8.0.2"
|
||||
}
|
||||
},
|
||||
"Npgsql.EntityFrameworkCore.PostgreSQL": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.2",
|
||||
"contentHash": "cYdOGplIvr9KgsG8nJ8xnzBTImeircbgetlzS1OmepS5dAQW6PuGpVrLOKBNEwEvGYZPsV8037X5vZ/Dmpwz7Q==",
|
||||
"dependencies": {
|
||||
"Microsoft.EntityFrameworkCore": "[9.0.0, 10.0.0)",
|
||||
"Microsoft.EntityFrameworkCore.Relational": "[9.0.0, 10.0.0)",
|
||||
"Npgsql": "9.0.2"
|
||||
}
|
||||
},
|
||||
"Npgsql.EntityFrameworkCore.PostgreSQL.NetTopologySuite": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.2",
|
||||
"contentHash": "D38a3+CF8dO7nPiwt/NtQ/sLbrzZpX910jaaGiETdeS18KI0yMYEFvWWO5I/JBjVXLVnruodsukIUupdoD4fRA==",
|
||||
"dependencies": {
|
||||
"Npgsql.EntityFrameworkCore.PostgreSQL": "9.0.2",
|
||||
"Npgsql.NetTopologySuite": "9.0.2"
|
||||
}
|
||||
},
|
||||
"Npgsql.NetTopologySuite": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.2",
|
||||
"contentHash": "DCwN+IVl3yWfOftPe0UBUUDOqa877ca+z+xSDQVi5ShDnOIAipaaYZlzDYm8Nga8hcxx6UrIQuImFnXv8fDpwg==",
|
||||
"dependencies": {
|
||||
"NetTopologySuite": "2.5.0",
|
||||
"NetTopologySuite.IO.PostGIS": "2.1.0",
|
||||
"Npgsql": "9.0.2"
|
||||
}
|
||||
},
|
||||
"Oceanbox.FvcomKit": {
|
||||
"type": "Transitive",
|
||||
"resolved": "5.12.2",
|
||||
"contentHash": "vOB9Me2Gb7yhnnEGOLfWFMBgKBiGc9ktOor++YfCSddCOjdLVM4oLQwbg2/miL8rZ26lprU/H6IdZ5nnWlVDRg==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "9.0.201",
|
||||
"FSharp.Data": "6.4.1",
|
||||
"FSharpPlus": "1.7.0",
|
||||
"FsPickler": "5.3.2",
|
||||
"KDTree": "1.4.1",
|
||||
"MathNet.Numerics.FSharp": "5.0.0",
|
||||
"MessagePack": "3.1.3",
|
||||
"Oceanbox.SDSLite": "2.8.0",
|
||||
"ProjNet.FSharp": "5.2.0",
|
||||
"Serilog": "4.2.0",
|
||||
"Serilog.Sinks.Console": "6.0.0",
|
||||
"Serilog.Sinks.Seq": "9.0.0",
|
||||
"Thoth.Json.Net": "12.0.0"
|
||||
}
|
||||
},
|
||||
"Oceanbox.SDSLite": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.8.0",
|
||||
"contentHash": "DzMcnywHhtmLVDZSVCZq6Mqq+SIm4snGRYgquho9xZSyEq5RhBkLdSa5k59m7o24FGZyt75DGpElN9p+dezU7Q==",
|
||||
"dependencies": {
|
||||
"DynamicInterop": "0.9.1"
|
||||
}
|
||||
},
|
||||
"ProjNET": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.0.0",
|
||||
"contentHash": "iMJG8qpGJ8SjFrB044O8wgo0raAWCdG1Bvly0mmVcjzsrexDHhC+dUct6Wb1YwQtupMBjSTWq7Fn00YeNErprA==",
|
||||
"dependencies": {
|
||||
"System.Memory": "4.5.3",
|
||||
"System.Numerics.Vectors": "4.5.0"
|
||||
}
|
||||
},
|
||||
"ProjNet.FSharp": {
|
||||
"type": "Transitive",
|
||||
"resolved": "5.2.0",
|
||||
"contentHash": "sYSePg/0sVo16Fk3r7okVSga6i9GAN0kkjt1haEXVw25SF8A4S3Gcpf5+6lgknBGdYiZBmJ+3S6v5g1WSSCp2g==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "8.0.100",
|
||||
"FSharp.Data": "6.3.0",
|
||||
"FSharpPlus": "1.5.0",
|
||||
"ProjNet": "2.0.0"
|
||||
}
|
||||
"contentHash": "iMJG8qpGJ8SjFrB044O8wgo0raAWCdG1Bvly0mmVcjzsrexDHhC+dUct6Wb1YwQtupMBjSTWq7Fn00YeNErprA=="
|
||||
},
|
||||
"Serilog.Sinks.File": {
|
||||
"type": "Transitive",
|
||||
@@ -772,16 +555,20 @@
|
||||
"Serilog": "4.0.0"
|
||||
}
|
||||
},
|
||||
"Serilog.Sinks.Seq": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.0",
|
||||
"contentHash": "aNU8A0K322q7+voPNmp1/qNPH+9QK8xvM1p72sMmCG0wGlshFzmtDW9QnVSoSYCj0MgQKcMOlgooovtBhRlNHw==",
|
||||
"dependencies": {
|
||||
"Serilog": "4.2.0",
|
||||
"Serilog.Sinks.File": "6.0.0"
|
||||
}
|
||||
},
|
||||
"System.CodeDom": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.0.0",
|
||||
"contentHash": "CPc6tWO1LAer3IzfZufDBRL+UZQcj5uS207NHALQzP84Vp/z6wF0Aa0YZImOQY8iStY0A2zI/e3ihKNPfUm8XA=="
|
||||
},
|
||||
"System.Collections.Immutable": {
|
||||
"type": "Transitive",
|
||||
"resolved": "7.0.0",
|
||||
"contentHash": "dQPcs0U1IKnBdRDBkrCTi1FoajSTBzLcVTpjO4MBCMC7f4pDOIPzgBoX8JjG7X6uZRJ8EBxsi8+DR1JuwjnzOQ=="
|
||||
},
|
||||
"System.Composition": {
|
||||
"type": "Transitive",
|
||||
"resolved": "7.0.0",
|
||||
@@ -830,128 +617,6 @@
|
||||
"System.Composition.Runtime": "7.0.0"
|
||||
}
|
||||
},
|
||||
"System.IO": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.3.0",
|
||||
"contentHash": "3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==",
|
||||
"dependencies": {
|
||||
"Microsoft.NETCore.Platforms": "1.1.0",
|
||||
"Microsoft.NETCore.Targets": "1.1.0",
|
||||
"System.Runtime": "4.3.0",
|
||||
"System.Text.Encoding": "4.3.0",
|
||||
"System.Threading.Tasks": "4.3.0"
|
||||
}
|
||||
},
|
||||
"System.IO.Pipelines": {
|
||||
"type": "Transitive",
|
||||
"resolved": "7.0.0",
|
||||
"contentHash": "jRn6JYnNPW6xgQazROBLSfpdoczRw694vO5kKvMcNnpXuolEixUyw6IBuBs2Y2mlSX/LdLvyyWmfXhaI3ND1Yg=="
|
||||
},
|
||||
"System.Memory": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.5.4",
|
||||
"contentHash": "1MbJTHS1lZ4bS4FmsJjnuGJOu88ZzTT2rLvrhW7Ygic+pC0NWA+3hgAen0HRdsocuQXCkUTdFn9yHJJhsijDXw=="
|
||||
},
|
||||
"System.Numerics.Vectors": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.5.0",
|
||||
"contentHash": "QQTlPTl06J/iiDbJCiepZ4H//BVraReU4O4EoRw1U02H5TLUIT7xn3GnDp9AXPSlJUDyFs4uWjWafNX6WrAojQ=="
|
||||
},
|
||||
"System.Reflection": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.3.0",
|
||||
"contentHash": "KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==",
|
||||
"dependencies": {
|
||||
"Microsoft.NETCore.Platforms": "1.1.0",
|
||||
"Microsoft.NETCore.Targets": "1.1.0",
|
||||
"System.IO": "4.3.0",
|
||||
"System.Reflection.Primitives": "4.3.0",
|
||||
"System.Runtime": "4.3.0"
|
||||
}
|
||||
},
|
||||
"System.Reflection.Emit.ILGeneration": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.3.0",
|
||||
"contentHash": "59tBslAk9733NXLrUJrwNZEzbMAcu8k344OYo+wfSVygcgZ9lgBdGIzH/nrg3LYhXceynyvTc8t5/GD4Ri0/ng==",
|
||||
"dependencies": {
|
||||
"System.Reflection": "4.3.0",
|
||||
"System.Reflection.Primitives": "4.3.0",
|
||||
"System.Runtime": "4.3.0"
|
||||
}
|
||||
},
|
||||
"System.Reflection.Emit.Lightweight": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.3.0",
|
||||
"contentHash": "oadVHGSMsTmZsAF864QYN1t1QzZjIcuKU3l2S9cZOwDdDueNTrqq1yRj7koFfIGEnKpt6NjpL3rOzRhs4ryOgA==",
|
||||
"dependencies": {
|
||||
"System.Reflection": "4.3.0",
|
||||
"System.Reflection.Emit.ILGeneration": "4.3.0",
|
||||
"System.Reflection.Primitives": "4.3.0",
|
||||
"System.Runtime": "4.3.0"
|
||||
}
|
||||
},
|
||||
"System.Reflection.Metadata": {
|
||||
"type": "Transitive",
|
||||
"resolved": "7.0.0",
|
||||
"contentHash": "MclTG61lsD9sYdpNz9xsKBzjsmsfCtcMZYXz/IUr2zlhaTaABonlr1ESeompTgM+Xk+IwtGYU7/voh3YWB/fWw==",
|
||||
"dependencies": {
|
||||
"System.Collections.Immutable": "7.0.0"
|
||||
}
|
||||
},
|
||||
"System.Reflection.Primitives": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.3.0",
|
||||
"contentHash": "5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==",
|
||||
"dependencies": {
|
||||
"Microsoft.NETCore.Platforms": "1.1.0",
|
||||
"Microsoft.NETCore.Targets": "1.1.0",
|
||||
"System.Runtime": "4.3.0"
|
||||
}
|
||||
},
|
||||
"System.Runtime": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.3.0",
|
||||
"contentHash": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==",
|
||||
"dependencies": {
|
||||
"Microsoft.NETCore.Platforms": "1.1.0",
|
||||
"Microsoft.NETCore.Targets": "1.1.0"
|
||||
}
|
||||
},
|
||||
"System.Runtime.CompilerServices.Unsafe": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.0.0",
|
||||
"contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg=="
|
||||
},
|
||||
"System.Text.Encoding": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.3.0",
|
||||
"contentHash": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==",
|
||||
"dependencies": {
|
||||
"Microsoft.NETCore.Platforms": "1.1.0",
|
||||
"Microsoft.NETCore.Targets": "1.1.0",
|
||||
"System.Runtime": "4.3.0"
|
||||
}
|
||||
},
|
||||
"System.Text.Json": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.1",
|
||||
"contentHash": "eqWHDZqYPv1PvuvoIIx5pF74plL3iEOZOl/0kQP+Y0TEbtgNnM2W6k8h8EPYs+LTJZsXuWa92n5W5sHTWvE3VA=="
|
||||
},
|
||||
"System.Threading.Channels": {
|
||||
"type": "Transitive",
|
||||
"resolved": "7.0.0",
|
||||
"contentHash": "qmeeYNROMsONF6ndEZcIQ+VxR4Q/TX/7uIVLJqtwIWL7dDWeh0l1UIqgo4wYyjG//5lUNhwkLDSFl+pAWO6oiA=="
|
||||
},
|
||||
"System.Threading.Tasks": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.3.0",
|
||||
"contentHash": "LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==",
|
||||
"dependencies": {
|
||||
"Microsoft.NETCore.Platforms": "1.1.0",
|
||||
"Microsoft.NETCore.Targets": "1.1.0",
|
||||
"System.Runtime": "4.3.0"
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
@@ -967,7 +632,7 @@
|
||||
"Dapper.FSharp": "[4.9.0, )",
|
||||
"Dapr.Actors": "[1.16.0, )",
|
||||
"Entity": "[1.0.0, )",
|
||||
"FSharp.Core": "[9.0.201, )",
|
||||
"FSharp.Core": "[9.0.303, )",
|
||||
"FSharp.Data": "[6.4.1, )",
|
||||
"FSharpPlus": "[1.7.0, )",
|
||||
"Fable.Remoting.DotnetClient": "[3.35.0, )",
|
||||
@@ -977,7 +642,7 @@
|
||||
"Npgsql.EntityFrameworkCore.PostgreSQL": "[9.0.2, )",
|
||||
"Npgsql.EntityFrameworkCore.PostgreSQL.NetTopologySuite": "[9.0.2, )",
|
||||
"Npgsql.NetTopologySuite": "[9.0.2, )",
|
||||
"Oceanbox.FvcomKit": "[5.12.2, )",
|
||||
"Oceanbox.FvcomKit": "[5.13.0, )",
|
||||
"Oceanbox.SDSLite": "[2.8.0, )",
|
||||
"Serilog.Sinks.Console": "[6.0.0, )",
|
||||
"Thoth.Json.Net": "[12.0.0, )"
|
||||
@@ -986,140 +651,209 @@
|
||||
"Oceanbox.DataAgent.Api": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "[9.0.201, )"
|
||||
"FSharp.Core": "[9.0.303, )"
|
||||
}
|
||||
},
|
||||
"Dapper.FSharp": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[4.9.0, )",
|
||||
"resolved": "4.9.0",
|
||||
"contentHash": "wqMi/wHSQV9v79/u8OELxO+lmUOxk3J5CAUuAmWbltbIYH0A64CV1z1RG+9EVpyAAD9bovKYAnQ2wNwDoPxTxA==",
|
||||
"dependencies": {
|
||||
"Dapper": "2.1.35",
|
||||
"FSharp.Core": "8.0.200"
|
||||
}
|
||||
},
|
||||
"Dapr.Actors": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[1.16.0, )",
|
||||
"resolved": "1.16.0",
|
||||
"contentHash": "s9v6VofXXYoRqZJQlQbvNYYSlGhkL+Z+bpqrx1TRo06kLhANeDmXA9yeVaD+1KwJIO1chUFj5O4iKuTxIkg1sA==",
|
||||
"dependencies": {
|
||||
"Dapr.Client": "1.16.0",
|
||||
"Dapr.Common": "1.16.0",
|
||||
"Google.Api.CommonProtos": "2.17.0",
|
||||
"Google.Protobuf": "3.32.0",
|
||||
"Grpc.Net.Client": "2.71.0",
|
||||
"Microsoft.Extensions.Configuration": "9.0.8",
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "9.0.8",
|
||||
"Microsoft.Extensions.DependencyInjection": "9.0.8",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.8",
|
||||
"Microsoft.Extensions.Http": "9.0.8",
|
||||
"Microsoft.Extensions.Logging": "9.0.8",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.8",
|
||||
"Microsoft.Extensions.Options": "9.0.8"
|
||||
}
|
||||
},
|
||||
"Dapr.Client": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[1.16.0, )",
|
||||
"resolved": "1.16.0",
|
||||
"contentHash": "dFDKol+mtQrk1lIKlEyCx3k6W0Pf+0wC6xcsaDqa0Bg+XCWDc4juROuDcSb0/L1Y+Ev6LSLDMC/FgzNWMw9YtQ==",
|
||||
"dependencies": {
|
||||
"Dapr.Common": "1.16.0",
|
||||
"Dapr.Protos": "1.16.0",
|
||||
"Google.Api.CommonProtos": "2.17.0",
|
||||
"Google.Protobuf": "3.32.0",
|
||||
"Grpc.Net.Client": "2.71.0",
|
||||
"Microsoft.Extensions.Configuration": "9.0.8",
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "9.0.8",
|
||||
"Microsoft.Extensions.DependencyInjection": "9.0.8",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.8",
|
||||
"Microsoft.Extensions.Http": "9.0.8",
|
||||
"Microsoft.Extensions.Logging": "9.0.8",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.8",
|
||||
"Microsoft.Extensions.Options": "9.0.8"
|
||||
}
|
||||
},
|
||||
"Fable.Core": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[4.4.0, )",
|
||||
"resolved": "3.1.6",
|
||||
"contentHash": "w6M1F0zoLk4kTFc1Lx6x1Ft6BD3QwRe0eaLiinAqbjVkcF+iK+NiXGJO+a6q9RAF9NCg0vI48Xku7aNeqG4JVw==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "4.7.1"
|
||||
}
|
||||
},
|
||||
"Fable.Remoting.DotnetClient": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[3.35.0, )",
|
||||
"resolved": "3.35.0",
|
||||
"contentHash": "xaxt9nKfqIWh30+cOrG9GNl06+7yTy5htrcF5eXsZ7QJLLy7T5ZD3xeGpAb0xbh+TZTVQGluSQzxh/opIZm2PQ==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "6.0.0",
|
||||
"Fable.Remoting.Json": "2.25.0",
|
||||
"Fable.Remoting.MsgPack": "1.24.0"
|
||||
}
|
||||
},
|
||||
"Fable.Remoting.MsgPack": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[1.24.0, )",
|
||||
"resolved": "1.24.0",
|
||||
"contentHash": "Bn3nzoZbib6lPk70bIJumEu2wFMxciB4o8k0Zw6tRfAOpNKvUsi79OOll2nW3FU1P6MVBepT43m+R8JvfYnNiw==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "4.7.2"
|
||||
}
|
||||
},
|
||||
"MessagePack": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[3.1.3, )",
|
||||
"resolved": "3.1.3",
|
||||
"contentHash": "UiNv3fknvPzh5W+S0VV96R17RBZQQU71qgmsMnjjRZU2rtQM/XcTnOB+klT2dA6T1mxjnNKYrEm164AoXvGmYg==",
|
||||
"dependencies": {
|
||||
"MessagePack.Annotations": "3.1.3",
|
||||
"MessagePackAnalyzer": "3.1.3",
|
||||
"Microsoft.NET.StringTools": "17.11.4"
|
||||
}
|
||||
},
|
||||
"Microsoft.EntityFrameworkCore.Relational": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[9.0.1, )",
|
||||
"resolved": "9.0.1",
|
||||
"contentHash": "7Iu0h4oevRvH4IwPzmxuIJGYRt55TapoREGlluk75KCO7lenN0+QnzCl6cQDY48uDoxAUpJbpK2xW7o8Ix69dw==",
|
||||
"dependencies": {
|
||||
"Microsoft.EntityFrameworkCore": "9.0.1",
|
||||
"Microsoft.Extensions.Caching.Memory": "9.0.1",
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "9.0.1",
|
||||
"Microsoft.Extensions.Logging": "9.0.1"
|
||||
}
|
||||
},
|
||||
"NetTopologySuite": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[2.5.0, )",
|
||||
"resolved": "2.5.0",
|
||||
"contentHash": "5/+2O2ADomEdUn09mlSigACdqvAf0m/pVPGtIPEPQWnyrVykYY0NlfXLIdkMgi41kvH9kNrPqYaFBTZtHYH7Xw=="
|
||||
},
|
||||
"Newtonsoft.Json": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[13.0.3, )",
|
||||
"resolved": "13.0.3",
|
||||
"contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ=="
|
||||
},
|
||||
"Npgsql": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[9.0.2, )",
|
||||
"resolved": "9.0.2",
|
||||
"contentHash": "hCbO8box7i/XXiTFqCJ3GoowyLqx3JXxyrbOJ6om7dr+eAknvBNhhUHeJVGAQo44sySZTfdVffp4BrtPeLZOAA==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Logging.Abstractions": "8.0.2"
|
||||
}
|
||||
},
|
||||
"Npgsql.EntityFrameworkCore.PostgreSQL": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[9.0.2, )",
|
||||
"resolved": "9.0.2",
|
||||
"contentHash": "cYdOGplIvr9KgsG8nJ8xnzBTImeircbgetlzS1OmepS5dAQW6PuGpVrLOKBNEwEvGYZPsV8037X5vZ/Dmpwz7Q==",
|
||||
"dependencies": {
|
||||
"Microsoft.EntityFrameworkCore": "[9.0.0, 10.0.0)",
|
||||
"Microsoft.EntityFrameworkCore.Relational": "[9.0.0, 10.0.0)",
|
||||
"Npgsql": "9.0.2"
|
||||
}
|
||||
},
|
||||
"Npgsql.EntityFrameworkCore.PostgreSQL.NetTopologySuite": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[9.0.2, )",
|
||||
"resolved": "9.0.2",
|
||||
"contentHash": "D38a3+CF8dO7nPiwt/NtQ/sLbrzZpX910jaaGiETdeS18KI0yMYEFvWWO5I/JBjVXLVnruodsukIUupdoD4fRA==",
|
||||
"dependencies": {
|
||||
"Npgsql.EntityFrameworkCore.PostgreSQL": "9.0.2",
|
||||
"Npgsql.NetTopologySuite": "9.0.2"
|
||||
}
|
||||
},
|
||||
"Npgsql.NetTopologySuite": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[9.0.2, )",
|
||||
"resolved": "9.0.2",
|
||||
"contentHash": "DCwN+IVl3yWfOftPe0UBUUDOqa877ca+z+xSDQVi5ShDnOIAipaaYZlzDYm8Nga8hcxx6UrIQuImFnXv8fDpwg==",
|
||||
"dependencies": {
|
||||
"NetTopologySuite": "2.5.0",
|
||||
"NetTopologySuite.IO.PostGIS": "2.1.0",
|
||||
"Npgsql": "9.0.2"
|
||||
}
|
||||
},
|
||||
"Oceanbox.FvcomKit": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[5.13.0, )",
|
||||
"resolved": "5.13.0",
|
||||
"contentHash": "6uVL3fLhRf4OU1hWygGpVex4pI5YB+GaWrKZUgoL/LkGmdFv0qU8Y7v+meHNM3E9bjR7xKinCVfrw5SXsF6C8g==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "9.0.201",
|
||||
"FSharp.Data": "6.4.1",
|
||||
"FSharpPlus": "1.7.0",
|
||||
"FsPickler": "5.3.2",
|
||||
"KDTree": "1.4.1",
|
||||
"MathNet.Numerics.FSharp": "5.0.0",
|
||||
"MessagePack": "3.1.3",
|
||||
"Oceanbox.SDSLite": "2.8.0",
|
||||
"ProjNet.FSharp": "5.2.0",
|
||||
"Serilog": "4.2.0",
|
||||
"Serilog.Sinks.Console": "6.0.0",
|
||||
"Serilog.Sinks.Seq": "9.0.0",
|
||||
"Thoth.Json.Net": "12.0.0"
|
||||
}
|
||||
},
|
||||
"Oceanbox.SDSLite": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[2.8.0, )",
|
||||
"resolved": "2.8.0",
|
||||
"contentHash": "DzMcnywHhtmLVDZSVCZq6Mqq+SIm4snGRYgquho9xZSyEq5RhBkLdSa5k59m7o24FGZyt75DGpElN9p+dezU7Q==",
|
||||
"dependencies": {
|
||||
"DynamicInterop": "0.9.1"
|
||||
}
|
||||
},
|
||||
"ProjNet.FSharp": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[5.2.0, )",
|
||||
"resolved": "5.2.0",
|
||||
"contentHash": "sYSePg/0sVo16Fk3r7okVSga6i9GAN0kkjt1haEXVw25SF8A4S3Gcpf5+6lgknBGdYiZBmJ+3S6v5g1WSSCp2g==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "8.0.100",
|
||||
"FSharp.Data": "6.3.0",
|
||||
"FSharpPlus": "1.5.0",
|
||||
"ProjNet": "2.0.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"net9.0/linux-x64": {
|
||||
"runtime.any.System.IO": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.3.0",
|
||||
"contentHash": "SDZ5AD1DtyRoxYtEcqQ3HDlcrorMYXZeCt7ZhG9US9I5Vva+gpIWDGMkcwa5XiKL0ceQKRZIX2x0XEjLX7PDzQ=="
|
||||
},
|
||||
"runtime.any.System.Reflection": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.3.0",
|
||||
"contentHash": "hLC3A3rI8jipR5d9k7+f0MgRCW6texsAp0MWkN/ci18FMtQ9KH7E2vDn/DH2LkxsszlpJpOn9qy6Z6/69rH6eQ=="
|
||||
},
|
||||
"runtime.any.System.Reflection.Primitives": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.3.0",
|
||||
"contentHash": "Nrm1p3armp6TTf2xuvaa+jGTTmncALWFq22CpmwRvhDf6dE9ZmH40EbOswD4GnFLrMRS0Ki6Kx5aUPmKK/hZBg=="
|
||||
},
|
||||
"runtime.any.System.Runtime": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.3.0",
|
||||
"contentHash": "fRS7zJgaG9NkifaAxGGclDDoRn9HC7hXACl52Or06a/fxdzDajWb5wov3c6a+gVSlekRoexfjwQSK9sh5um5LQ==",
|
||||
"dependencies": {
|
||||
"System.Private.Uri": "4.3.0"
|
||||
}
|
||||
},
|
||||
"runtime.any.System.Text.Encoding": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.3.0",
|
||||
"contentHash": "+ihI5VaXFCMVPJNstG4O4eo1CfbrByLxRrQQTqOTp1ttK0kUKDqOdBSTaCB2IBk/QtjDrs6+x4xuezyMXdm0HQ=="
|
||||
},
|
||||
"runtime.any.System.Threading.Tasks": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.3.0",
|
||||
"contentHash": "OhBAVBQG5kFj1S+hCEQ3TUHBAEtZ3fbEMgZMRNdN8A0Pj4x+5nTELEqL59DU0TjKVE6II3dqKw4Dklb3szT65w=="
|
||||
},
|
||||
"runtime.native.System": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.3.0",
|
||||
"contentHash": "c/qWt2LieNZIj1jGnVNsE2Kl23Ya2aSTBuXMD6V7k9KWr6l16Tqdwq+hJScEpWER9753NWC8h96PaVNY5Ld7Jw==",
|
||||
"dependencies": {
|
||||
"Microsoft.NETCore.Platforms": "1.1.0",
|
||||
"Microsoft.NETCore.Targets": "1.1.0"
|
||||
}
|
||||
},
|
||||
"runtime.unix.System.Private.Uri": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.3.0",
|
||||
"contentHash": "ooWzobr5RAq34r9uan1r/WPXJYG1XWy9KanrxNvEnBzbFdQbMG7Y3bVi4QxR7xZMNLOxLLTAyXvnSkfj5boZSg==",
|
||||
"dependencies": {
|
||||
"runtime.native.System": "4.3.0"
|
||||
}
|
||||
},
|
||||
"System.IO": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.3.0",
|
||||
"contentHash": "3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==",
|
||||
"dependencies": {
|
||||
"Microsoft.NETCore.Platforms": "1.1.0",
|
||||
"Microsoft.NETCore.Targets": "1.1.0",
|
||||
"System.Runtime": "4.3.0",
|
||||
"System.Text.Encoding": "4.3.0",
|
||||
"System.Threading.Tasks": "4.3.0",
|
||||
"runtime.any.System.IO": "4.3.0"
|
||||
}
|
||||
},
|
||||
"System.Private.Uri": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.3.0",
|
||||
"contentHash": "I4SwANiUGho1esj4V4oSlPllXjzCZDE+5XXso2P03LW2vOda2Enzh8DWOxwN6hnrJyp314c7KuVu31QYhRzOGg==",
|
||||
"dependencies": {
|
||||
"Microsoft.NETCore.Platforms": "1.1.0",
|
||||
"Microsoft.NETCore.Targets": "1.1.0",
|
||||
"runtime.unix.System.Private.Uri": "4.3.0"
|
||||
}
|
||||
},
|
||||
"System.Reflection": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.3.0",
|
||||
"contentHash": "KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==",
|
||||
"dependencies": {
|
||||
"Microsoft.NETCore.Platforms": "1.1.0",
|
||||
"Microsoft.NETCore.Targets": "1.1.0",
|
||||
"System.IO": "4.3.0",
|
||||
"System.Reflection.Primitives": "4.3.0",
|
||||
"System.Runtime": "4.3.0",
|
||||
"runtime.any.System.Reflection": "4.3.0"
|
||||
}
|
||||
},
|
||||
"System.Reflection.Primitives": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.3.0",
|
||||
"contentHash": "5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==",
|
||||
"dependencies": {
|
||||
"Microsoft.NETCore.Platforms": "1.1.0",
|
||||
"Microsoft.NETCore.Targets": "1.1.0",
|
||||
"System.Runtime": "4.3.0",
|
||||
"runtime.any.System.Reflection.Primitives": "4.3.0"
|
||||
}
|
||||
},
|
||||
"System.Runtime": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.3.0",
|
||||
"contentHash": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==",
|
||||
"dependencies": {
|
||||
"Microsoft.NETCore.Platforms": "1.1.0",
|
||||
"Microsoft.NETCore.Targets": "1.1.0",
|
||||
"runtime.any.System.Runtime": "4.3.0"
|
||||
}
|
||||
},
|
||||
"System.Text.Encoding": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.3.0",
|
||||
"contentHash": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==",
|
||||
"dependencies": {
|
||||
"Microsoft.NETCore.Platforms": "1.1.0",
|
||||
"Microsoft.NETCore.Targets": "1.1.0",
|
||||
"System.Runtime": "4.3.0",
|
||||
"runtime.any.System.Text.Encoding": "4.3.0"
|
||||
}
|
||||
},
|
||||
"System.Threading.Tasks": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.3.0",
|
||||
"contentHash": "LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==",
|
||||
"dependencies": {
|
||||
"Microsoft.NETCore.Platforms": "1.1.0",
|
||||
"Microsoft.NETCore.Targets": "1.1.0",
|
||||
"System.Runtime": "4.3.0",
|
||||
"runtime.any.System.Threading.Tasks": "4.3.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
"net10.0/linux-x64": {}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Version>7.1.0</Version>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
@@ -9,9 +9,9 @@
|
||||
<Compile Include="App.fs"/>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Fable.Lit" Version="1.4.2"/>
|
||||
<PackageReference Include="Fable.Remoting.Client" Version="7.32.0"/>
|
||||
<PackageReference Update="FSharp.Core" Version="9.0.201"/>
|
||||
<PackageReference Include="Fable.Lit" />
|
||||
<PackageReference Include="Fable.Remoting.Client" />
|
||||
<PackageReference Include="FSharp.Core" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../../Interfaces/Archmaester/Archmaester.Api.fsproj"/>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Version>7.1.0</Version>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
@@ -11,23 +11,23 @@
|
||||
<Compile Include="Main.fs"/>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Argu" Version="6.2.5"/>
|
||||
<PackageReference Include="Dapper.FSharp" Version="4.9.0"/>
|
||||
<PackageReference Include="FSharp.Data" Version="6.4.1"/>
|
||||
<PackageReference Include="FSharpPlus" Version="1.7.0"/>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.2">
|
||||
<PackageReference Include="Argu" />
|
||||
<PackageReference Include="Dapper.FSharp" />
|
||||
<PackageReference Include="FSharp.Data" />
|
||||
<PackageReference Include="FSharpPlus" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" >
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.2"/>
|
||||
<PackageReference Include="Npgsql.NetTopologySuite" Version="9.0.2"/>
|
||||
<PackageReference Include="Serilog" Version="4.2.0"/>
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0"/>
|
||||
<PackageReference Include="Thoth.Json.Net" Version="12.0.0"/>
|
||||
<PackageReference Include="NetTopologySuite" Version="2.5.0"/>
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.2"/>
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.NetTopologySuite" Version="9.0.2"/>
|
||||
<PackageReference Update="FSharp.Core" Version="9.0.201"/>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" />
|
||||
<PackageReference Include="Npgsql.NetTopologySuite" />
|
||||
<PackageReference Include="Serilog" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" />
|
||||
<PackageReference Include="Thoth.Json.Net" />
|
||||
<PackageReference Include="NetTopologySuite" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.NetTopologySuite" />
|
||||
<PackageReference Update="FSharp.Core" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\Interfaces\Archmaester\Archmaester.Api.fsproj"/>
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
open Fake.Core
|
||||
open Fake.IO
|
||||
open Farmer
|
||||
open Farmer.Builders
|
||||
|
||||
open Helpers
|
||||
|
||||
initializeContext()
|
||||
|
||||
let serverPath = Path.getFullName "src/Server"
|
||||
let clientPath = Path.getFullName "src/Client"
|
||||
let testPath = Path.getFullName "test"
|
||||
let libPath = Path.getFullName "src/Interfaces" |> Some
|
||||
|
||||
let distPath = Path.getFullName "dist"
|
||||
let packPath = Path.getFullName "packages"
|
||||
let versionFile = Path.getFullName ".version"
|
||||
|
||||
let vite = """bunx --bun vite -c ../../vite.config.js"""
|
||||
|
||||
let fableOpt opts =
|
||||
$"-e .jsx -o build --test:MSBuildCracker --run {vite} build --emptyOutDir --outDir {distPath}/public {opts}"
|
||||
|
||||
let fableWatch = $"watch -e .jsx -o build --run {vite}"
|
||||
|
||||
Target.create "Clean" (fun _ -> Shell.cleanDir distPath)
|
||||
|
||||
Target.create "Bundle" (fun _ ->
|
||||
[ "server", dotnet $"build -tl -c Release -o {distPath} -p:DefineConstants=" serverPath
|
||||
"client", fable (fableOpt "-m production") clientPath ]
|
||||
|> runParallel
|
||||
)
|
||||
|
||||
Target.create "BundleDebug" (fun _ ->
|
||||
[ "server", dotnet $"build -tl -c Debug -o {distPath} -p:DefineConstants=" serverPath
|
||||
"client", fable (fableOpt "-m development --minify false --sourcemap true") clientPath ]
|
||||
|> runParallel
|
||||
)
|
||||
|
||||
Target.create "Pack" (fun _ ->
|
||||
match libPath with
|
||||
| Some p -> run dotnet $"pack -c Release -o \"{packPath}\"" p
|
||||
| None -> ()
|
||||
)
|
||||
|
||||
Target.create "Run" (fun _ ->
|
||||
[ "server", dotnet "watch run" serverPath
|
||||
"client", fable fableWatch clientPath ]
|
||||
|> runParallel
|
||||
)
|
||||
|
||||
Target.create "Client" (fun _ ->
|
||||
run fable fableWatch clientPath
|
||||
)
|
||||
|
||||
Target.create "Format" (fun _ ->
|
||||
run fantomas ". -r" "src"
|
||||
)
|
||||
|
||||
Target.create "Test" (fun _ ->
|
||||
if System.IO.Directory.Exists testPath then
|
||||
[ "server", dotnet "run" (testPath + "/Server")
|
||||
"client", fable $"-e .jsx -o build --run {vite}" (testPath + "/Client") ]
|
||||
|> runParallel
|
||||
else ()
|
||||
)
|
||||
|
||||
open Fake.Core.TargetOperators
|
||||
|
||||
let dependencies = [
|
||||
"Clean"
|
||||
==> "Bundle"
|
||||
|
||||
"Clean"
|
||||
==> "BundleDebug"
|
||||
|
||||
"Clean"
|
||||
==> "Test"
|
||||
|
||||
"Clean"
|
||||
==> "Run"
|
||||
|
||||
"Clean"
|
||||
==> "Pack"
|
||||
|
||||
"Client"
|
||||
]
|
||||
|
||||
[<EntryPoint>]
|
||||
let main args = runOrDefault args
|
||||
@@ -1,17 +1,15 @@
|
||||
# yaml-language-server: $schema=https://gitlab.com/gitlab-org/gitlab/-/raw/master/app/assets/javascripts/editor/schema/ci.json
|
||||
variables:
|
||||
SKIP_TESTS: "true"
|
||||
SKIP_SINGULARITY: "true"
|
||||
SKIP_TESTS: "true"
|
||||
|
||||
include:
|
||||
- project: oceanbox/gitlab-ci
|
||||
ref: v4.2
|
||||
file: DotnetDeployment.gitlab-ci.yml
|
||||
inputs:
|
||||
project-name: atlantis
|
||||
project-dir: src/Atlantis
|
||||
- project: oceanbox/gitlab-ci
|
||||
ref: v4.5
|
||||
file: DotnetDeployment.gitlab-ci.yml
|
||||
inputs:
|
||||
project-name: atlantis
|
||||
project-dir: src/Atlantis
|
||||
|
||||
# TODO(mrtz): Create a nix-runner
|
||||
# dockerize-atlantis:
|
||||
# tags:
|
||||
# - saas-linux-large-amd64
|
||||
dockerize-atlantis:
|
||||
tags:
|
||||
- nix
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="../../.build/Helpers.fs" />
|
||||
<Compile Include=".build/Build.fs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Fake.Core.Target" Version="6.1.3" />
|
||||
<PackageReference Include="Fake.DotNet.Cli" Version="6.1.3" />
|
||||
<PackageReference Include="Fake.IO.FileSystem" Version="6.1.3" />
|
||||
<PackageReference Include="Farmer" Version="1.9.6" />
|
||||
<PackageReference Update="FSharp.Core" Version="9.0.201" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:9.0.6
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:10.0
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y gcc-multilib libnetcdf19 libnetcdf-dev
|
||||
@@ -12,4 +12,4 @@ ENV SERVER_CONTENT_ROOT=/app/public
|
||||
COPY dist/ /app
|
||||
|
||||
WORKDIR /app
|
||||
CMD [ "dotnet", "/app/Server.dll" ]
|
||||
CMD [ "dotnet", "/app/Server.dll" ]
|
||||
@@ -42,11 +42,6 @@ docker_build_with_restart(
|
||||
# "../../default.nix",
|
||||
# )
|
||||
|
||||
redis=str(local(
|
||||
'helm template -f ./tilt/tilt/redis.yaml --namespace {namespace} {env}-{name}-redis bitnami/redis'
|
||||
.format(namespace=namespace, env=env, name=name), echo_off=False, quiet=True)).replace('<x>',"{env}".format(env=env))
|
||||
k8s_yaml(blob(redis))
|
||||
|
||||
manifest=helm(
|
||||
'../../../manifests/charts/{name}'.format(name=name),
|
||||
name='{env}'.format(env=env),
|
||||
@@ -65,13 +60,13 @@ k8s_yaml(namespace_inject(blob(kustomizations), namespace))
|
||||
|
||||
local_resource(
|
||||
'create-bundle',
|
||||
cmd='dotnet run bundledebug',
|
||||
cmd='just bundle-debug',
|
||||
trigger_mode=TRIGGER_MODE_MANUAL
|
||||
)
|
||||
|
||||
local_resource(
|
||||
'build-server',
|
||||
cmd='dotnet publish -o ./dist src/Server',
|
||||
cmd='just bundle-debug-server',
|
||||
deps=[
|
||||
'./src/Server',
|
||||
'./src/Shared'
|
||||
@@ -79,6 +74,10 @@ local_resource(
|
||||
ignore=[
|
||||
'src/Server/bin',
|
||||
'src/Server/obj',
|
||||
'src/Server/Archmaester/obj',
|
||||
'src/Server/Hipster/obj',
|
||||
'src/Server/Petimeter/obj',
|
||||
'src/Server/Common/obj',
|
||||
'src/Shared/bin',
|
||||
'src/Shared/obj',
|
||||
],
|
||||
@@ -89,7 +88,7 @@ local_resource(
|
||||
|
||||
local_resource(
|
||||
'run-client',
|
||||
serve_cmd='fable watch -e .jsx -o build --run vite -c ../../vite.config.js',
|
||||
serve_cmd='just run-client',
|
||||
serve_dir='./src/Client',
|
||||
links=['https://{name}.local.oceanbox.io:{port}'.format(name=name, port=clientPort)],
|
||||
resource_deps=['build-server'],
|
||||
|
||||
86
src/Atlantis/justfile
Normal file
86
src/Atlantis/justfile
Normal file
@@ -0,0 +1,86 @@
|
||||
# Atlantis build commands
|
||||
# Install just: https://github.com/casey/just
|
||||
|
||||
set dotenv-load
|
||||
|
||||
src_path := "src"
|
||||
server_path := "src/Server"
|
||||
client_path := "src/Client"
|
||||
test_path := "test"
|
||||
lib_path := "src/Interfaces"
|
||||
|
||||
dist_path := "../../dist"
|
||||
pack_path := "../../packages"
|
||||
|
||||
vite_prod := "bunx --bun vite build -c ../../vite.config.js -m production --emptyOutDir --outDir " + "../../dist/public"
|
||||
vite_dev := "bunx --bun vite build -c ../../vite.config.js -m development --minify false --sourcemap true --emptyOutDir --outDir " + "../../dist/public"
|
||||
vite := "bunx vite -c ../../vite.config.js -m development "
|
||||
|
||||
# Default recipe - show available commands
|
||||
default:
|
||||
@just --list
|
||||
|
||||
# Clean build artifacts
|
||||
clean:
|
||||
rm -rf {{dist_path}}
|
||||
|
||||
# Build production bundle (server + client)
|
||||
[parallel]
|
||||
bundle: clean bundle-server bundle-client
|
||||
|
||||
[working-directory: 'src/Server']
|
||||
bundle-server:
|
||||
dotnet build -tl -c Release -o {{dist_path}}
|
||||
|
||||
[working-directory: 'src/Client']
|
||||
install-client:
|
||||
bun install --frozen-lockfile
|
||||
|
||||
[working-directory: 'src/Client']
|
||||
bundle-client: install-client
|
||||
|
||||
# Build debug bundle (server + client)
|
||||
[parallel]
|
||||
bundle-debug: clean bundle-debug-server bundle-debug-client
|
||||
|
||||
[working-directory: 'src/Server']
|
||||
bundle-debug-server:
|
||||
dotnet build -tl -c Debug -o {{dist_path}}
|
||||
|
||||
[working-directory: 'src/Client']
|
||||
bundle-debug-client:
|
||||
fable -e .jsx -o build --test:MSBuildCracker --run {{vite_dev}}
|
||||
|
||||
# Create NuGet package
|
||||
[working-directory: 'src/Server']
|
||||
pack: clean
|
||||
dotnet pack -c Release -o "{{pack_path}}" {{lib_path}}
|
||||
|
||||
# Run development server (watch mode)
|
||||
[parallel]
|
||||
run: clean run-server run-client
|
||||
|
||||
[working-directory: 'src/Server']
|
||||
run-server:
|
||||
dotnet watch run
|
||||
|
||||
# Run client only in watch mode
|
||||
[working-directory: 'src/Client']
|
||||
run-client: install-client
|
||||
fable watch -e .jsx -o build --test:MSBuildCracker --run {{vite}}
|
||||
|
||||
# Format code with Fantomas
|
||||
format:
|
||||
fantomas {{src_path}} -r
|
||||
|
||||
# Run tests
|
||||
[parallel]
|
||||
test: clean test-server test-client
|
||||
|
||||
[working-directory: 'src']
|
||||
test-server:
|
||||
dotnet run {{test_path}}/Server
|
||||
|
||||
[working-directory: 'src/Client']
|
||||
test-client: install-client
|
||||
fable -e .jsx -o build --run {{vite}}
|
||||
@@ -14,4 +14,4 @@ import barpolar from 'plotly-dist/lib/barpolar';
|
||||
|
||||
Plotly.register([scatter, contour, heatmap, box, bar, barpolar]);
|
||||
|
||||
export default Plotly;
|
||||
export default Plotly;
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
sources ? import ./../../lon.nix,
|
||||
sources ? import ./../../nix,
|
||||
pkgs ? import sources.nixpkgs { },
|
||||
}:
|
||||
let
|
||||
@@ -29,4 +29,4 @@ pkgs.mkShellNoCC {
|
||||
export APP_NAME=atlantis
|
||||
export APP_NAMESPACE=$USER-atlantis
|
||||
'';
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ open Fable.Core.JsInterop
|
||||
open Lit
|
||||
open Remoting
|
||||
|
||||
let initSentry = Array.contains window.location.hostname Sentry.hostTargets
|
||||
let initSentry = Sentry.hostTargets |> Array.contains window.location.hostname
|
||||
|
||||
if initSentry then
|
||||
console.debug "Pushing to Sentry"
|
||||
@@ -20,7 +20,7 @@ let InitApp () =
|
||||
let! authenticated = authApi.IsAuthenticated()
|
||||
do! Auth.setId ()
|
||||
if authenticated.IsSome && not (isNullOrUndefined sessionStorage["archive_id"]) then
|
||||
do! Utils.initAtlantisSessionUrls ()
|
||||
do! Auth.initSessionUrls ()
|
||||
window.location.href <- "/map.html"
|
||||
else
|
||||
sessionStorage.removeItem "archive_id"
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
module Atlas
|
||||
|
||||
open System
|
||||
|
||||
open Browser
|
||||
open Fable.Core
|
||||
open Fable.Core.JsInterop
|
||||
open Fable.OpenLayers
|
||||
open Fable.OpenLayers.Event
|
||||
open Feliz.prop
|
||||
open Lit
|
||||
open Lit.Elmish
|
||||
open Remoting
|
||||
|
||||
open Archmaester.Dto
|
||||
open Atlantis.Types
|
||||
open Maps
|
||||
open Remoting
|
||||
|
||||
importSideEffects "../public/style.scss"
|
||||
|
||||
importSideEffects "@spectrum-web-components/action-button/sp-action-button.js"
|
||||
importSideEffects "@spectrum-web-components/action-group/sp-action-group.js"
|
||||
importSideEffects "@spectrum-web-components/action-menu/sp-action-menu.js"
|
||||
@@ -53,12 +54,8 @@ importSideEffects "@spectrum-web-components/table/sp-table-row.js"
|
||||
importSideEffects "@spectrum-web-components/dialog/sp-dialog.js"
|
||||
importSideEffects "@spectrum-web-components/underlay/sp-underlay.js"
|
||||
|
||||
importSideEffects "@shoelace-style/shoelace/dist/components/dialog/dialog.js"
|
||||
|
||||
let register () = ()
|
||||
|
||||
let private hmr = HMR.createToken ()
|
||||
|
||||
let polygonStyle (feature: Feature) =
|
||||
let name = feature.get "name" :?> string
|
||||
let models = feature.get "models" :?> int
|
||||
@@ -96,7 +93,8 @@ let private createSelectInteraction () =
|
||||
|
||||
type private Model = {
|
||||
activeTab: Tab
|
||||
archives: Map<ModelAreaId, ArchiveProps[]>
|
||||
// TODO: Save this in localstorage or IndexedDB
|
||||
archives: Map<ModelAreaId, ArchiveProps array>
|
||||
helloWorld: ModelArea option
|
||||
map: OlMap
|
||||
mapKind: MapKind
|
||||
@@ -254,11 +252,21 @@ let emptyModel = {
|
||||
json = ""
|
||||
}
|
||||
|
||||
let private hmr = HMR.createToken ()
|
||||
|
||||
[<HookComponent>]
|
||||
let private archivePopup (m: ModelArea option) =
|
||||
Hook.useHmr hmr
|
||||
|
||||
let modelArea, setModelArea =
|
||||
Hook.useState<ModelArea> (fun () -> m |> Option.defaultValue emptyModel)
|
||||
Hook.useEffectOnChange (m, Option.iter setModelArea)
|
||||
|
||||
Hook.useEffectOnChange (
|
||||
m,
|
||||
function
|
||||
| Some modelArea -> setModelArea modelArea
|
||||
| None -> ()
|
||||
)
|
||||
|
||||
let archives =
|
||||
if modelArea.archives > 0 then
|
||||
@@ -269,7 +277,8 @@ let private archivePopup (m: ModelArea option) =
|
||||
let areas =
|
||||
if modelArea.models > 0 then
|
||||
html $""" Available areas: {modelArea.models} """
|
||||
else Lit.nothing
|
||||
else
|
||||
Lit.nothing
|
||||
|
||||
html
|
||||
$"""
|
||||
@@ -300,20 +309,23 @@ let private archivePopup (m: ModelArea option) =
|
||||
"
|
||||
>
|
||||
{modelArea.name}
|
||||
<sp-divider> </sp-divider>
|
||||
<sp-divider size="s"></sp-divider>
|
||||
</div>
|
||||
<div>
|
||||
<span>{archives}</span>
|
||||
<span>{areas}</span>
|
||||
</div>
|
||||
<p>
|
||||
{archives}
|
||||
{areas}
|
||||
</p>
|
||||
</sp-popover>
|
||||
</div>
|
||||
"""
|
||||
|
||||
[<HookComponent>]
|
||||
let private topNav (dispatch: Msg -> unit) (model: Model) =
|
||||
Hook.useHmr hmr
|
||||
|
||||
let icon =
|
||||
match model.activeTab with
|
||||
| Tab.Select -> html $"""<img src="ob.png" height="35px"/>"""
|
||||
| Tab.Select -> html $"""<img src="ob.png" />"""
|
||||
| _ -> html $"""<sp-icon-undo></sp-icon-undo>"""
|
||||
|
||||
let handleActionMenu (ev: Types.Event) =
|
||||
@@ -321,35 +333,33 @@ let private topNav (dispatch: Msg -> unit) (model: Model) =
|
||||
| "logout" -> window.location.href <- "/signout"
|
||||
| _ -> ()
|
||||
|
||||
let menu =
|
||||
html
|
||||
$"""
|
||||
<sp-top-nav-item href="#about">Oceanbox</sp-top-nav-item>
|
||||
<sp-action-menu label="Account" placement="bottom-end" @change={Ev (handleActionMenu)} style="margin-inline-start: auto;">
|
||||
// NOTE: The img sizing is a bit weird, which the height is explicit
|
||||
html
|
||||
$"""
|
||||
<div class="atlas-top-nav">
|
||||
<div class="atlas-top-nav-start">
|
||||
<a href="/" style="height: 32px;">{icon}</a>
|
||||
<a href="#about">Oceanbox</a>
|
||||
</div>
|
||||
|
||||
<sp-action-menu
|
||||
label="Account"
|
||||
placement="bottom-end"
|
||||
@change={Ev handleActionMenu}
|
||||
>
|
||||
<sp-menu-item disabled>Settings</sp-menu-item>
|
||||
<sp-menu-item disabled>Profile</sp-menu-item>
|
||||
<sp-menu-divider></sp-menu-divider>
|
||||
<sp-menu-item disabled>Help</sp-menu-item>
|
||||
<sp-menu-item value="logout">Logout</sp-menu-item>
|
||||
</sp-action-menu>
|
||||
"""
|
||||
let menuButton =
|
||||
match model.activeTab with
|
||||
| Tab.Select -> menu
|
||||
| _ -> menu
|
||||
|
||||
html
|
||||
$"""
|
||||
<sp-top-nav size="s">
|
||||
<sp-top-nav-item @click={Ev (fun _ -> window.location.reload ())}>
|
||||
{icon}
|
||||
</sp-top-nav-item>
|
||||
{menuButton}
|
||||
</sp-top-nav>
|
||||
</div>
|
||||
"""
|
||||
|
||||
[<HookComponent>]
|
||||
let archiveSelectModal (modelAreaOpt: ModelArea option) (archives: ArchiveProps array) onClose =
|
||||
let private archiveSelectModal (modelAreaOpt: ModelArea option) (archives: ArchiveProps array) onClose =
|
||||
Hook.useHmr hmr
|
||||
|
||||
let modelAreaName = modelAreaOpt |> Option.map (fun model -> $"{model.name} archives") |> Option.defaultValue "N/A"
|
||||
let archiveRow (archive: ArchiveProps) =
|
||||
let selectArchive (archive: ArchiveProps) =
|
||||
@@ -375,32 +385,48 @@ let archiveSelectModal (modelAreaOpt: ModelArea option) (archives: ArchiveProps
|
||||
|> Array.sortByDescending _.startTime
|
||||
|> Lit.mapUnique (fun a -> a.archiveId.ToString ()) archiveRow
|
||||
|
||||
html $"""
|
||||
<sp-underlay ?open={modelAreaOpt.IsSome} @click={fun _ -> onClose ()}></sp-underlay>
|
||||
<sp-dialog size="l" dismissable @close={Ev (fun _ -> onClose ())}>
|
||||
<h2 slot="heading">{modelAreaName}</h2>
|
||||
<sp-table
|
||||
size="s"
|
||||
scroller="true"
|
||||
style="width: 100%%;"
|
||||
>
|
||||
<sp-table-head>
|
||||
<sp-table-head-cell>Name</sp-table-head-cell>
|
||||
<sp-table-head-cell>Start</sp-table-head-cell>
|
||||
<sp-table-head-cell>Days</sp-table-head-cell>
|
||||
</sp-table-head>
|
||||
<sp-table-body>
|
||||
{archiveRows}
|
||||
</sp-table-body>
|
||||
</sp-table>
|
||||
</sp-dialog>
|
||||
"""
|
||||
let content () =
|
||||
if Array.isEmpty archives then
|
||||
html
|
||||
$"""
|
||||
<div class="full-box flex-center">
|
||||
<sp-progress-circle label="Loading archives" size="l" indeterminate></sp-progress-circle>
|
||||
</div>
|
||||
"""
|
||||
else
|
||||
html
|
||||
$"""
|
||||
<sp-table
|
||||
size="s"
|
||||
scroller="true"
|
||||
style="width: 100%%;"
|
||||
>
|
||||
<sp-table-head>
|
||||
<sp-table-head-cell>Name</sp-table-head-cell>
|
||||
<sp-table-head-cell>Start</sp-table-head-cell>
|
||||
<sp-table-head-cell>Days</sp-table-head-cell>
|
||||
</sp-table-head>
|
||||
<sp-table-body>
|
||||
{archiveRows}
|
||||
</sp-table-body>
|
||||
</sp-table>
|
||||
"""
|
||||
|
||||
html
|
||||
$"""
|
||||
<sp-underlay ?open={modelAreaOpt.IsSome} @click={fun _ -> onClose ()}></sp-underlay>
|
||||
<sp-dialog size="l" dismissable @close={Ev (fun _ -> onClose ())}>
|
||||
<h2 slot="heading">{modelAreaName}</h2>
|
||||
{content ()}
|
||||
</sp-dialog>
|
||||
"""
|
||||
|
||||
let selectStyle name =
|
||||
let stroke' =
|
||||
Style.stroke [ stroke.color "rgba(50, 50, 50, 0.9)"; stroke.width 1.3 ]
|
||||
let fill' = Style.fill [ fill.color "rgba(255, 255, 255, 0.5)" ]
|
||||
Fable.OpenLayers.Style.style [
|
||||
|
||||
Style.style [
|
||||
style.fill fill'
|
||||
style.stroke stroke'
|
||||
style.text (
|
||||
@@ -414,9 +440,8 @@ let selectStyle name =
|
||||
|
||||
[<HookComponent>]
|
||||
let userAccessModal isRegistered isActive =
|
||||
// let showModal, setModal = Hook.useState (isActive || isRegistered)
|
||||
Hook.useHmr hmr
|
||||
|
||||
console.debug $"user registered={isRegistered} active={isActive}"
|
||||
let accessNotifier =
|
||||
if not isRegistered then
|
||||
html
|
||||
@@ -446,7 +471,7 @@ let userAccessModal isRegistered isActive =
|
||||
"""
|
||||
|
||||
[<LitElement("atlas-app")>]
|
||||
let SelectApp () =
|
||||
let AtlasApp () =
|
||||
Hook.useHmr hmr
|
||||
LitElement.init (fun cfg -> cfg.useShadowDom <- false) |> ignore
|
||||
|
||||
@@ -493,7 +518,7 @@ let SelectApp () =
|
||||
// // TODO: Refresh in expiry time?
|
||||
// sessionStorage["barentswatch_token"] <- token.AccessToken
|
||||
|
||||
do! Utils.initAtlantisSessionUrls ()
|
||||
do! Auth.initSessionUrls ()
|
||||
|
||||
// let drifters = driftersJobApi ()
|
||||
// let! fenceRadius = drifters.getFenceRadius()
|
||||
@@ -537,7 +562,6 @@ let SelectApp () =
|
||||
"click",
|
||||
fun (event: MapBrowserEvent) ->
|
||||
let features = model.map.getFeaturesAtPixel event.pixel
|
||||
console.debug ("Features:", features)
|
||||
features
|
||||
|> Array.tryHead
|
||||
|> Option.iter (fun feature ->
|
||||
@@ -563,12 +587,10 @@ let SelectApp () =
|
||||
|
||||
let selectedModelAreaArchives =
|
||||
model.selectedModelArea
|
||||
|> Option.map (fun modelArea ->
|
||||
if Map.containsKey modelArea.modelAreaId model.archives then
|
||||
model.archives[modelArea.modelAreaId]
|
||||
else
|
||||
[||]
|
||||
)
|
||||
|> Option.bind (fun modelArea ->
|
||||
model.archives
|
||||
|> Map.tryFind modelArea.modelAreaId
|
||||
)
|
||||
|> Option.defaultValue [||]
|
||||
|
||||
html
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<DefineConstants>FABLE_COMPILER</DefineConstants>
|
||||
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
||||
<Version>2.87.0</Version>
|
||||
@@ -11,27 +11,27 @@
|
||||
<Compile Include="Atlas.fs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Fable.Browser.IndexedDB" Version="2.2.0" />
|
||||
<PackageReference Include="Fable.Browser.WebGL" Version="1.3.0" />
|
||||
<PackageReference Include="Fable.Core" Version="4.4.0" />
|
||||
<PackageReference Include="Fable.Elmish" Version="4.2.0" />
|
||||
<PackageReference Include="Fable.Fetch" Version="2.7.0" />
|
||||
<PackageReference Include="Fable.Lit" Version="1.6.2-oceanbox" />
|
||||
<PackageReference Include="Fable.Lit.React" Version="1.6.2-oceanbox" />
|
||||
<PackageReference Include="Fable.Lit.Elmish" Version="1.6.2-oceanbox" />
|
||||
<PackageReference Include="Fable.Promise" Version="3.2.0" />
|
||||
<PackageReference Include="Fable.React" Version="9.4.0" />
|
||||
<PackageReference Include="Fable.Remoting.Client" Version="7.32.0" />
|
||||
<PackageReference Include="Fable.Remoting.MsgPack" Version="1.24.0" />
|
||||
<PackageReference Include="Fable.OpenLayers" Version="2.19.0" />
|
||||
<PackageReference Include="Fable.SignalR.Elmish" Version="2.1.0" />
|
||||
<PackageReference Include="Fable.SimpleHttp" Version="3.6.0" />
|
||||
<PackageReference Include="Feliz" Version="2.9.0" />
|
||||
<PackageReference Include="Feliz.CompilerPlugins" Version="2.2.0" />
|
||||
<PackageReference Include="Thoth.Fetch" Version="3.0.1" />
|
||||
<PackageReference Include="Thoth.Json" Version="10.4.1" />
|
||||
<PackageReference Include="Matplotlib.ColorMaps" Version="3.0.1" />
|
||||
<PackageReference Update="FSharp.Core" Version="9.0.303" />
|
||||
<PackageReference Include="Fable.Browser.IndexedDB" />
|
||||
<PackageReference Include="Fable.Browser.WebGL" />
|
||||
<PackageReference Include="Fable.Core" />
|
||||
<PackageReference Include="Fable.Elmish" />
|
||||
<PackageReference Include="Fable.Fetch" />
|
||||
<PackageReference Include="Fable.Lit" />
|
||||
<PackageReference Include="Fable.Lit.React" />
|
||||
<PackageReference Include="Fable.Lit.Elmish" />
|
||||
<PackageReference Include="Fable.Promise" />
|
||||
<PackageReference Include="Fable.React" />
|
||||
<PackageReference Include="Fable.Remoting.Client" />
|
||||
<PackageReference Include="Fable.Remoting.MsgPack" />
|
||||
<PackageReference Include="Fable.OpenLayers" />
|
||||
<PackageReference Include="Fable.SignalR.Elmish" />
|
||||
<PackageReference Include="Fable.SimpleHttp" />
|
||||
<PackageReference Include="Feliz" />
|
||||
<PackageReference Include="Feliz.CompilerPlugins" />
|
||||
<PackageReference Include="Thoth.Fetch" />
|
||||
<PackageReference Include="Thoth.Json" />
|
||||
<PackageReference Include="Matplotlib.ColorMaps" />
|
||||
<PackageReference Include="FSharp.Core" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Lib\Lib.fsproj" />
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"version": 1,
|
||||
"version": 2,
|
||||
"dependencies": {
|
||||
"net9.0": {
|
||||
"net10.0": {
|
||||
"Fable.Browser.IndexedDB": {
|
||||
"type": "Direct",
|
||||
"requested": "[2.2.0, )",
|
||||
@@ -225,46 +225,6 @@
|
||||
"Fable.Core": "4.1.0"
|
||||
}
|
||||
},
|
||||
"Dapr.Actors": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.16.0",
|
||||
"contentHash": "s9v6VofXXYoRqZJQlQbvNYYSlGhkL+Z+bpqrx1TRo06kLhANeDmXA9yeVaD+1KwJIO1chUFj5O4iKuTxIkg1sA==",
|
||||
"dependencies": {
|
||||
"Dapr.Client": "1.16.0",
|
||||
"Dapr.Common": "1.16.0",
|
||||
"Google.Api.CommonProtos": "2.17.0",
|
||||
"Google.Protobuf": "3.32.0",
|
||||
"Grpc.Net.Client": "2.71.0",
|
||||
"Microsoft.Extensions.Configuration": "9.0.8",
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "9.0.8",
|
||||
"Microsoft.Extensions.DependencyInjection": "9.0.8",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.8",
|
||||
"Microsoft.Extensions.Http": "9.0.8",
|
||||
"Microsoft.Extensions.Logging": "9.0.8",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.8",
|
||||
"Microsoft.Extensions.Options": "9.0.8"
|
||||
}
|
||||
},
|
||||
"Dapr.Client": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.16.0",
|
||||
"contentHash": "dFDKol+mtQrk1lIKlEyCx3k6W0Pf+0wC6xcsaDqa0Bg+XCWDc4juROuDcSb0/L1Y+Ev6LSLDMC/FgzNWMw9YtQ==",
|
||||
"dependencies": {
|
||||
"Dapr.Common": "1.16.0",
|
||||
"Dapr.Protos": "1.16.0",
|
||||
"Google.Api.CommonProtos": "2.17.0",
|
||||
"Google.Protobuf": "3.32.0",
|
||||
"Grpc.Net.Client": "2.71.0",
|
||||
"Microsoft.Extensions.Configuration": "9.0.8",
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "9.0.8",
|
||||
"Microsoft.Extensions.DependencyInjection": "9.0.8",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.8",
|
||||
"Microsoft.Extensions.Http": "9.0.8",
|
||||
"Microsoft.Extensions.Logging": "9.0.8",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.8",
|
||||
"Microsoft.Extensions.Options": "9.0.8"
|
||||
}
|
||||
},
|
||||
"Dapr.Common": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.16.0",
|
||||
@@ -296,14 +256,6 @@
|
||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.8"
|
||||
}
|
||||
},
|
||||
"Drifters.Api": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.22.0",
|
||||
"contentHash": "EQguKE22Tfd3ayO/jdWiWMBK5R1uzcYo+8agG3ZzAJ1ltl72mIXHqr68BKqO4uhOLtiFs8ErZa4cZ9NVueYHWA==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "9.0.201"
|
||||
}
|
||||
},
|
||||
"Fable.AST": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.2.1",
|
||||
@@ -571,7 +523,7 @@
|
||||
"atlantis.api": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "[9.0.201, )",
|
||||
"FSharp.Core": "[9.0.303, )",
|
||||
"Hipster.Api": "[1.0.1, )",
|
||||
"Petimeter.Api": "[1.0.0, )"
|
||||
}
|
||||
@@ -581,7 +533,7 @@
|
||||
"dependencies": {
|
||||
"Dapr.Actors": "[1.16.0, )",
|
||||
"Drifters.Api": "[6.22.0, )",
|
||||
"FSharp.Core": "[9.0.201, )"
|
||||
"FSharp.Core": "[9.0.303, )"
|
||||
}
|
||||
},
|
||||
"lib": {
|
||||
@@ -606,6 +558,7 @@
|
||||
"Fable.SimpleHttp": "[3.6.0, )",
|
||||
"Feliz": "[2.9.0, )",
|
||||
"Feliz.CompilerPlugins": "[2.2.0, )",
|
||||
"FsToolkit.ErrorHandling": "[5.0.1, )",
|
||||
"Hipster.Api": "[1.0.1, )",
|
||||
"Matplotlib.ColorMaps": "[3.0.1, )",
|
||||
"Oceanbox.DataAgent.Api": "[7.2.1, )",
|
||||
@@ -618,23 +571,83 @@
|
||||
"Oceanbox.DataAgent.Api": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "[9.0.201, )"
|
||||
"FSharp.Core": "[9.0.303, )"
|
||||
}
|
||||
},
|
||||
"petimeter.api": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"Dapr.Actors": "[1.16.0, )",
|
||||
"FSharp.Core": "[9.0.201, )"
|
||||
"FSharp.Core": "[9.0.303, )"
|
||||
}
|
||||
},
|
||||
"sorcerer.api": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"Drifters.Api": "[6.22.0, )",
|
||||
"FSharp.Core": "[9.0.201, )",
|
||||
"FSharp.Core": "[9.0.303, )",
|
||||
"Oceanbox.DataAgent.Api": "[7.2.1, )"
|
||||
}
|
||||
},
|
||||
"Dapr.Actors": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[1.16.0, )",
|
||||
"resolved": "1.16.0",
|
||||
"contentHash": "s9v6VofXXYoRqZJQlQbvNYYSlGhkL+Z+bpqrx1TRo06kLhANeDmXA9yeVaD+1KwJIO1chUFj5O4iKuTxIkg1sA==",
|
||||
"dependencies": {
|
||||
"Dapr.Client": "1.16.0",
|
||||
"Dapr.Common": "1.16.0",
|
||||
"Google.Api.CommonProtos": "2.17.0",
|
||||
"Google.Protobuf": "3.32.0",
|
||||
"Grpc.Net.Client": "2.71.0",
|
||||
"Microsoft.Extensions.Configuration": "9.0.8",
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "9.0.8",
|
||||
"Microsoft.Extensions.DependencyInjection": "9.0.8",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.8",
|
||||
"Microsoft.Extensions.Http": "9.0.8",
|
||||
"Microsoft.Extensions.Logging": "9.0.8",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.8",
|
||||
"Microsoft.Extensions.Options": "9.0.8"
|
||||
}
|
||||
},
|
||||
"Dapr.Client": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[1.16.0, )",
|
||||
"resolved": "1.16.0",
|
||||
"contentHash": "dFDKol+mtQrk1lIKlEyCx3k6W0Pf+0wC6xcsaDqa0Bg+XCWDc4juROuDcSb0/L1Y+Ev6LSLDMC/FgzNWMw9YtQ==",
|
||||
"dependencies": {
|
||||
"Dapr.Common": "1.16.0",
|
||||
"Dapr.Protos": "1.16.0",
|
||||
"Google.Api.CommonProtos": "2.17.0",
|
||||
"Google.Protobuf": "3.32.0",
|
||||
"Grpc.Net.Client": "2.71.0",
|
||||
"Microsoft.Extensions.Configuration": "9.0.8",
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "9.0.8",
|
||||
"Microsoft.Extensions.DependencyInjection": "9.0.8",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.8",
|
||||
"Microsoft.Extensions.Http": "9.0.8",
|
||||
"Microsoft.Extensions.Logging": "9.0.8",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.8",
|
||||
"Microsoft.Extensions.Options": "9.0.8"
|
||||
}
|
||||
},
|
||||
"Drifters.Api": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[6.22.0, )",
|
||||
"resolved": "6.22.0",
|
||||
"contentHash": "EQguKE22Tfd3ayO/jdWiWMBK5R1uzcYo+8agG3ZzAJ1ltl72mIXHqr68BKqO4uhOLtiFs8ErZa4cZ9NVueYHWA==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "9.0.201"
|
||||
}
|
||||
},
|
||||
"FsToolkit.ErrorHandling": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[5.0.1, )",
|
||||
"resolved": "5.0.1",
|
||||
"contentHash": "93oG3WSogK05H4gkikAmx5pBf30TQJfO1Jky+o/N/nv+RTP3nfOfjlmCHzuyUjQCRFOQog/xQabcky+WBWceeQ==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "9.0.300"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Version>6.20.0</Version>
|
||||
<RootNamespace>Archivist</RootNamespace>
|
||||
</PropertyGroup>
|
||||
@@ -10,9 +10,9 @@
|
||||
<Compile Include="App.fs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Fable.Lit" Version="1.4.2" />
|
||||
<PackageReference Include="Fable.Remoting.Client" Version="7.32.0" />
|
||||
<PackageReference Update="FSharp.Core" Version="9.0.201" />
|
||||
<PackageReference Include="Fable.Lit" />
|
||||
<PackageReference Include="Fable.Remoting.Client" />
|
||||
<PackageReference Include="FSharp.Core" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\Interfaces\Archmaester\Archmaester.Api.fsproj" />
|
||||
|
||||
@@ -1,28 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<DefineConstants>FABLE_COMPILER</DefineConstants>
|
||||
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
||||
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
|
||||
<Version>2.102.0</Version>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<RootNamespace>Main</RootNamespace>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<None Include="index.html"/>
|
||||
<None Include="map.html"/>
|
||||
<None Include="atlas.html"/>
|
||||
<None Include="./public/*.scss"/>
|
||||
<None Include="./public\js\*.js"/>
|
||||
<None Include="./public\js\modules\*"/>
|
||||
<Compile Include="App.fs"/>
|
||||
<None Include="index.html" />
|
||||
<None Include="map.html" />
|
||||
<None Include="atlas.html" />
|
||||
<None Include="./public/*.scss" />
|
||||
<None Include="./public\js\*.js" />
|
||||
<None Include="./public\js\modules\*" />
|
||||
<Compile Include="App.fs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Update="FSharp.Core" Version="9.0.303"/>
|
||||
<PackageReference Include="FSharp.Core" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="Atlas\Atlas.fsproj"/>
|
||||
<ProjectReference Include="Mapster\Mapster.fsproj"/>
|
||||
<ProjectReference Include="Atlas\Atlas.fsproj" />
|
||||
<ProjectReference Include="Mapster\Mapster.fsproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -1,8 +1,10 @@
|
||||
module Auth
|
||||
|
||||
open System
|
||||
|
||||
open Browser
|
||||
open Thoth.Json
|
||||
|
||||
open Remoting
|
||||
|
||||
type private JwtToken = { exp: int }
|
||||
@@ -13,10 +15,9 @@ let private jwtDecoder =
|
||||
let establishAuthentication () =
|
||||
async {
|
||||
match! authApi.IsAuthenticated () with
|
||||
| Some _ ->
|
||||
console.debug $"user authenticated"
|
||||
| Some _ -> console.debug $"user authenticated"
|
||||
| None ->
|
||||
console.log "user not authenticated"
|
||||
console.error "User not authenticated"
|
||||
window.location.href <- "/signin"
|
||||
}
|
||||
|
||||
@@ -33,7 +34,7 @@ let setId () : Async<unit> =
|
||||
let rec tokenRefreshLoop () =
|
||||
async {
|
||||
let decode (token: string) =
|
||||
localStorage[ token ].Split '.'
|
||||
localStorage[token].Split '.'
|
||||
|> fun x -> Utils.fromBase64String x[1]
|
||||
|> jwtDecoder
|
||||
|> function
|
||||
@@ -41,7 +42,7 @@ let rec tokenRefreshLoop () =
|
||||
| Error e ->
|
||||
console.log e
|
||||
0
|
||||
let t = DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()
|
||||
let t = DateTimeOffset(DateTime.Now).ToUnixTimeSeconds ()
|
||||
|
||||
do! Async.Sleep 1000
|
||||
let aExp = decode "access_token"
|
||||
@@ -49,29 +50,44 @@ let rec tokenRefreshLoop () =
|
||||
let dtA = int64 aExp - t
|
||||
let dtR = int64 rExp - t
|
||||
console.log $"{t} {dtA} {dtR}"
|
||||
if dtR < 0 then Dom.window.location.href <- "/signin"
|
||||
if dtR < 0 then
|
||||
window.location.href <- "/signin"
|
||||
if dtA < 30 then
|
||||
async {
|
||||
match! authApi.RefreshAccessToken localStorage["refresh_token"] with
|
||||
| Some (access, refresh) ->
|
||||
localStorage.setItem ("access_token", access)
|
||||
localStorage.setItem ("refresh_token", refresh)
|
||||
| None ->
|
||||
Dom.window.location.href <- "/signin"
|
||||
| None -> window.location.href <- "/signin"
|
||||
}
|
||||
|> Async.StartImmediate
|
||||
do! Async.Sleep((int dtA - 30) * 1000)
|
||||
do! Async.Sleep ((int dtA - 30) * 1000)
|
||||
tokenRefreshLoop ()
|
||||
}
|
||||
|> Async.StartImmediate
|
||||
|
||||
let startTokenRefreshLoopOrLogin () =
|
||||
async {
|
||||
match! authApi.IsAuthenticated() with
|
||||
match! authApi.IsAuthenticated () with
|
||||
| Some _ ->
|
||||
console.log "already authenticated"
|
||||
tokenRefreshLoop ()
|
||||
| None ->
|
||||
console.log "not already authenticated"
|
||||
Dom.window.location.href <- "/login"
|
||||
window.location.href <- "/login"
|
||||
}
|
||||
|
||||
let initSessionUrls () =
|
||||
async {
|
||||
try
|
||||
let! archiveUrl = servicesApi.GetArchiveService ()
|
||||
let! dataUrl = servicesApi.GetFileService ()
|
||||
|
||||
console.log $"Archive service: {archiveUrl}"
|
||||
sessionStorage["archmaester_url"] <- archiveUrl
|
||||
console.log $"Data service: {dataUrl}"
|
||||
sessionStorage["sorcerer_url"] <- dataUrl
|
||||
with e ->
|
||||
console.error("Failed fetching services. Redirecting to signin. %s", e.Message)
|
||||
window.location.href <- "/signin"
|
||||
}
|
||||
48
src/Atlantis/src/Client/Lib/Chaikin.fs
Normal file
48
src/Atlantis/src/Client/Lib/Chaikin.fs
Normal file
@@ -0,0 +1,48 @@
|
||||
module Chaikin
|
||||
|
||||
let private cut (start: float * float) (end': float * float) (ratio: float) =
|
||||
let startX, startY = start
|
||||
let endX, endY = end'
|
||||
|
||||
// Find point at a given ratio going from A to B
|
||||
let r1 =
|
||||
startX * (1.0 - ratio) + endX * ratio, startY * (1.0 - ratio) + endY * ratio
|
||||
|
||||
// Find point at a given ratio going from B to A
|
||||
let r2 =
|
||||
startX * ratio + endX * (1.0 - ratio), startY * ratio + endY * (1.0 - ratio)
|
||||
|
||||
r1, r2
|
||||
|
||||
let private chaikin (curve: (float * float) array) (iterations: int) (closed: bool) (ratio: float) =
|
||||
// If ratio is greater than 0.5 flip it so we avoid cutting across the midpoint of the line.
|
||||
let adjustedRatio = if ratio > 0.5 then 1.0 - ratio else ratio
|
||||
|
||||
let rec iterate (currentPoints: (float * float) array) remainingIterations =
|
||||
let len = currentPoints.Length
|
||||
if remainingIterations <= 0 || len = 0 then
|
||||
currentPoints
|
||||
else
|
||||
let refined = ResizeArray<float * float> ()
|
||||
|
||||
refined.Add (currentPoints[0])
|
||||
|
||||
for i in 0 .. len - 2 do
|
||||
let r1, r2 = cut currentPoints[i] currentPoints[i + 1] adjustedRatio
|
||||
refined.Add r1
|
||||
refined.Add r2
|
||||
|
||||
if closed then
|
||||
let r1, r2 = cut currentPoints[len - 1] currentPoints[0] adjustedRatio
|
||||
refined.Add r1
|
||||
refined.Add r2
|
||||
else
|
||||
refined.Add (currentPoints[len - 1])
|
||||
|
||||
iterate (refined.ToArray ()) (remainingIterations - 1)
|
||||
|
||||
iterate curve iterations
|
||||
|
||||
let chaikinDefault curve = chaikin curve 1 false 0.25
|
||||
let chaikinWithIterations iterations curve = chaikin curve iterations false 0.25
|
||||
let chaikinWithClosed closed curve = chaikin curve 1 closed 0.25
|
||||
20
src/Atlantis/src/Client/Lib/Colors.fs
Normal file
20
src/Atlantis/src/Client/Lib/Colors.fs
Normal file
@@ -0,0 +1,20 @@
|
||||
namespace Lib
|
||||
|
||||
module Colors =
|
||||
open Matplotlib.ColorMaps
|
||||
|
||||
open Atlantis.Types
|
||||
|
||||
let getColormap (colorMap: ColorMode * ColorMap) (lo, hi) =
|
||||
let mode, cmap = colorMap
|
||||
|
||||
let cm =
|
||||
match cmap with
|
||||
| ColorMap.Ocean name -> ColorMap<single>(name, lo, hi, 256, Ocean.colors)
|
||||
| ColorMap.Color16 name -> ColorMap<single>(name, lo, hi, 256, Color16.colors)
|
||||
| ColorMap.Custom _ -> failwith "not implemented"
|
||||
|
||||
match mode with
|
||||
| ColorMode.Normal -> cm
|
||||
| ColorMode.Mirror -> ColorMap<single>.mirrorPalette cm
|
||||
| ColorMode.Gray -> ColorMap<single>.toGrayscale cm
|
||||
@@ -43,9 +43,7 @@ module Types =
|
||||
let RGBAFormat: obj = jsNative
|
||||
|
||||
[<AllowNullLiteral>]
|
||||
type Matrix4 =
|
||||
interface
|
||||
end
|
||||
type Matrix4 = interface end
|
||||
|
||||
[<AllowNullLiteral>]
|
||||
type Euler =
|
||||
@@ -77,9 +75,7 @@ module Types =
|
||||
abstract Create: ?p1: obj * ?p2: obj * ?p3: obj * ?p4: obj -> Vector4
|
||||
|
||||
[<AllowNullLiteral>]
|
||||
type Float32BufferAttribute =
|
||||
interface
|
||||
end
|
||||
type Float32BufferAttribute = interface end
|
||||
and [<AllowNullLiteral>] Float32BufferAttributeType =
|
||||
[<Emit("new THREE.$0($1...)")>]
|
||||
abstract Create: p1: obj * p2: int -> Float32BufferAttribute
|
||||
@@ -221,9 +217,7 @@ module Types =
|
||||
abstract Create: unit -> DirectionalLight
|
||||
|
||||
[<AllowNullLiteral>]
|
||||
type EventDispatcher =
|
||||
interface
|
||||
end
|
||||
type EventDispatcher = interface end
|
||||
|
||||
// Type to resolve parameters for Geometries.
|
||||
[<AllowNullLiteral>]
|
||||
|
||||
@@ -91,7 +91,7 @@ module rec Timeline =
|
||||
abstract ``end``: DateTime option with set, get
|
||||
abstract start: DateTime with get, set
|
||||
abstract object: obj
|
||||
abstract type' : ItemType
|
||||
abstract type': ItemType
|
||||
|
||||
[<StringEnum>]
|
||||
[<RequireQualifiedAccess>]
|
||||
@@ -116,11 +116,14 @@ module rec Timeline =
|
||||
let inline mkTooltip (key: string) (value: obj) : TimelineTooltipOption = unbox (key, value)
|
||||
type tooltip' =
|
||||
static member inline template(value: string) : TimelineTooltipOption = mkTooltip "template" value
|
||||
static member inline template(value: (TimelineItem) -> string) : TimelineTooltipOption = mkTooltip "template" value
|
||||
static member inline template(value: (TimelineItem * TimelineItem) -> string) : TimelineTooltipOption = mkTooltip "template" value
|
||||
static member inline followMouse(value: bool): TimelineTooltipOption = mkTooltip "followMouse" value
|
||||
static member inline overflowMethod(value: TimelineTooltipOptionOverflowMethod): TimelineTooltipOption = mkTooltip "overflowMethod" value
|
||||
static member inline delay(value: int): TimelineTooltipOption = mkTooltip "delay" value
|
||||
static member inline template(value: (TimelineItem) -> string) : TimelineTooltipOption =
|
||||
mkTooltip "template" value
|
||||
static member inline template(value: (TimelineItem * TimelineItem) -> string) : TimelineTooltipOption =
|
||||
mkTooltip "template" value
|
||||
static member inline followMouse(value: bool) : TimelineTooltipOption = mkTooltip "followMouse" value
|
||||
static member inline overflowMethod(value: TimelineTooltipOptionOverflowMethod) : TimelineTooltipOption =
|
||||
mkTooltip "overflowMethod" value
|
||||
static member inline delay(value: int) : TimelineTooltipOption = mkTooltip "delay" value
|
||||
|
||||
|
||||
let private createTimelineItem options : TimelineItem = unbox options
|
||||
@@ -174,7 +177,8 @@ module rec Timeline =
|
||||
static member inline maxHeight(value: string) : TimelineOptions = mkTimelineOption "maxHeight" value
|
||||
/// This option allows you to scroll horizontally to move backwards and forwards in the time range.
|
||||
/// Only applicable when option zoomKey is defined or zoomable is false.
|
||||
static member inline horizontalScroll(value: bool) : TimelineOptions = mkTimelineOption "horizontalScroll" value
|
||||
static member inline horizontalScroll(value: bool) : TimelineOptions =
|
||||
mkTimelineOption "horizontalScroll" value
|
||||
/// NOTE: This probably does not work. Only functioning workaround we have found for nb locales is:
|
||||
///
|
||||
/// o?moment <- fun x ->
|
||||
@@ -202,18 +206,19 @@ module rec Timeline =
|
||||
static member inline groupOrder(value: string) : TimelineOptions = mkTimelineOption "groupOrder" value
|
||||
/// Use a function to render groups as react elements.
|
||||
/// Uses React.render in the bindings to apply the given function to all groups.
|
||||
static member inline groupTemplate(renderFunc: obj -> Feliz.ReactElement): TimelineOptions =
|
||||
static member inline groupTemplate(renderFunc: obj -> Feliz.ReactElement) : TimelineOptions =
|
||||
let value (item, elem, _data) =
|
||||
let root = Feliz.ReactDOM.createRoot elem
|
||||
root.render(renderFunc item)
|
||||
root.render (renderFunc item)
|
||||
mkTimelineOption "groupTemplate" value
|
||||
static member inline selectable(value: bool) : TimelineOptions = mkTimelineOption "selectable" value
|
||||
static member inline showWeekScale(value: bool) : TimelineOptions = mkTimelineOption "showWeekScale" value
|
||||
/// Reduces down to timeline.template, renamed to itemTemplate to match groupTemplate.
|
||||
/// Use a function to render items as react elements.
|
||||
/// Uses React.render in the bindings to apply the given function to all items.
|
||||
static member inline itemTemplate(renderFunc: obj -> Feliz.ReactElement): TimelineOptions =
|
||||
mkTimelineOption "template"
|
||||
static member inline itemTemplate(renderFunc: obj -> Feliz.ReactElement) : TimelineOptions =
|
||||
mkTimelineOption
|
||||
"template"
|
||||
(fun (item, element, data) ->
|
||||
Browser.Dom.console.log item
|
||||
Browser.Dom.console.log element
|
||||
@@ -223,7 +228,7 @@ module rec Timeline =
|
||||
()
|
||||
else
|
||||
let root = Feliz.ReactDOM.createRoot element
|
||||
root.render(renderFunc item)
|
||||
root.render (renderFunc item)
|
||||
)
|
||||
static member inline timeAxis(value: timeAxis) : TimelineOptions = mkTimelineOption "timeAxis" value
|
||||
static member inline tooltip(value: TimelineTooltipOption) : TimelineOptions = mkTimelineOption "tooltip" value
|
||||
@@ -231,7 +236,7 @@ module rec Timeline =
|
||||
/// Specifies whether the Timeline can be zoomed by pinching or scrolling in the window. Only applicable when option moveable is set true.
|
||||
static member inline zoomable(value: bool) : TimelineOptions = mkTimelineOption "zoomable" value
|
||||
/// Specifies how strong the zooming is for each scroll tick. Higher zooming friction will slow zooming speed.
|
||||
static member inline zoomFriction(value: float): TimelineOptions = mkTimelineOption "zoomFriction" value
|
||||
static member inline zoomFriction(value: float) : TimelineOptions = mkTimelineOption "zoomFriction" value
|
||||
/// Specifies whether the Timeline is only zoomed when an additional key is down.
|
||||
/// Available values are '' (does not apply), 'altKey', 'ctrlKey', 'shiftKey' or 'metaKey'.
|
||||
/// Only applicable when option moveable is set true.
|
||||
@@ -243,17 +248,10 @@ module rec Timeline =
|
||||
/// <example>36_000_000. ms is a minimum zoom of 1 hour in ms</example>
|
||||
static member inline zoomMin(value: float) : TimelineOptions = mkTimelineOption "zoomMin" value
|
||||
static member inline xssWhitelist(value: obj) : TimelineOptions =
|
||||
let xss = {|
|
||||
disabled = false
|
||||
filterOptions = {|
|
||||
whiteList = value
|
||||
|}
|
||||
|}
|
||||
let xss = {| disabled = false; filterOptions = {| whiteList = value |} |}
|
||||
mkTimelineOption "xss" xss
|
||||
|
||||
type timeAxis =
|
||||
interface
|
||||
end
|
||||
type timeAxis = interface end
|
||||
|
||||
[<StringEnum(CaseRules.LowerFirst)>]
|
||||
[<RequireQualifiedAccess>]
|
||||
@@ -274,9 +272,7 @@ module rec Timeline =
|
||||
static member inline showStipes(value: bool) = mkTimelineOption "showStipes" value
|
||||
|
||||
// Timeline Options
|
||||
type EditableOption =
|
||||
interface
|
||||
end
|
||||
type EditableOption = interface end
|
||||
|
||||
let inline mkEditableOption (key: string) (value: obj) : EditableOption = unbox (key, value)
|
||||
|
||||
@@ -318,9 +314,9 @@ type IDataSet =
|
||||
[<Import("DataSet", "vis-timeline/standalone")>]
|
||||
type DataSet(data: U2<TimelineItem array, TimelineGroup array>, ?options: obj) =
|
||||
|
||||
new(data: TimelineItem array) = DataSet(U2.Case1 data)
|
||||
new(data: TimelineItem array) = DataSet (U2.Case1 data)
|
||||
|
||||
new(data: TimelineGroup array) = DataSet(U2.Case2 data)
|
||||
new(data: TimelineGroup array) = DataSet (U2.Case2 data)
|
||||
|
||||
/// <summary>
|
||||
/// Add one or multiple items to the DataSet. data can be a single item or an array with items. Adding an item will fail when there already is an item with the same id. The function returns an array with the ids of the added items. See section Data Manipulation.
|
||||
@@ -450,38 +446,34 @@ type DataSet(data: U2<TimelineItem array, TimelineGroup array>, ?options: obj) =
|
||||
type AnimationOptions =
|
||||
| Animation of AnimationOption
|
||||
| Bool of bool
|
||||
and AnimationOption = {
|
||||
duration: float
|
||||
easingFunction: EasingFunction
|
||||
}
|
||||
and
|
||||
[<StringEnum(CaseRules.LowerFirst)>]
|
||||
EasingFunction =
|
||||
| Linear
|
||||
| EaseInQuad
|
||||
| EaseOutQuad
|
||||
| EaseInOutQuad
|
||||
| EaseInCubic
|
||||
| EaseOutCubic
|
||||
| EaseInOutCubic
|
||||
| EaseInQuart
|
||||
| EaseOutQuart
|
||||
| EaseInOutQuart
|
||||
| EaseInQuint
|
||||
| EaseOutQuint
|
||||
| EaseInOutQuint
|
||||
and AnimationOption = { duration: float; easingFunction: EasingFunction }
|
||||
and [<StringEnum(CaseRules.LowerFirst)>] EasingFunction =
|
||||
| Linear
|
||||
| EaseInQuad
|
||||
| EaseOutQuad
|
||||
| EaseInOutQuad
|
||||
| EaseInCubic
|
||||
| EaseOutCubic
|
||||
| EaseInOutCubic
|
||||
| EaseInQuart
|
||||
| EaseOutQuart
|
||||
| EaseInOutQuart
|
||||
| EaseInQuint
|
||||
| EaseOutQuint
|
||||
| EaseInOutQuint
|
||||
|
||||
[<AllowNullLiteral>]
|
||||
[<Import("Timeline", "vis-timeline/standalone")>]
|
||||
type Timeline(container: HTMLElement, items: U2<TimelineItem seq, DataSet>, ?groups: U2<TimelineGroup seq, DataSet>, ?options: TimelineOptions) =
|
||||
type Timeline
|
||||
(
|
||||
container: HTMLElement,
|
||||
items: U2<TimelineItem seq, DataSet>,
|
||||
?groups: U2<TimelineGroup seq, DataSet>,
|
||||
?options: TimelineOptions
|
||||
) =
|
||||
|
||||
new(container, items, groups, options) =
|
||||
Timeline(
|
||||
container = container,
|
||||
items = U2.Case2 items,
|
||||
groups = U2.Case2 groups,
|
||||
options = options
|
||||
)
|
||||
Timeline (container = container, items = U2.Case2 items, groups = U2.Case2 groups, options = options)
|
||||
|
||||
/// <summary>
|
||||
/// Add new vertical bar representing a custom time that can be dragged by the user. Parameter time can be a Date,
|
||||
@@ -559,8 +551,8 @@ type Timeline(container: HTMLElement, items: U2<TimelineItem seq, DataSet>, ?gro
|
||||
member _.getCustomTime() : DateTime = jsNative
|
||||
member _.getCustomTime(ids: Id array) : DateTime = jsNative
|
||||
|
||||
member _.itemsData : IDataSet = jsNative
|
||||
member _.groupsData : IDataSet = jsNative
|
||||
member _.itemsData: IDataSet = jsNative
|
||||
member _.groupsData: IDataSet = jsNative
|
||||
member _.moveTo(time: DateTime, ?options, ?callback) : unit = jsNative
|
||||
|
||||
member _.on(_: string, _: 'a -> unit) : unit = jsNative
|
||||
@@ -601,7 +593,7 @@ type Timeline(container: HTMLElement, items: U2<TimelineItem seq, DataSet>, ?gro
|
||||
member _.setGroups(_: DataSet) : unit = jsNative
|
||||
|
||||
member _.setOptions(_: obj) : unit = jsNative
|
||||
member _.setSelection(_: string []) : unit = jsNative
|
||||
member _.setSelection(_: string[]) : unit = jsNative
|
||||
member _.setWindow(start: DateTime, end': DateTime, ?options: obj, ?callback: obj -> unit) : unit = jsNative
|
||||
member _.zoomIn(_: float) : unit = jsNative
|
||||
|
||||
@@ -630,10 +622,11 @@ type Timeline(container: HTMLElement, items: U2<TimelineItem seq, DataSet>, ?gro
|
||||
///
|
||||
/// A callback function can be passed as an optional parameter. This function will be called at the end of zoomOut function.
|
||||
/// </summary>
|
||||
member _.zoomOut(percentage: float, options: {| animation: AnimationOption |}, ?callback: obj -> unit) : unit = jsNative
|
||||
member _.zoomOut(percentage: float, options: {| animation: AnimationOption |}, ?callback: obj -> unit) : unit =
|
||||
jsNative
|
||||
member _.zoomOut(percentage: float, options: {| animation: bool |}, ?callback: obj -> unit) : unit = jsNative
|
||||
member _.zoomOut(percentage: float) : unit = jsNative
|
||||
|
||||
[<ImportDefault("moment")>]
|
||||
type Moment(timestamp: int) =
|
||||
member _.locale(_:string): Moment = jsNative
|
||||
member _.locale(_: string) : Moment = jsNative
|
||||
@@ -13,14 +13,14 @@ let floatingBox () =
|
||||
let this, props =
|
||||
LitElement.init (fun cfg ->
|
||||
cfg.useShadowDom <- true
|
||||
cfg.props <-
|
||||
{|
|
||||
title = Prop.Of ""
|
||||
xPos = Prop.Of (Dom.window.document.documentElement.clientWidth - 250.0)
|
||||
yPos = Prop.Of 50.0
|
||||
|}
|
||||
cfg.props <- {|
|
||||
title = Prop.Of ""
|
||||
xPos = Prop.Of (Dom.window.document.documentElement.clientWidth - 250.0)
|
||||
yPos = Prop.Of 50.0
|
||||
|}
|
||||
cfg.styles <- [
|
||||
css $"""
|
||||
css
|
||||
$"""
|
||||
#floating-box {{
|
||||
position: fixed;
|
||||
padding: 3px;
|
||||
@@ -38,8 +38,8 @@ let floatingBox () =
|
||||
font-family: Georgia;
|
||||
}}
|
||||
"""
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
let xPos = props.xPos.Value
|
||||
let yPos = props.yPos.Value
|
||||
@@ -47,35 +47,41 @@ let floatingBox () =
|
||||
if this.shadowRoot |> isNull |> not then
|
||||
Hook.useEffectOnce (fun () ->
|
||||
let document = Dom.document
|
||||
let floatingBox = getShadowElementById this "floating-box" :?> Browser.Types.HTMLDivElement
|
||||
let floatingBox =
|
||||
getShadowElementById this "floating-box" :?> Browser.Types.HTMLDivElement
|
||||
let mutable x, y = xPos, yPos
|
||||
let mutable x', y' = xPos, yPos
|
||||
floatingBox.onmousedown <- fun e ->
|
||||
this.dispatchCustomEvent("dragStart")
|
||||
// e.preventDefault ()
|
||||
// NOTE(SimenLK): Hacky attempt to prevent moving the floating box when pressing items within it
|
||||
x' <- e.clientX
|
||||
y' <- e.clientY
|
||||
floatingBox.onmousedown <-
|
||||
fun e ->
|
||||
this.dispatchCustomEvent ("dragStart")
|
||||
// e.preventDefault ()
|
||||
// NOTE(SimenLK): Hacky attempt to prevent moving the floating box when pressing items within it
|
||||
x' <- e.clientX
|
||||
y' <- e.clientY
|
||||
|
||||
floatingBox?style?cursor <- "grabbing"
|
||||
document.onmouseup <- fun _ ->
|
||||
this.dispatchCustomEvent("dragStop")
|
||||
floatingBox?style?cursor <- ""
|
||||
document.onmouseup <- fun _ -> ()
|
||||
document.onmousemove <- fun _ -> ()
|
||||
document.onmousemove <- fun e' ->
|
||||
this.dispatchCustomEvent("dragging")
|
||||
e.preventDefault ()
|
||||
x <- x' - e'.clientX
|
||||
y <- y' - e'.clientY
|
||||
x' <- e'.clientX
|
||||
y' <- e'.clientY
|
||||
floatingBox?style?left <- $"{(floatingBox.offsetLeft - x)}px" // $"{e'.clientX}px"
|
||||
floatingBox?style?top <- $"{(floatingBox.offsetTop - y)}px" // $"{e'.clientY}px"
|
||||
()
|
||||
())
|
||||
floatingBox?style?cursor <- "grabbing"
|
||||
document.onmouseup <-
|
||||
fun _ ->
|
||||
this.dispatchCustomEvent ("dragStop")
|
||||
floatingBox?style?cursor <- ""
|
||||
document.onmouseup <- fun _ -> ()
|
||||
document.onmousemove <- fun _ -> ()
|
||||
document.onmousemove <-
|
||||
fun e' ->
|
||||
this.dispatchCustomEvent ("dragging")
|
||||
e.preventDefault ()
|
||||
x <- x' - e'.clientX
|
||||
y <- y' - e'.clientY
|
||||
x' <- e'.clientX
|
||||
y' <- e'.clientY
|
||||
floatingBox?style?left <- $"{(floatingBox.offsetLeft - x)}px" // $"{e'.clientX}px"
|
||||
floatingBox?style?top <- $"{(floatingBox.offsetTop - y)}px" // $"{e'.clientY}px"
|
||||
()
|
||||
()
|
||||
)
|
||||
|
||||
html $"""
|
||||
html
|
||||
$"""
|
||||
<div
|
||||
id="floating-box" resize="both"
|
||||
style="top: {yPos}px; left: {xPos}px;"
|
||||
|
||||
@@ -11,74 +11,85 @@ let openDB () : IDBDatabase JS.Promise =
|
||||
Promise.create (fun resolve reject ->
|
||||
let openRequest = indexedDB.``open`` ("oceanbox", dbVersion)
|
||||
|
||||
openRequest.onerror <- (fun _ ->
|
||||
console.error "Failed opening oceanbox database"
|
||||
reject (unbox "Fail"))
|
||||
openRequest.onerror <-
|
||||
fun _ ->
|
||||
console.error "Failed opening oceanbox database"
|
||||
reject (unbox "Fail")
|
||||
|
||||
openRequest.onsuccess <-
|
||||
fun _ ->
|
||||
// console.log "oceanbox database opened"
|
||||
let db: IDBDatabase = unbox openRequest.result
|
||||
resolve db
|
||||
|
||||
openRequest.onupgradeneeded <-
|
||||
fun ev ->
|
||||
// console.log $"Upgrading oceanbox database"
|
||||
let db: IDBDatabase = ev.target?result
|
||||
db.onerror <- (fun ev -> console.error $"Could not upgrade database: {ev.target}")
|
||||
let archives =
|
||||
db.createObjectStore ("PlainGrids", !!{| keyPath = "GridSha"; autoIncrement = true |})
|
||||
archives.createIndex ("Vertices", "Vertices") |> ignore
|
||||
archives.createIndex ("Indices", "Indices") |> ignore
|
||||
|
||||
openRequest.onsuccess <- (fun _ ->
|
||||
// console.log "oceanbox database opened"
|
||||
let db: IDBDatabase = unbox openRequest.result
|
||||
resolve db
|
||||
)
|
||||
openRequest.onupgradeneeded <- (fun ev ->
|
||||
// console.log $"Upgrading oceanbox database"
|
||||
let db: IDBDatabase = ev.target?result
|
||||
db.onerror <- (fun ev -> console.error $"Could not upgrade database: {ev.target}")
|
||||
let archives = db.createObjectStore("PlainGrids", !!{| keyPath = "GridSha"; autoIncrement = true |})
|
||||
archives.createIndex("Vertices", "Vertices") |> ignore
|
||||
archives.createIndex("Indices", "Indices") |> ignore
|
||||
)
|
||||
)
|
||||
|
||||
let getFromIDB<'T> (db: IDBDatabase) (store: string) (sha: string): 'T option JS.Promise =
|
||||
let getFromIDB<'T> (db: IDBDatabase) (store: string) (sha: string) : 'T option JS.Promise =
|
||||
Promise.create (fun resolve reject ->
|
||||
let t = db.transaction(store, IDBTransactionMode.Readonly)
|
||||
let t = db.transaction (store, IDBTransactionMode.Readonly)
|
||||
let index = t.objectStore store
|
||||
let request = index.get sha
|
||||
t.commit ()
|
||||
|
||||
request.onerror <- (fun _ ->
|
||||
console.error $"Failed retrieving {store}: {sha}"
|
||||
reject (unbox false))
|
||||
request.onerror <-
|
||||
fun _ ->
|
||||
console.error $"Failed retrieving {store}: {sha}"
|
||||
reject (unbox false)
|
||||
|
||||
request.onsuccess <- (fun _ ->
|
||||
// console.log $"checking {store} {sha}"
|
||||
// NOTE(SimenLK): when getting on aid, if it is not present, the result will be undefined, otherwise, unbox
|
||||
// to get the DB archive grid
|
||||
request.result
|
||||
|> Option.map unbox<'T>
|
||||
|> resolve)
|
||||
request.onsuccess <-
|
||||
fun _ ->
|
||||
// console.log $"checking {store} {sha}"
|
||||
// NOTE(SimenLK): when getting on aid, if it is not present, the result will be undefined, otherwise, unbox
|
||||
// to get the DB archive grid
|
||||
request.result |> Option.map unbox<'T> |> resolve
|
||||
)
|
||||
|
||||
let saveToIDB (db: IDBDatabase) (store: string) item =
|
||||
Promise.create (fun resolve reject ->
|
||||
let t = db.transaction(store, IDBTransactionMode.Readwrite)
|
||||
let archives = t.objectStore(store)
|
||||
let t = db.transaction (store, IDBTransactionMode.Readwrite)
|
||||
let archives = t.objectStore (store)
|
||||
|
||||
let req = archives.add item
|
||||
req.onerror <- (fun ev ->
|
||||
let error = ev.target :?> IDBRequest
|
||||
console.error $"saveToIDB error: {error.error}"
|
||||
reject (error.error :?> exn))
|
||||
req.onsuccess <- (fun _ ->
|
||||
console.debug $"Successfully added {store} to indexedDB"
|
||||
resolve ())
|
||||
)
|
||||
req.onerror <-
|
||||
fun ev ->
|
||||
let error = ev.target :?> IDBRequest
|
||||
console.error $"saveToIDB error: {error.error}"
|
||||
reject (error.error :?> exn)
|
||||
|
||||
req.onsuccess <-
|
||||
fun _ ->
|
||||
console.debug $"Successfully added {store} to indexedDB"
|
||||
resolve ()
|
||||
)
|
||||
|
||||
let tryResetOutdatedIDB () =
|
||||
Promise.create (fun resolve reject ->
|
||||
openDB () |> Promise.iter (fun db ->
|
||||
openDB ()
|
||||
|> Promise.iter (fun db ->
|
||||
if db.objectStoreNames.contains "WireframeGrids" then
|
||||
db.close ()
|
||||
let req = indexedDB.deleteDatabase "oceanbox"
|
||||
req.onerror <- (fun ev ->
|
||||
let error = ev.target :?> IDBRequest
|
||||
console.error $"Reset indexDB error: {error.error}"
|
||||
reject (error.error :?> exn))
|
||||
req.onsuccess <- (fun _ ->
|
||||
console.debug $"Successfully reset indexedDB"
|
||||
resolve ())
|
||||
req.onerror <-
|
||||
fun ev ->
|
||||
let error = ev.target :?> IDBRequest
|
||||
console.error $"Reset indexDB error: {error.error}"
|
||||
reject (error.error :?> exn)
|
||||
|
||||
req.onsuccess <-
|
||||
fun _ ->
|
||||
console.debug $"Successfully reset indexedDB"
|
||||
resolve ()
|
||||
else
|
||||
resolve ()
|
||||
resolve ()
|
||||
)
|
||||
)
|
||||
@@ -5,11 +5,18 @@ open Fable.Core.JsInterop
|
||||
let register () = ()
|
||||
|
||||
importAll "../public/style.scss"
|
||||
|
||||
importSideEffects "@spectrum-web-components/theme/sp-theme.js"
|
||||
importSideEffects "@spectrum-web-components/theme/spectrum-two/scale-medium.js"
|
||||
importSideEffects "@spectrum-web-components/theme/spectrum-two/scale-large.js"
|
||||
importSideEffects "@spectrum-web-components/theme/spectrum-two/theme-light.js"
|
||||
importSideEffects "@spectrum-web-components/theme/spectrum-two/theme-dark.js"
|
||||
importSideEffects "@spectrum-web-components/accordion/sp-accordion.js"
|
||||
importSideEffects "@spectrum-web-components/accordion/sp-accordion-item.js"
|
||||
importSideEffects "@spectrum-web-components/action-button/sp-action-button.js"
|
||||
importSideEffects "@spectrum-web-components/action-group/sp-action-group.js"
|
||||
importSideEffects "@spectrum-web-components/action-menu/sp-action-menu.js"
|
||||
importSideEffects "@spectrum-web-components/action-menu/sync/sp-action-menu.js"
|
||||
importSideEffects "@spectrum-web-components/alert-banner/sp-alert-banner.js"
|
||||
importSideEffects "@spectrum-web-components/button/sp-button.js"
|
||||
importSideEffects "@spectrum-web-components/card/sp-card.js"
|
||||
importSideEffects "@spectrum-web-components/checkbox/sp-checkbox.js"
|
||||
@@ -24,13 +31,12 @@ importSideEffects "@spectrum-web-components/menu/sp-menu-group.js"
|
||||
importSideEffects "@spectrum-web-components/menu/sp-menu-item.js"
|
||||
importSideEffects "@spectrum-web-components/menu/sp-menu-divider.js"
|
||||
importSideEffects "@spectrum-web-components/number-field/sp-number-field.js"
|
||||
importSideEffects "@spectrum-web-components/picker/sync/sp-picker.js"
|
||||
importSideEffects "@spectrum-web-components/popover/sp-popover.js"
|
||||
importSideEffects "@spectrum-web-components/progress-bar/sp-progress-bar.js"
|
||||
importSideEffects "@spectrum-web-components/progress-circle/sp-progress-circle.js"
|
||||
importSideEffects "@spectrum-web-components/radio/sp-radio.js"
|
||||
importSideEffects "@spectrum-web-components/radio/sp-radio-group.js"
|
||||
importSideEffects "@spectrum-web-components/slider/sp-slider.js"
|
||||
importSideEffects "@spectrum-web-components/slider/sync/sp-slider.js"
|
||||
importSideEffects "@spectrum-web-components/slider/sp-slider-handle.js"
|
||||
importSideEffects "@spectrum-web-components/split-view/sp-split-view.js"
|
||||
importSideEffects "@spectrum-web-components/switch/sp-switch.js"
|
||||
@@ -44,20 +50,21 @@ importSideEffects "@spectrum-web-components/table/sp-table-head-cell.js"
|
||||
importSideEffects "@spectrum-web-components/table/sp-table-row.js"
|
||||
importSideEffects "@spectrum-web-components/tabs/sp-tabs.js"
|
||||
importSideEffects "@spectrum-web-components/tabs/sp-tab.js"
|
||||
importSideEffects "@spectrum-web-components/tabs/sp-tab-panel.js"
|
||||
importSideEffects "@spectrum-web-components/textfield/sp-textfield.js"
|
||||
importSideEffects "@spectrum-web-components/theme/sp-theme.js"
|
||||
importSideEffects "@spectrum-web-components/theme/spectrum-two/scale-medium.js"
|
||||
importSideEffects "@spectrum-web-components/theme/spectrum-two/scale-large.js"
|
||||
importSideEffects "@spectrum-web-components/theme/spectrum-two/theme-light.js"
|
||||
importSideEffects "@spectrum-web-components/theme/spectrum-two/theme-dark.js"
|
||||
importSideEffects "@spectrum-web-components/toast/sp-toast.js"
|
||||
importSideEffects "@spectrum-web-components/top-nav/sp-top-nav.js"
|
||||
importSideEffects "@spectrum-web-components/top-nav/sp-top-nav-item.js"
|
||||
importSideEffects "@spectrum-web-components/underlay/sp-underlay.js"
|
||||
importSideEffects "@spectrum-web-components/overlay/overlay-trigger.js"
|
||||
importSideEffects "@spectrum-web-components/overlay/sync/overlay-trigger.js"
|
||||
importSideEffects "@spectrum-web-components/overlay/sp-overlay.js"
|
||||
importSideEffects "@spectrum-web-components/tooltip/sp-tooltip.js"
|
||||
importSideEffects "@spectrum-web-components/dialog/sp-dialog.js"
|
||||
importSideEffects "@spectrum-web-components/dialog/sp-dialog-base.js"
|
||||
importSideEffects "@spectrum-web-components/dialog/sp-dialog-wrapper.js"
|
||||
importSideEffects "@spectrum-web-components/infield-button/sp-infield-button.js"
|
||||
importSideEffects "@spectrum-web-components/picker/sync/sp-picker.js"
|
||||
importSideEffects "@spectrum-web-components/contextual-help/sp-contextual-help.js"
|
||||
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-add.js"
|
||||
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-add-circle.js"
|
||||
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-alert.js"
|
||||
@@ -67,6 +74,7 @@ importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-copy.js
|
||||
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-crosshairs.js"
|
||||
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-target.js"
|
||||
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-delete.js"
|
||||
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-color-harmony.js"
|
||||
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-deselect.js"
|
||||
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-erase.js"
|
||||
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-reorder.js"
|
||||
@@ -84,8 +92,13 @@ importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-asteris
|
||||
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-bug.js"
|
||||
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-sampler.js"
|
||||
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-measure.js"
|
||||
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-ruler.js"
|
||||
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-circle.js"
|
||||
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-image-map-rectangle.js"
|
||||
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-histogram.js"
|
||||
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-chart-bar-vert.js"
|
||||
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-chart-trend.js"
|
||||
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-location.js"
|
||||
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-crop.js"
|
||||
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-download.js"
|
||||
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-edit.js"
|
||||
@@ -108,4 +121,4 @@ importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-checkma
|
||||
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-rotate-cc-w.js"
|
||||
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-align-bottom.js"
|
||||
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-transform-perspective.js"
|
||||
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-search.js"
|
||||
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-search.js"
|
||||
41
src/Atlantis/src/Client/Lib/Intl.fs
Normal file
41
src/Atlantis/src/Client/Lib/Intl.fs
Normal file
@@ -0,0 +1,41 @@
|
||||
module Intl
|
||||
|
||||
open Browser
|
||||
open Fable.Core
|
||||
open Fable.Core.JsInterop
|
||||
|
||||
|
||||
[<Emit("new Intl.DateTimeFormat($0, $1)")>]
|
||||
let private dateTimeFormat (lang: string) (opt: obj) = jsNative
|
||||
|
||||
// See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat
|
||||
let private uk : obj =
|
||||
let opts = {|
|
||||
dateStyle = "full"
|
||||
timeStyle = "short"
|
||||
|}
|
||||
|
||||
dateTimeFormat "en-GB" opts
|
||||
|
||||
// See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat
|
||||
let private ukShort : obj =
|
||||
let opts = {|
|
||||
dateStyle = "short"
|
||||
|}
|
||||
|
||||
dateTimeFormat "en-GB" opts
|
||||
|
||||
// See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat
|
||||
let private ukDateTimeShort : obj =
|
||||
let opts = {|
|
||||
dateStyle = "short"
|
||||
timeStyle = "short"
|
||||
|}
|
||||
|
||||
dateTimeFormat "en-GB" opts
|
||||
|
||||
/// Returns date string formatted as e.g.: "Wednesday 11 June 2025 at 06:00"
|
||||
let format (date: System.DateTime) : string = uk?format date
|
||||
|
||||
let shortDate (date: System.DateTime) : string = ukShort?format date
|
||||
let shortDateTime (date: System.DateTime) : string = ukDateTimeShort?format date
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<DefineConstants>FABLE_COMPILER</DefineConstants>
|
||||
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
||||
<Version>2.87.0</Version>
|
||||
@@ -9,43 +9,49 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="../../Shared/Atlantis.Shared.fs" />
|
||||
<Compile Include="Sentry.fs"/>
|
||||
<Compile Include="Fable.VisJS.fs" />
|
||||
<Compile Include="Types.fs" />
|
||||
<Compile Include="Utils.fs" />
|
||||
<Compile Include="React.fs" />
|
||||
<Compile Include="Sentry.fs" />
|
||||
<Compile Include="Remoting.fs" />
|
||||
<Compile Include="Auth.fs" />
|
||||
<Compile Include="Search.fs" />
|
||||
<Compile Include="Imports.fs" />
|
||||
<Compile Include="Fable.VisJS.fs" />
|
||||
<Compile Include="IDB.fs" />
|
||||
<Compile Include="FloatingBox.fs" />
|
||||
<Compile Include="Turf.fs" />
|
||||
<Compile Include="Types.fs" />
|
||||
<Compile Include="Remoting.fs" />
|
||||
<Compile Include="Utils.fs" />
|
||||
<Compile Include="Auth.fs" />
|
||||
<Compile Include="Chaikin.fs" />
|
||||
<Compile Include="Umami.fs" />
|
||||
<Compile Include="StreamLayer.fs" />
|
||||
<Compile Include="WebGLLayer.fs" />
|
||||
<Compile Include="Intl.fs" />
|
||||
<Compile Include="Colors.fs" />
|
||||
<Compile Include="Maps.fs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Fable.Browser.IndexedDB" Version="2.2.0" />
|
||||
<PackageReference Include="Fable.Browser.WebGL" Version="1.3.0" />
|
||||
<PackageReference Include="Fable.Core" Version="4.4.0" />
|
||||
<PackageReference Include="Fable.Elmish" Version="4.2.0" />
|
||||
<PackageReference Include="Fable.Fetch" Version="2.7.0" />
|
||||
<PackageReference Include="Fable.Lit" Version="1.6.2-oceanbox" />
|
||||
<PackageReference Include="Fable.Lit.React" Version="1.6.2-oceanbox" />
|
||||
<PackageReference Include="Fable.Lit.Elmish" Version="1.6.2-oceanbox" />
|
||||
<PackageReference Include="Fable.Promise" Version="3.2.0" />
|
||||
<PackageReference Include="Fable.React" Version="9.4.0" />
|
||||
<PackageReference Include="Fable.Remoting.Client" Version="7.32.0" />
|
||||
<PackageReference Include="Fable.Remoting.MsgPack" Version="1.24.0" />
|
||||
<PackageReference Include="Fable.OpenLayers" Version="2.19.0" />
|
||||
<PackageReference Include="Fable.SignalR.Elmish" Version="2.1.0" />
|
||||
<PackageReference Include="Fable.SimpleHttp" Version="3.6.0" />
|
||||
<PackageReference Include="Feliz" Version="2.9.0" />
|
||||
<PackageReference Include="Feliz.CompilerPlugins" Version="2.2.0" />
|
||||
<PackageReference Include="Thoth.Fetch" Version="3.0.1" />
|
||||
<PackageReference Include="Thoth.Json" Version="10.4.1" />
|
||||
<PackageReference Include="Matplotlib.ColorMaps" Version="3.0.1" />
|
||||
<PackageReference Update="FSharp.Core" Version="9.0.303" />
|
||||
<PackageReference Include="Fable.Browser.IndexedDB" />
|
||||
<PackageReference Include="Fable.Browser.WebGL" />
|
||||
<PackageReference Include="Fable.Core" />
|
||||
<PackageReference Include="Fable.Elmish" />
|
||||
<PackageReference Include="Fable.Fetch" />
|
||||
<PackageReference Include="Fable.Lit" />
|
||||
<PackageReference Include="Fable.Lit.React" />
|
||||
<PackageReference Include="Fable.Lit.Elmish" />
|
||||
<PackageReference Include="Fable.Promise" />
|
||||
<PackageReference Include="Fable.React" />
|
||||
<PackageReference Include="Fable.Remoting.Client" />
|
||||
<PackageReference Include="Fable.Remoting.MsgPack" />
|
||||
<PackageReference Include="Fable.OpenLayers" />
|
||||
<PackageReference Include="Fable.SignalR.Elmish" />
|
||||
<PackageReference Include="Fable.SimpleHttp" />
|
||||
<PackageReference Include="Feliz" />
|
||||
<PackageReference Include="Feliz.CompilerPlugins" />
|
||||
<PackageReference Include="FsToolkit.ErrorHandling" />
|
||||
<PackageReference Include="Thoth.Fetch" />
|
||||
<PackageReference Include="Thoth.Json" />
|
||||
<PackageReference Include="Matplotlib.ColorMaps" />
|
||||
<PackageReference Include="FSharp.Core" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\Interfaces\Atlantis\Atlantis.Api.fsproj" />
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
module Maps
|
||||
|
||||
open System.Text.RegularExpressions
|
||||
open Browser
|
||||
open Fable.Core
|
||||
open Fable.Core.JsInterop
|
||||
@@ -18,7 +17,7 @@ open Atlantis.Types
|
||||
|
||||
let theCenter = [| 14.39; 65.2 |]
|
||||
|
||||
let flyTo (map: OlMap) (zoom: float) (c: float []) =
|
||||
let flyTo (map: OlMap) (zoom: float) (c: float[]) =
|
||||
let v = map.getView ()
|
||||
|
||||
Animation.animationOptions [
|
||||
@@ -32,7 +31,7 @@ let flyTo (map: OlMap) (zoom: float) (c: float []) =
|
||||
|
||||
let zoomOut (map: OlMap) (zoom: Zoom) =
|
||||
let v = map.getView ()
|
||||
let center = v.getCenter()
|
||||
let center = v.getCenter ()
|
||||
let anim =
|
||||
Animation.animationOptions [
|
||||
Animation.animation.center center
|
||||
@@ -42,7 +41,7 @@ let zoomOut (map: OlMap) (zoom: Zoom) =
|
||||
Animation.animation.easing Animation.easeOut
|
||||
]
|
||||
|
||||
do v.animate(anim)
|
||||
do v.animate (anim)
|
||||
|
||||
module SimpleWebGLLayer =
|
||||
[<ImportDefault("../public/js/WebGLLayer")>]
|
||||
@@ -69,6 +68,7 @@ type barbTile =
|
||||
static member inline drawColor(value: string) = unbox ("drawColor", value)
|
||||
static member inline time(value: int) = unbox ("time", value)
|
||||
static member inline url(value: string) = unbox ("url", value)
|
||||
static member inline template(value: string) = unbox ("template", value)
|
||||
|
||||
module BarbTile =
|
||||
type BarbTile =
|
||||
@@ -103,50 +103,40 @@ let getInteractionByName (map: OlMap) name =
|
||||
|> fun c -> c.getArray ()
|
||||
|> Seq.tryFind (fun i ->
|
||||
let name' = i.get "name"
|
||||
name' = name)
|
||||
name' = name
|
||||
)
|
||||
|
||||
let simpleWebGL layerName (data: MapData) =
|
||||
webGLLayer [
|
||||
webglLayer.className layerName
|
||||
webglLayer.indices data.Grid.Indices
|
||||
webglLayer.props data.Props
|
||||
// NOTE(SimenLK): Breaks if you change the source from xyz
|
||||
// NOTE(simkir): Breaks if you change the source from xyz
|
||||
webglLayer.source (Source.xyz [])
|
||||
webglLayer.vertices data.Grid.Vertices
|
||||
webglLayer.opacity 0.15
|
||||
webglLayer.attenuate 0.0
|
||||
]
|
||||
|
||||
let osmLayer =
|
||||
Layer.tileLayer [
|
||||
layer.source (Source.osm [])
|
||||
]
|
||||
let osmLayer = Layer.tileLayer [ layer.source (Source.osm []) ]
|
||||
|
||||
let baseMapLayer =
|
||||
Layer.tileLayer [
|
||||
layer.source ((MapKind.MapTiler MapTiler.Basic).source())
|
||||
] :> Layer.Layer
|
||||
Layer.tileLayer [ layer.source ((MapKind.MapTiler MapTiler.Basic).source ()) ] :> Layer.Layer
|
||||
|
||||
let selectedMapLayer (kind: MapKind) =
|
||||
match kind with
|
||||
| OSM
|
||||
| MapTiler _ ->
|
||||
Layer.tileLayer [
|
||||
layer.source (kind.source())
|
||||
layer.source (kind.source ())
|
||||
layer.minZoom 0.0
|
||||
// layer.maxZoom 15.0
|
||||
] :> Layer.Layer
|
||||
// layer.maxZoom 15.0
|
||||
]
|
||||
:> Layer.Layer
|
||||
| NorgesKart Sentinel2
|
||||
| NorgesKart SeaRaster ->
|
||||
Layer.imageLayer [
|
||||
layer.source (kind.source ())
|
||||
layer.minZoom (kind.minZoom())
|
||||
] :> Layer.Layer
|
||||
| NorgesKart _ ->
|
||||
Layer.tileLayer [
|
||||
layer.source (kind.source ())
|
||||
layer.minZoom (kind.minZoom())
|
||||
] :> Layer.Layer
|
||||
Layer.imageLayer [ layer.source (kind.source ()); layer.minZoom (kind.minZoom ()) ] :> Layer.Layer
|
||||
| NorgesKart _ -> Layer.tileLayer [ layer.source (kind.source ()); layer.minZoom (kind.minZoom ()) ] :> Layer.Layer
|
||||
|
||||
let createStreamsLayer (data: MapData) uvFlat bbox =
|
||||
let streamIndices = data.Grid.Indices
|
||||
@@ -161,13 +151,20 @@ let createStreamsLayer (data: MapData) uvFlat bbox =
|
||||
]
|
||||
|
||||
// Example filter from openlayers example: https://openlayers.org/en/latest/examples/vector-wfs-getfeature.html
|
||||
let private wfsLoader (vectorSource: VectorSource) = // must return a lambda!
|
||||
fun (extent: Extent) (_: Resolution) (_: Projection) (success: Feature[] -> unit) (failure: unit -> unit) ->
|
||||
let private wfsLoader (vectorSource: VectorSource) = // NOTE: must return a lambda!
|
||||
fun
|
||||
(extent: Extent)
|
||||
(_resolution: Resolution)
|
||||
(projection: Projection)
|
||||
(success: Feature array -> unit)
|
||||
(failure: unit -> unit) ->
|
||||
let proj = projection?getCode()
|
||||
let url = Lokaliteter.wfsUrl ()
|
||||
let lower = extent[0], extent[1]
|
||||
let upper = extent[2], extent[3]
|
||||
// NOTE: THE XML IS CASE SENSITIVE!
|
||||
let filter = $"""
|
||||
let filter =
|
||||
$"""
|
||||
<fes:Filter>
|
||||
<fes:And>
|
||||
<fes:And>
|
||||
@@ -190,102 +187,118 @@ let private wfsLoader (vectorSource: VectorSource) = // must return a lambda!
|
||||
</fes:And>
|
||||
</fes:Filter>"""
|
||||
|
||||
let query = $"""
|
||||
<wfs:GetFeature service="WFS" version="2.0.0" outputFormat="GeoJSON"
|
||||
let query =
|
||||
$"""<wfs:GetFeature service="WFS" version="2.0.0" outputFormat="GeoJSON"
|
||||
xmlns:FiskeridirWFS="http://gis.fiskeridir.no/wfs/2.0"
|
||||
xmlns:wfs="http://www.opengis.net/wfs/2.0"
|
||||
xmlns:fes="http://www.opengis.net/fes/2.0"
|
||||
xmlns:gml="http://www.opengis.net/gml/3.2">
|
||||
xmlns:gml="http://www.opengis.net/gml/3.2"
|
||||
>
|
||||
<wfs:Query typeName="FiskeridirWFS:Akvakultur_-_Lokaliteter" srsName="EPSG:3857">
|
||||
{filter}
|
||||
{filter}
|
||||
</wfs:Query>
|
||||
</wfs:GetFeature>"""
|
||||
let xhr = XMLHttpRequest.Create()
|
||||
xhr.``open`` ("POST", url)
|
||||
xhr.addEventListener("error", fun _ ->
|
||||
vectorSource.removeLoadedExtent(extent)
|
||||
failure ())
|
||||
xhr.addEventListener("load", fun _ ->
|
||||
if xhr.status = 200 then
|
||||
let fmt = vectorSource.getFormat () :?> GeoJSON
|
||||
let features = fmt.readFeatures(xhr.responseText)
|
||||
do
|
||||
features
|
||||
|> Array.iter (fun feature ->
|
||||
let geom = feature.getGeometry()
|
||||
let coords = geom?getCoordinates()
|
||||
let transformed = Utils.coordToEpsg3857 coords
|
||||
|
||||
geom?setCoordinates transformed
|
||||
)
|
||||
vectorSource.addFeatures features
|
||||
success(features)
|
||||
else
|
||||
let xhr = XMLHttpRequest.Create ()
|
||||
xhr.``open`` ("POST", url)
|
||||
xhr.addEventListener (
|
||||
"error",
|
||||
fun _ ->
|
||||
vectorSource.removeLoadedExtent extent
|
||||
failure ()
|
||||
)
|
||||
)
|
||||
xhr.addEventListener (
|
||||
"load",
|
||||
fun _ ->
|
||||
if xhr.status = 200 then
|
||||
let fmt = vectorSource.getFormat () :?> GeoJSON
|
||||
let features = fmt.readFeatures xhr.responseText
|
||||
|
||||
do
|
||||
features
|
||||
|> Array.iter (fun feature ->
|
||||
let geom = feature.getGeometry()
|
||||
let coords = geom?getCoordinates()
|
||||
let transformed = Utils.coordToEpsg3857 coords
|
||||
|
||||
geom?setCoordinates transformed
|
||||
)
|
||||
|
||||
vectorSource.addFeatures features
|
||||
success features
|
||||
else
|
||||
failure ()
|
||||
)
|
||||
xhr.send query
|
||||
|
||||
let private createAquacultureLocalityLayer () =
|
||||
// let f : FeatureUrlFunction = emitJsExpr urlFunc "$0"
|
||||
let vectorSource =
|
||||
Source.vectorSource [
|
||||
source.projection epsg3857
|
||||
source.format (Format.geoJSON [])
|
||||
source.strategy LoadingStrategy.bbox
|
||||
]
|
||||
let proj = vectorSource.getProjection()
|
||||
console.debug("[Maps] Vector source projection: %o", proj)
|
||||
// NOTE(simkir): Here is were we set the custom WFS loader
|
||||
do vectorSource.setLoader (wfsLoader vectorSource)
|
||||
let defaultStyle =
|
||||
Style.style [
|
||||
style.image (
|
||||
Style.circle [
|
||||
circle.radius 5.0
|
||||
circle.fill (
|
||||
Style.fill [
|
||||
fill.color "rgba(150, 0, 0, 1.0)"
|
||||
]
|
||||
)
|
||||
circle.stroke (
|
||||
Style.stroke [
|
||||
stroke.color "white"
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
||||
]
|
||||
let polygonStyle =
|
||||
Style.style [
|
||||
style.stroke (
|
||||
Style.stroke [
|
||||
stroke.color "white"
|
||||
stroke.width 1
|
||||
]
|
||||
)
|
||||
style.fill (
|
||||
Style.fill [
|
||||
fill.color "rgba(0.5, 0.5, 0.5, 0.1)"
|
||||
]
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
Layer.vectorLayer [
|
||||
layer.className (string MapLayer.Aquaculture)
|
||||
layer.zIndex 12
|
||||
layer.minZoom 7.5
|
||||
layer.source vectorSource
|
||||
layer.style (fun feature ->
|
||||
if feature.get "type" = "polygon" then
|
||||
polygonStyle
|
||||
else
|
||||
if feature.get "selected" = "true" then
|
||||
Style.style []
|
||||
else
|
||||
defaultStyle
|
||||
)
|
||||
]
|
||||
|
||||
// Ref: https://gis.fiskeridir.no/server/services/FiskeridirWFS_akva/MapServer/WFSServer?SERVICE=WFS&REQUEST=GetCapabilities
|
||||
// arcgis wfs info: https://enterprise.arcgis.com/en/server/latest/publish-services/linux/communicating-with-a-wfs-service-in-a-web-browser.htm
|
||||
let fiskeri (topic: InfoLayer) : Ol.Layer.Layer =
|
||||
if topic = Lokaliteter then
|
||||
console.debug("fiskeri")
|
||||
// let f : FeatureUrlFunction = emitJsExpr urlFunc "$0"
|
||||
let vectorSource =
|
||||
Source.vectorSource [
|
||||
source.projection epsg3857
|
||||
source.format (Format.geoJSON [])
|
||||
source.strategy LoadingStrategy.bbox
|
||||
]
|
||||
vectorSource.setLoader (wfsLoader vectorSource)
|
||||
let defaultStyle =
|
||||
Style.style [
|
||||
style.image (
|
||||
Style.circle [
|
||||
circle.radius 5.0
|
||||
circle.fill (
|
||||
Style.fill [
|
||||
fill.color "rgba(150, 0, 0, 1.0)"
|
||||
]
|
||||
)
|
||||
circle.stroke (
|
||||
Style.stroke [
|
||||
stroke.color "white"
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
||||
]
|
||||
let polygonStyle =
|
||||
Style.style [
|
||||
style.stroke (
|
||||
Style.stroke [
|
||||
stroke.color "white"
|
||||
stroke.width 1
|
||||
]
|
||||
)
|
||||
style.fill (
|
||||
Style.fill [
|
||||
fill.color "rgba(0.5, 0.5, 0.5, 0.1)"
|
||||
]
|
||||
)
|
||||
]
|
||||
|
||||
// TODO: Try webglPoints again
|
||||
Layer.vectorLayer [
|
||||
layer.className (string MapLayer.Aquaculture)
|
||||
layer.minZoom 7.5
|
||||
layer.source vectorSource
|
||||
layer.style (fun feature ->
|
||||
if feature.get "type" = "polygon" then
|
||||
polygonStyle
|
||||
else
|
||||
if feature.get "selected" = "true" then
|
||||
Style.style []
|
||||
else
|
||||
defaultStyle)
|
||||
layer.zIndex 12
|
||||
]
|
||||
console.debug ("[Map] Creating fiskeri OpenLayers Layer on topic %o", topic)
|
||||
createAquacultureLocalityLayer ()
|
||||
else
|
||||
let alpha =
|
||||
match topic with
|
||||
@@ -299,30 +312,22 @@ let fiskeri (topic: InfoLayer) : Ol.Layer.Layer =
|
||||
source.serverType Source.ServerType.Geoserver
|
||||
source.params' !!{| layers = string topic |}
|
||||
]
|
||||
Layer.imageLayer [
|
||||
layer.source source
|
||||
layer.opacity alpha
|
||||
]
|
||||
|
||||
Layer.imageLayer [ layer.source source; layer.opacity alpha ]
|
||||
|
||||
let toGeometry ((x, y): single * single) =
|
||||
Geometry.point [
|
||||
geometry.coordinates [|
|
||||
float x
|
||||
float y
|
||||
|]
|
||||
geometry.coordinates [| float x; float y |]
|
||||
geometry.layout GeometryLayout.XY
|
||||
]
|
||||
|
||||
let toFeature (p: single * single) =
|
||||
let g = toGeometry p
|
||||
let f =
|
||||
Feature.feature [
|
||||
feature.geometryOrProperties g
|
||||
]
|
||||
let f = Feature.feature [ feature.geometryOrProperties g ]
|
||||
f.setGeometry g
|
||||
f
|
||||
|
||||
let toFeatures (p: (single * single) []) = Array.map toFeature p
|
||||
let toFeatures (p: (single * single)[]) = Array.map toFeature p
|
||||
|
||||
// let conc (r, b) (p: Particles) =
|
||||
// let source =
|
||||
@@ -371,49 +376,38 @@ let createWebGLWireframeLayer (grid: WireframeGrid) =
|
||||
WebGLWireframeLayer.webGLWireframeLayer [
|
||||
layer.source (Source.osm [])
|
||||
layer.className "wireframe"
|
||||
(Interop.mkLayerProp "vertices" grid.Vertices)
|
||||
(Interop.mkLayerProp "barycentric" grid.BarycentricCoords)
|
||||
Interop.mkLayerProp "vertices" grid.Vertices
|
||||
Interop.mkLayerProp "barycentric" grid.BarycentricCoords
|
||||
]
|
||||
|
||||
let createMapWithLayers center (layers: Layer.Layer []) =
|
||||
let createMapWithLayers center (layers: Layer.Layer[]) =
|
||||
let lonLat = fromLonLat center
|
||||
let view =
|
||||
View.view [
|
||||
view.projection epsg3857
|
||||
view.center lonLat
|
||||
view.zoom 5.5
|
||||
]
|
||||
let view = View.view [ view.projection epsg3857; view.center lonLat; view.zoom 5.5 ]
|
||||
|
||||
OlMap.map [
|
||||
map.layers layers
|
||||
map.view view
|
||||
]
|
||||
OlMap.map [ map.layers layers; map.view view ]
|
||||
|
||||
let crossHairSelect (map: OlMap) (clickKey: Event.EventsKey option ref) onClick active =
|
||||
let elem = map.getTargetElement()
|
||||
let elem = map.getTargetElement ()
|
||||
let key =
|
||||
if active then
|
||||
elem?style?cursor <- "crosshair"
|
||||
map.on (
|
||||
"click",
|
||||
(fun (e: Event.MapBrowserEvent) ->
|
||||
onClick e)
|
||||
)
|
||||
|> Some
|
||||
map.on ("click", (fun (e: Event.MapBrowserEvent) -> onClick e)) |> Some
|
||||
else
|
||||
None
|
||||
|
||||
clickKey.contents <- key
|
||||
|
||||
Hook.createDisposable (fun () ->
|
||||
console.debug("Maps.crossHairSelect dispose")
|
||||
if not (isNullOrUndefined elem) then elem?style?cursor <- ""
|
||||
Observable.unByKey key)
|
||||
console.debug ("Maps.crossHairSelect dispose")
|
||||
if not (isNullOrUndefined elem) then
|
||||
elem?style?cursor <- ""
|
||||
Observable.unByKey key
|
||||
)
|
||||
|
||||
let private hmr = HMR.createToken ()
|
||||
|
||||
// let private olCss': {| ``default``: string |} =
|
||||
// importSideEffects "../public/ol.css"
|
||||
// importSideEffects "../public/ol.css"
|
||||
|
||||
// let private olCss = olCss'.``default``
|
||||
// console.log olCss
|
||||
@@ -422,15 +416,15 @@ let private hmr = HMR.createToken ()
|
||||
let unsafeCSS _ = jsNative
|
||||
|
||||
[<LitElement("ol-map")>]
|
||||
let OlMapElement() =
|
||||
let OlMapElement () =
|
||||
let this, props =
|
||||
LitElement.init (fun cfg ->
|
||||
cfg.useShadowDom <- true
|
||||
cfg.props <- {| map = Prop.Of(OlMap.map [], attribute = "map") |}
|
||||
cfg.styles <-
|
||||
[
|
||||
// unsafeCSS olCss
|
||||
unsafeCSS """
|
||||
cfg.props <- {| map = Prop.Of (OlMap.map [], attribute = "map") |}
|
||||
cfg.styles <- [
|
||||
// unsafeCSS olCss
|
||||
unsafeCSS
|
||||
"""
|
||||
:host {
|
||||
height: 100%;
|
||||
}
|
||||
@@ -456,12 +450,14 @@ let OlMapElement() =
|
||||
padding: 10px;
|
||||
}
|
||||
"""
|
||||
])
|
||||
]
|
||||
)
|
||||
|
||||
Hook.useEffect (fun () ->
|
||||
if isNull this.shadowRoot |> not then
|
||||
let target = getShadowElementById this "map"
|
||||
props.map.Value.setTarget target)
|
||||
props.map.Value.setTarget target
|
||||
)
|
||||
|
||||
let classes = Lit.classes [ "map", true ]
|
||||
|
||||
|
||||
7
src/Atlantis/src/Client/Lib/React.fs
Normal file
7
src/Atlantis/src/Client/Lib/React.fs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Lib
|
||||
|
||||
module React =
|
||||
open Fable.Core
|
||||
open Feliz
|
||||
|
||||
let inline fromJsx (el: JSX.Element) : ReactElement = unbox el
|
||||
@@ -1,12 +1,31 @@
|
||||
module Remoting
|
||||
|
||||
open Browser
|
||||
open Fable.Remoting.Client
|
||||
open FsToolkit.ErrorHandling
|
||||
|
||||
open Atlantis
|
||||
open Sorcerer
|
||||
|
||||
let getArchiveUrl () = Browser.WebStorage.sessionStorage["archmaester_url"]
|
||||
let getDataUrl () = Browser.WebStorage.sessionStorage["sorcerer_url"]
|
||||
let getArchiveUrl () = sessionStorage["archmaester_url"]
|
||||
let getDataUrl () = sessionStorage["sorcerer_url"]
|
||||
let tryGetArchiveUrl () = sessionStorage["archmaester_url"] |> Utils.tryStr
|
||||
let tryGetDataUrl () = sessionStorage["sorcerer_url"] |> Utils.tryStr
|
||||
|
||||
/// NOTE: This function will redirect the user on a ProxyRequestException
|
||||
let tryCatch (comp: Async<'T>) : Async<Result<'T, exn>> =
|
||||
comp
|
||||
|> Async.Catch
|
||||
|> Async.map (fun choice ->
|
||||
choice
|
||||
|> Result.ofChoice
|
||||
|> Result.teeError (function
|
||||
| :? ProxyRequestException as ex ->
|
||||
console.error("[Remoting] Proxy request error: %o", ex.Response)
|
||||
window.location.href <- "/signin"
|
||||
| _ -> ()
|
||||
)
|
||||
)
|
||||
|
||||
let authApi =
|
||||
Remoting.createApi ()
|
||||
@@ -90,6 +109,7 @@ type DriftersApi(url) =
|
||||
member val Particles = createBinApi Remoting.buildProxy<Api.Drifters.Particles>
|
||||
member val FieldMetaData = createBinApi Remoting.buildProxy<Api.Drifters.FieldMetaData>
|
||||
member val Sedimentation = createBinApi Remoting.buildProxy<Api.Drifters.Sedimentation>
|
||||
member val WaterContact = createBinApi Remoting.buildProxy<Api.Drifters.WaterContact>
|
||||
member val Field2D = createBinApi Remoting.buildProxy<Api.Drifters.Field2D>
|
||||
member val Field3D = createBinApi Remoting.buildProxy<Api.Drifters.Field3D>
|
||||
member val Network = createApi Remoting.buildProxy<Api.Drifters.Network>
|
||||
@@ -112,12 +132,17 @@ type StatsApi(url) =
|
||||
|> Remoting.withRouteBuilder Api.Stats.routeBuilder
|
||||
|> f
|
||||
|
||||
member val FvStatsInfo =
|
||||
createApi Remoting.buildProxy<Api.Stats.FvStatsInfo>
|
||||
member val FvStatsInfo = createApi Remoting.buildProxy<Api.Stats.FvStatsInfo>
|
||||
member val FvStatsByLayer =
|
||||
createApi (Remoting.withBinarySerialization >> Remoting.buildProxy<Api.Stats.FvStatsByLayer>)
|
||||
createApi (
|
||||
Remoting.withBinarySerialization
|
||||
>> Remoting.buildProxy<Api.Stats.FvStatsByLayer>
|
||||
)
|
||||
member val FvStatsByIndex =
|
||||
createApi (Remoting.withBinarySerialization >> Remoting.buildProxy<Api.Stats.FvStatsByIndex>)
|
||||
createApi (
|
||||
Remoting.withBinarySerialization
|
||||
>> Remoting.buildProxy<Api.Stats.FvStatsByIndex>
|
||||
)
|
||||
member val FvStatsSeries =
|
||||
createApi (Remoting.withBinarySerialization >> Remoting.buildProxy<Api.Stats.FvStatsSeries>)
|
||||
|
||||
@@ -168,4 +193,10 @@ let plumeApi () =
|
||||
Remoting.createApi ()
|
||||
|> Remoting.withCredentials true
|
||||
|> Remoting.withRouteBuilder Api.authorizedRouteBuilder
|
||||
|> Remoting.buildProxy<Api.Plume>
|
||||
|> Remoting.buildProxy<Api.Plume>
|
||||
|
||||
let xtractApi () =
|
||||
Remoting.createApi ()
|
||||
|> Remoting.withCredentials true
|
||||
|> Remoting.withRouteBuilder Api.authorizedRouteBuilder
|
||||
|> Remoting.buildProxy<Api.Xtract>
|
||||
44
src/Atlantis/src/Client/Lib/Search.fs
Normal file
44
src/Atlantis/src/Client/Lib/Search.fs
Normal file
@@ -0,0 +1,44 @@
|
||||
module Search
|
||||
|
||||
open FsToolkit.ErrorHandling
|
||||
|
||||
open Sorcerer.Types
|
||||
|
||||
/// Search an archive for the closest node and elem idx based on the given coordinate
|
||||
let tryGetNearestNodeAndElement aid (x, y) : Async<Option<NodeIdx * ElemIdx>> =
|
||||
async {
|
||||
let dataSvc = Remoting.getDataUrl ()
|
||||
let api = Remoting.proximityApi dataSvc
|
||||
let! nodes = api.GetNearestNodes (aid, [| x, y |])
|
||||
let! elems = api.GetNearestElements (aid, [| x, y |])
|
||||
let firstNode = nodes |> Array.tryHead |> Option.flatten
|
||||
let firstElem = elems |> Array.tryHead |> Option.flatten
|
||||
let result =
|
||||
firstNode
|
||||
|> Option.bind (fun node -> firstElem |> Option.map (fun elem -> node, elem))
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/// Search an archive for the closest node and elem idx based on the given coordinate
|
||||
let tryGetNearestNodeAndElementRes dataSvc aid (x, y) : Async<Result<NodeIdx * ElemIdx, exn>> =
|
||||
asyncResult {
|
||||
let api = Remoting.proximityApi dataSvc
|
||||
let! nodes = api.GetNearestNodes (aid, [| x, y |]) |> Remoting.tryCatch
|
||||
let! elems = api.GetNearestElements (aid, [| x, y |]) |> Remoting.tryCatch
|
||||
let firstNode = nodes |> Array.tryHead |> Option.flatten
|
||||
let firstElem = elems |> Array.tryHead |> Option.flatten
|
||||
let result =
|
||||
firstNode
|
||||
|> Option.bind (fun node -> firstElem |> Option.map (fun elem -> node, elem))
|
||||
|> Result.requireSome (exn "Could not find node")
|
||||
|
||||
return! result
|
||||
}
|
||||
|
||||
let probePoint (aid: System.Guid) (point: float * float) : Async<Option<(float * float) * Atlantis.Types.GridIdx>> =
|
||||
async {
|
||||
let! idxOpt = tryGetNearestNodeAndElement aid point
|
||||
let res = idxOpt |> Option.map (fun idx -> point, idx)
|
||||
return res
|
||||
}
|
||||
@@ -9,10 +9,10 @@ type StreamLayerProp = interface end
|
||||
|
||||
|
||||
type StreamLayer =
|
||||
inherit Layer
|
||||
inherit Layer
|
||||
|
||||
abstract updateProps: single array -> unit
|
||||
abstract updateUvs: single array -> unit
|
||||
abstract updateProps: single array -> unit
|
||||
abstract updateUvs: single array -> unit
|
||||
|
||||
let inline mkStreamLineProp (key: string) (value: obj) : StreamLayerProp = unbox (key, value)
|
||||
|
||||
@@ -24,7 +24,7 @@ type stream =
|
||||
static member inline uvs(value: single array) = mkStreamLineProp "uvs" value
|
||||
static member inline source(value: Source) = mkStreamLineProp "source" value
|
||||
static member inline vertices(value: single array) = mkStreamLineProp "vertices" value
|
||||
static member inline geoCenter(value: single * single ) = mkStreamLineProp "geoCenter" value
|
||||
static member inline geoCenter(value: single * single) = mkStreamLineProp "geoCenter" value
|
||||
static member inline bbox(value: Sorcerer.Types.BBox) = mkStreamLineProp "bbox" value
|
||||
|
||||
[<ImportDefault("../public/js/StreamLayer")>]
|
||||
@@ -32,7 +32,6 @@ type stream =
|
||||
let private createStreamLayer _ : StreamLayer = jsNative
|
||||
|
||||
let streamLayer (opts: StreamLayerProp seq) : StreamLayer =
|
||||
let options = keyValueList CaseRules.LowerFirst opts
|
||||
|
||||
createStreamLayer options
|
||||
let options = keyValueList CaseRules.LowerFirst opts
|
||||
|
||||
createStreamLayer options
|
||||
@@ -4,10 +4,7 @@ open Fable.Core
|
||||
open Fable.Core.JsInterop
|
||||
|
||||
[<Erase>]
|
||||
type Geometry = {
|
||||
``type``: string
|
||||
coordinates: (float * float) array
|
||||
}
|
||||
type Geometry = { ``type``: string; coordinates: (float * float) array }
|
||||
|
||||
[<Erase>]
|
||||
type Feature = {
|
||||
@@ -26,22 +23,15 @@ type Feature = {
|
||||
/// <param name="sharpness">How curvy the path should be between splines. Defaults to 85.0</param>
|
||||
[<AllowNullLiteral>]
|
||||
[<Global>]
|
||||
type BezierOptions
|
||||
[<ParamObject; Emit("$0")>]
|
||||
(
|
||||
?properties,
|
||||
?resolution: float,
|
||||
?sharpness: float
|
||||
)
|
||||
=
|
||||
type BezierOptions [<ParamObject; Emit("$0")>] (?properties, ?resolution: float, ?sharpness: float) =
|
||||
member val properties: obj option = jsNative with get, set
|
||||
member val resolution: float option = jsNative with get, set
|
||||
member val sharpness: float option = jsNative with get, set
|
||||
|
||||
type Helpers () =
|
||||
type Helpers() =
|
||||
[<Import("lineString", "@turf/helpers")>]
|
||||
static member lineString(coordinates, ?properties, ?options) : Feature = jsNative
|
||||
|
||||
type Bezier () =
|
||||
type Bezier() =
|
||||
[<ImportDefault("@turf/bezier-spline")>]
|
||||
static member bezier(feature: Feature, ?options: BezierOptions) : Feature = jsNative
|
||||
@@ -1,15 +1,18 @@
|
||||
// TODO: Atlantis.Types? Atlantis client types?
|
||||
module Atlantis.Types
|
||||
|
||||
open Archmaester.Dto
|
||||
open System
|
||||
|
||||
open Fable.Core.JsInterop
|
||||
open Fable.OpenLayers
|
||||
open Proj4
|
||||
open System
|
||||
|
||||
open Archmaester.Dto
|
||||
open Drifters.ApiTypes
|
||||
open Sorcerer.Types
|
||||
open Hipster.Job
|
||||
open Sorcerer.Types
|
||||
|
||||
// TODO(simkir): These are not Types :^)
|
||||
proj4.defs ("EPSG:25832", "+proj=utm +zone=32 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs")
|
||||
proj4.defs ("EPSG:25833", "+proj=utm +zone=33 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs")
|
||||
Proj.proj4.register proj4.proj4
|
||||
@@ -22,6 +25,36 @@ let epsg25832 = Proj.get "EPSG:25832"
|
||||
let mapAlpha0 = 0.35
|
||||
let mapAlpha1 = 0.95
|
||||
|
||||
// TODO: DU or maybe couple with properties, as they decide which to use
|
||||
/// Either a node or an elem on an FVCOM grid. Mostly used when probing for data.
|
||||
type GridIdx = NodeIdx * ElemIdx
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
type TimeUnit =
|
||||
| Hour
|
||||
| Day
|
||||
| Week
|
||||
| Month
|
||||
| Quarter
|
||||
| Year
|
||||
override x.ToString() =
|
||||
match x with
|
||||
| Hour -> "hour"
|
||||
| Day -> "day"
|
||||
| Week -> "week"
|
||||
| Month -> "month"
|
||||
| Quarter -> "quarter"
|
||||
| Year -> "year"
|
||||
static member FromString(s: string) =
|
||||
match s with
|
||||
| "hour" -> Hour
|
||||
| "day" -> Day
|
||||
| "week" -> Week
|
||||
| "month" -> Month
|
||||
| "quarter" -> Quarter
|
||||
| "year" -> Year
|
||||
| _ -> Hour
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
type Tab =
|
||||
| Select
|
||||
@@ -68,6 +101,12 @@ type NorgesKart =
|
||||
| BackgroundNorway -> "topo"
|
||||
| GEBCO -> "gebco"
|
||||
| Sentinel2 -> "2023"
|
||||
member this.ToLabel() =
|
||||
match this with
|
||||
| SeaRaster -> "Nautical"
|
||||
| BackgroundNorway -> "Kartverket"
|
||||
| GEBCO -> "Gebco"
|
||||
| Sentinel2 -> "Sentinel"
|
||||
static member epsgId =
|
||||
function
|
||||
| GEBCO -> "25833"
|
||||
@@ -92,15 +131,16 @@ type NorgesKart =
|
||||
| SeaRaster -> epsg3857 // epsg25833 //wgs84
|
||||
| _ -> epsg3857
|
||||
match x with
|
||||
| Sentinel2 | SeaRaster ->
|
||||
Source.imageWMS
|
||||
[
|
||||
source.url (x.url ())
|
||||
source.projection proj
|
||||
source.ratio 1.0
|
||||
source.serverType Source.ServerType.Geoserver
|
||||
source.params' !!{| layers = string x |}
|
||||
] :> Source
|
||||
| Sentinel2
|
||||
| SeaRaster ->
|
||||
Source.imageWMS [
|
||||
source.url (x.url ())
|
||||
source.projection proj
|
||||
source.ratio 1.0
|
||||
source.serverType Source.ServerType.Geoserver
|
||||
source.params' !!{| layers = string x |}
|
||||
]
|
||||
:> Source
|
||||
| _ -> Source.xyz [ source.projection proj; source.url (x.url ()) ] :> Source
|
||||
|
||||
type MapTiler =
|
||||
@@ -122,12 +162,22 @@ type MapTiler =
|
||||
| Toner -> "toner-v2"
|
||||
| OB_Basic -> "3f4c3c94-eac3-459b-8519-13f500453711"
|
||||
| OB_Terrain -> "b44735b0-893f-43a2-8fc9-45a1f8f3ab49"
|
||||
member this.ToLabel() =
|
||||
match this with
|
||||
| Basic -> "Basic"
|
||||
| Streets -> "Streets"
|
||||
| Topo -> "Topo"
|
||||
| Satellite -> "Satellite"
|
||||
| Ocean -> "Ocean"
|
||||
| Toner -> "Toner"
|
||||
| OB_Basic -> "Oceanbox Basic"
|
||||
| OB_Terrain -> "Terrain"
|
||||
member this.url() =
|
||||
let key = "JIDuhwaFCRDHWJjXYtJb"
|
||||
$"https://api.maptiler.com/maps/{string this}/tiles.json?key={key}"
|
||||
member this.source() =
|
||||
Source.tileJSON [
|
||||
source.url (this.url())
|
||||
source.url (this.url ())
|
||||
source.tileSize 512
|
||||
source.crossOrigin "anonymous"
|
||||
]
|
||||
@@ -141,6 +191,11 @@ type MapKind =
|
||||
| OSM -> "OpenStreetmap"
|
||||
| MapTiler x -> string x
|
||||
| NorgesKart x -> string x
|
||||
member this.ToLabel() =
|
||||
match this with
|
||||
| OSM -> "OpenStreetmap"
|
||||
| MapTiler x -> x.ToLabel()
|
||||
| NorgesKart x -> x.ToLabel()
|
||||
member this.source() =
|
||||
match this with
|
||||
| OSM -> Source.osm [] :> Source
|
||||
@@ -161,42 +216,56 @@ type MapKind =
|
||||
| SeaRaster -> 10.0
|
||||
| _ -> 5.0
|
||||
|
||||
type MapData =
|
||||
{
|
||||
Grid: PlainGrid
|
||||
Props: float32 array
|
||||
Zoom: int
|
||||
Center: float[]
|
||||
}
|
||||
static member empty =
|
||||
{
|
||||
Grid = PlainGrid.empty
|
||||
Props = Array.empty
|
||||
Zoom = 9
|
||||
Center = [| 14.39; 67.9 |]
|
||||
}
|
||||
static member OfString (str: string): MapKind option =
|
||||
match str with
|
||||
| "OpenStreetmap" -> Some OSM
|
||||
| "basic" -> Some (MapTiler Basic)
|
||||
| "streets-v2" -> Some (MapTiler Streets)
|
||||
| "topo-v2" -> Some (MapTiler Topo)
|
||||
| "satellite" -> Some (MapTiler Satellite)
|
||||
| "ocean" -> Some (MapTiler Ocean)
|
||||
| "toner-v2" -> Some (MapTiler Toner)
|
||||
| "3f4c3c94-eac3-459b-8519-13f500453711" -> Some (MapTiler OB_Basic)
|
||||
| "b44735b0-893f-43a2-8fc9-45a1f8f3ab49" -> Some (MapTiler OB_Terrain)
|
||||
| "hoved" -> Some (NorgesKart SeaRaster)
|
||||
| "topo" -> Some (NorgesKart BackgroundNorway)
|
||||
| "gebco" -> Some (NorgesKart GEBCO)
|
||||
| "2023" -> Some (NorgesKart Sentinel2)
|
||||
| _ -> None
|
||||
|
||||
type Particles = ApiParticle []
|
||||
type MapData = {
|
||||
Grid: PlainGrid
|
||||
Props: float32 array
|
||||
Zoom: int
|
||||
Center: float[]
|
||||
} with
|
||||
static member empty = {
|
||||
Grid = PlainGrid.empty
|
||||
Props = Array.empty
|
||||
Zoom = 9
|
||||
Center = [| 14.39; 67.9 |]
|
||||
}
|
||||
|
||||
type Particles = ApiParticle[]
|
||||
|
||||
type ParticleFilter = {
|
||||
fetched: bool
|
||||
availableFieldTypes: FieldKind list
|
||||
availableSedimentTypes: SedimentKind list
|
||||
availableDepthLayers: LayerType list
|
||||
availableGroupTypes: GroupType list
|
||||
availableParticleTypes: ParticleType list
|
||||
availableFieldTypes: FieldKind array
|
||||
availableSedimentTypes: SedimentKind array
|
||||
availableDepthLayers: LayerType array
|
||||
availableGroupTypes: GroupType array
|
||||
availableParticleTypes: ParticleType array
|
||||
showDepthLayer: Map<LayerIdx, bool>
|
||||
showGroupKind: Map<GroupKind, bool>
|
||||
showParticleKind: Map<ParticleKind, bool>
|
||||
}
|
||||
with
|
||||
} with
|
||||
static member empty = {
|
||||
fetched = false
|
||||
availableFieldTypes = [ UndefinedField ]
|
||||
availableSedimentTypes = [ TPM ]
|
||||
availableDepthLayers = [ { idx = 0; min = Surface; max = Bottom } ]
|
||||
availableGroupTypes = [ { idx = 0; kind = AnyGroup; name = "AnyGroup" } ]
|
||||
availableParticleTypes = [ { idx = 0; kind = AnyParticle; name = "AnyParticle" } ]
|
||||
availableFieldTypes = [| UndefinedField |]
|
||||
availableSedimentTypes = [| TPM |]
|
||||
availableDepthLayers = [| { idx = 0; min = Surface; max = Bottom } |]
|
||||
availableGroupTypes = [| { idx = 0; kind = AnyGroup; name = "AnyGroup" } |]
|
||||
availableParticleTypes = [| { idx = 0; kind = AnyParticle; name = "AnyParticle" } |]
|
||||
showDepthLayer = Map [ 0, true ]
|
||||
showGroupKind = Map [ AnyGroup, true ]
|
||||
showParticleKind = Map [ AnyParticle, true ]
|
||||
@@ -208,6 +277,7 @@ type Prop =
|
||||
| Temp
|
||||
| Salt
|
||||
| Zeta
|
||||
| Dens
|
||||
| Speed
|
||||
| Conc2D
|
||||
| Conc3D
|
||||
@@ -215,7 +285,7 @@ type Prop =
|
||||
| Sed of SedimentKind
|
||||
| SW
|
||||
| WC
|
||||
| DW
|
||||
| DW of int
|
||||
| Map
|
||||
override x.ToString() =
|
||||
match x with
|
||||
@@ -223,6 +293,7 @@ type Prop =
|
||||
| Temp -> "temp"
|
||||
| Salt -> "salt"
|
||||
| Zeta -> "zeta"
|
||||
| Dens -> "dens"
|
||||
| Speed -> "speed"
|
||||
| Conc2D -> "concentration2D"
|
||||
| Conc3D -> "concentration3D"
|
||||
@@ -230,7 +301,7 @@ type Prop =
|
||||
| Sed s -> s.ToString()
|
||||
| SW -> "shannonwiener"
|
||||
| WC -> "watercontact"
|
||||
| DW -> "downwelling"
|
||||
| DW i -> $"downwelling-{i}"
|
||||
| Map -> "map"
|
||||
|
||||
member x.ToLabel() =
|
||||
@@ -239,6 +310,7 @@ type Prop =
|
||||
| Temp -> "Temperature"
|
||||
| Salt -> "Salinity"
|
||||
| Zeta -> "Tide"
|
||||
| Dens -> "Density"
|
||||
| Speed -> "Current"
|
||||
| Conc2D -> "Concentration2D"
|
||||
| Conc3D -> "Concentration3D"
|
||||
@@ -246,32 +318,35 @@ type Prop =
|
||||
| Sed s -> s.ToLabel()
|
||||
| SW -> "Shannon-Wiener Index"
|
||||
| WC -> "Water Contact"
|
||||
| DW -> "Downwelling"
|
||||
| DW i -> "Downwelling"
|
||||
| Map -> "Map"
|
||||
|
||||
// Initial values
|
||||
/// Initial values
|
||||
member x.viewRange =
|
||||
match x with
|
||||
| Temp -> 0.0, 15.0
|
||||
| Salt -> 27.5, 35.0
|
||||
| Zeta -> -1.5, 1.5
|
||||
| Speed -> 0.0, 1.0
|
||||
| Dens -> 20.0, 27.0
|
||||
| Speed -> 0.0, 2.0
|
||||
| Conc2D -> 0.0, 1.0
|
||||
| Conc3D -> 0.0, 1.0
|
||||
| SedV2 -> 0.0, 10.0
|
||||
| Sed _ -> 0.0, 10.0
|
||||
| SW -> -4.0, 0.0
|
||||
| WC -> -100.0, 0.0
|
||||
| DW -> 0.0, 1000.0
|
||||
| DW 2 -> 0.0, 100.0
|
||||
| DW _ -> 0.0, 1000.0
|
||||
| Bathy -> 0.0, 500.0
|
||||
| _ -> 0.0, 1.0
|
||||
|
||||
// Absolute min and max
|
||||
/// Absolute min and max
|
||||
member x.minMax =
|
||||
match x with
|
||||
| Temp -> -5.0, 45.0
|
||||
| Salt -> 0.0, 50.0
|
||||
| Zeta -> -5.0, 5.0
|
||||
| Dens -> 15.0, 28.0
|
||||
| Speed -> 0.0, 5.0
|
||||
| Conc2D -> 0.0, 100.0
|
||||
| Conc3D -> 0.0, 100.0
|
||||
@@ -279,16 +354,36 @@ type Prop =
|
||||
| Sed _ -> 0.0, 10_000.0
|
||||
| SW -> -6.0, 0.0
|
||||
| WC -> -100.0, 0.0
|
||||
| DW -> 0.0, 10_000.0
|
||||
| DW 2 -> 0.0, 1000.0
|
||||
| DW _ -> 0.0, 10_000.0
|
||||
| Bathy -> 0.0, 1500.0
|
||||
| _ -> 0.0, 1.0
|
||||
|
||||
/// The step amount when updating a range
|
||||
member this.step : float =
|
||||
match this with
|
||||
| Temp -> 1.0
|
||||
| Salt -> 1.0
|
||||
| Zeta -> 0.1
|
||||
| Dens -> 0.1
|
||||
| Speed -> 0.1
|
||||
| Conc2D -> 0.1
|
||||
| Conc3D -> 1.0
|
||||
| Sed _ -> 10.0
|
||||
| SW -> 0.1
|
||||
| WC -> 1.0
|
||||
| DW 2 -> 1.0
|
||||
| DW _ -> 10.0
|
||||
| Bathy -> 10.0
|
||||
| _ -> 1.0
|
||||
|
||||
member x.unit =
|
||||
match x with
|
||||
| Bathy -> "m"
|
||||
| Temp -> "°C"
|
||||
| Salt -> "psu"
|
||||
| Zeta -> "m"
|
||||
| Dens -> "(ρ - 1000) kg/m3"
|
||||
| Speed -> "m/s"
|
||||
| Conc2D -> "1/km2"
|
||||
| Conc3D -> "1/km2"
|
||||
@@ -296,15 +391,38 @@ type Prop =
|
||||
| Sed s -> s.ToUnit()
|
||||
| SW -> "H'"
|
||||
| WC -> ""
|
||||
| DW -> "ml/l"
|
||||
| DW 2 -> "%"
|
||||
| DW _ -> "ml/l"
|
||||
| Map -> ""
|
||||
|
||||
/// Chooses what type of index to use based on the property
|
||||
member this.Idx (node: NodeIdx, elem: ElemIdx) : int =
|
||||
match this with
|
||||
| Speed -> elem
|
||||
| Temp
|
||||
| Salt
|
||||
| Dens
|
||||
| Bathy
|
||||
| Temp
|
||||
| Salt
|
||||
| Zeta
|
||||
| Speed
|
||||
| Conc2D
|
||||
| Conc3D
|
||||
| Sed _
|
||||
| SedV2
|
||||
| SW
|
||||
| WC
|
||||
| DW _
|
||||
| Map -> node
|
||||
|
||||
static member fromString(str: String) =
|
||||
match str.ToLower() with
|
||||
match str.ToLower () with
|
||||
| "bathy" -> Prop.Bathy
|
||||
| "zeta" -> Prop.Zeta
|
||||
| "temp" -> Prop.Temp
|
||||
| "salt" -> Prop.Salt
|
||||
| "dens" -> Prop.Dens
|
||||
| "speed" -> Prop.Speed
|
||||
| "concentration2D" -> Prop.Conc2D
|
||||
| "concentration3D" -> Prop.Conc3D
|
||||
@@ -313,14 +431,16 @@ type Prop =
|
||||
| "poc" -> Sed POC
|
||||
| "pon" -> Sed PON
|
||||
| "pop" -> Sed POP
|
||||
| "shannonwiener" -> Prop.SW
|
||||
| "watercontact" -> Prop.WC
|
||||
| "downwelling" -> Prop.DW
|
||||
| _ -> Prop.Map
|
||||
| "shannonwiener" -> SW
|
||||
| "watercontact" -> WC
|
||||
| "downwelling-1" -> DW 1
|
||||
| "downwelling-2" -> DW 2
|
||||
| _ -> Map
|
||||
|
||||
static member findRange(props: 'a array) = Array.min props, Array.max props
|
||||
|
||||
type Spinner =
|
||||
[<RequireQualifiedAccess>]
|
||||
type MapLoading =
|
||||
| Spinning
|
||||
| Progress
|
||||
|
||||
@@ -331,6 +451,8 @@ type MapLayer =
|
||||
| Ocean
|
||||
| Conc
|
||||
| Heatmap
|
||||
| SeaDistance
|
||||
| GridCircle
|
||||
| AzeContour
|
||||
| TransitionZoneContour of int
|
||||
| IsoValueContour of int
|
||||
@@ -348,11 +470,12 @@ type MapLayer =
|
||||
| GeoFences
|
||||
| Aquaculture
|
||||
| Networks
|
||||
with
|
||||
override x.ToString () =
|
||||
override x.ToString() =
|
||||
match x with
|
||||
| Ocean -> "basegl"
|
||||
| Conc -> "concgl"
|
||||
| SeaDistance -> "sea_distance"
|
||||
| GridCircle -> "grid-circle"
|
||||
| AzeContour -> "aze-contour"
|
||||
| IsoValueContour id -> $"iso-contour-{id}"
|
||||
| TransitionZoneContour id -> $"distance-contour-{id}"
|
||||
@@ -371,10 +494,12 @@ with
|
||||
| GeoFences -> "geo-fences"
|
||||
| Aquaculture -> "aquaculture"
|
||||
| Networks -> "networks"
|
||||
member x.Label () =
|
||||
member x.Label() =
|
||||
match x with
|
||||
| Ocean -> "Ocean"
|
||||
| Conc -> "Concentration"
|
||||
| Ocean -> "Oceanography"
|
||||
| Conc -> "Concentrations"
|
||||
| SeaDistance -> "SeaDistance"
|
||||
| GridCircle -> $"GridCircle"
|
||||
| AzeContour -> "AzeContour"
|
||||
| TransitionZoneContour id -> $"DistanceContour-{id}"
|
||||
| IsoValueContour id -> $"ValueContour-{id}"
|
||||
@@ -393,18 +518,19 @@ with
|
||||
| GeoFences -> "Fences"
|
||||
| Aquaculture -> "Aquaculture"
|
||||
| Networks -> "Networks"
|
||||
member x.Attenuate =
|
||||
member x.Attenuate =
|
||||
match x with
|
||||
| Conc -> 0.85
|
||||
| Crop -> 1.0
|
||||
| _ -> 0.0
|
||||
|
||||
member x.Id = x.ToString()
|
||||
member x.Id = x.ToString ()
|
||||
|
||||
static member All = [|
|
||||
Ocean
|
||||
Conc
|
||||
Heatmap
|
||||
SeaDistance
|
||||
AzeContour
|
||||
Particles
|
||||
Crop
|
||||
@@ -447,14 +573,6 @@ type InfoLayer =
|
||||
| Lokaliteter -> "fiskeridirWMS_akva"
|
||||
$"https://gis.fiskeridir.no/server/services/{svc}/MapServer/WMSServer"
|
||||
member x.wfsUrl() =
|
||||
let svc =
|
||||
match x with
|
||||
| Gyting
|
||||
| Beite
|
||||
| Korall
|
||||
| Vern -> "fiskeridirWFS"
|
||||
| POs
|
||||
| Lokaliteter -> "FiskeridirWFS_akva"
|
||||
"https://gis.fiskeridir.no/server/services/FiskeridirWFS/MapServer/WFSServer"
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
@@ -468,106 +586,98 @@ type ColorMap =
|
||||
| Ocean of string
|
||||
| Color16 of string
|
||||
| Custom of string
|
||||
with
|
||||
member x.ToLabel () =
|
||||
match x with
|
||||
| Ocean n
|
||||
| Color16 n
|
||||
| Custom n -> n[ 0..0 ].ToUpper() + n[1..]
|
||||
member x.ToLabel() =
|
||||
match x with
|
||||
| Ocean n
|
||||
| Color16 n
|
||||
| Custom n -> n[0..0].ToUpper () + n[1..]
|
||||
|
||||
type PropColor =
|
||||
{
|
||||
Levels: int
|
||||
LogScale: bool
|
||||
Scientific: bool
|
||||
ViewRange: float * float
|
||||
PropRange: float * float
|
||||
ColorMap: ColorMode * ColorMap
|
||||
type PropColor = {
|
||||
Levels: int
|
||||
LogScale: bool
|
||||
Scientific: bool
|
||||
ViewRange: float * float
|
||||
PropRange: float * float
|
||||
ColorMap: ColorMode * ColorMap
|
||||
} with
|
||||
static member empty = {
|
||||
Levels = 10
|
||||
LogScale = false
|
||||
Scientific = false
|
||||
ViewRange = 0.0, 1.0
|
||||
PropRange = 0.0, 1.0
|
||||
ColorMap = ColorMode.Normal, ColorMap.Color16 "jet"
|
||||
}
|
||||
static member empty =
|
||||
{
|
||||
Levels = 10
|
||||
LogScale = false
|
||||
Scientific = false
|
||||
ViewRange = 0.0, 1.0
|
||||
PropRange = 0.0, 1.0
|
||||
ColorMap = ColorMode.Normal, ColorMap.Color16 "jet"
|
||||
}
|
||||
|
||||
type ViewProp =
|
||||
{
|
||||
PropType: Prop
|
||||
FieldKind: FieldKind
|
||||
PropData: single array
|
||||
Alpha: float
|
||||
type ViewProp = {
|
||||
PropType: Prop
|
||||
FieldKind: FieldKind
|
||||
PropData: single array
|
||||
ScalingFactor: single
|
||||
Alpha: float
|
||||
} with
|
||||
static member empty = {
|
||||
PropType = Prop.Map
|
||||
FieldKind = UndefinedField
|
||||
PropData = [||]
|
||||
ScalingFactor = 1.f
|
||||
Alpha = 1.0
|
||||
}
|
||||
static member empty =
|
||||
{
|
||||
PropType = Prop.Map
|
||||
FieldKind = UndefinedField
|
||||
PropData = [||]
|
||||
Alpha = 1.0
|
||||
}
|
||||
|
||||
type WireframeGrid =
|
||||
{
|
||||
Vertices: single array
|
||||
BarycentricCoords: int array
|
||||
}
|
||||
type WireframeGrid = {
|
||||
Vertices: single array
|
||||
BarycentricCoords: int array
|
||||
} with
|
||||
static member empty = { Vertices = Array.empty; BarycentricCoords = Array.empty }
|
||||
|
||||
type DBPlainGrid =
|
||||
{
|
||||
GridSha: string
|
||||
Grid: PlainGrid
|
||||
}
|
||||
type DBPlainGrid = {
|
||||
GridSha: string
|
||||
Grid: PlainGrid
|
||||
} with
|
||||
static member store = "PlainGrids"
|
||||
|
||||
// NOTE(simkir): ArchiveDetails does not have number of frames
|
||||
type ArchiveInfo =
|
||||
{
|
||||
id: Guid
|
||||
sha: string
|
||||
name: string
|
||||
/// Seconds
|
||||
saveFreq: int
|
||||
startTime: DateTime
|
||||
defaultZoom: float
|
||||
focalPoint: float * float
|
||||
polygon: (float * float) [] option
|
||||
frames: int
|
||||
type ArchiveInfo = {
|
||||
id: Guid
|
||||
sha: string
|
||||
name: string
|
||||
/// Seconds
|
||||
saveFreq: int
|
||||
startTime: DateTime
|
||||
defaultZoom: float
|
||||
focalPoint: float * float
|
||||
polygon: (float * float) array option
|
||||
frames: int
|
||||
} with
|
||||
static member empty = {
|
||||
id = Guid.Empty
|
||||
sha = ""
|
||||
name = ""
|
||||
saveFreq = 0
|
||||
startTime = DateTime.MinValue
|
||||
defaultZoom = 10.0
|
||||
focalPoint = 0.0, 0.0
|
||||
polygon = None
|
||||
frames = 0
|
||||
}
|
||||
static member empty =
|
||||
{
|
||||
id = Guid.Empty
|
||||
sha = ""
|
||||
name = ""
|
||||
saveFreq = 0
|
||||
startTime = DateTime.MinValue
|
||||
defaultZoom = 10.0
|
||||
focalPoint = 0.0, 0.0
|
||||
polygon = None
|
||||
frames = 0
|
||||
|
||||
type SimArchive = {
|
||||
Archive: ArchiveProps
|
||||
Duration: TimeSpan
|
||||
Reverse: bool
|
||||
Status: JobStatus
|
||||
JobId: int option
|
||||
SimType: DriftersVariant
|
||||
SimFormat: DriftersFormat
|
||||
} with
|
||||
static member Create(sim: SimulationModel) =
|
||||
let archive = {
|
||||
ArchiveProps.empty with
|
||||
reference = sim.aid |> Some
|
||||
freq = sim.saveFreq.ToFloat () |> int
|
||||
startTime = sim.startTime
|
||||
}
|
||||
|
||||
|
||||
type SimArchive =
|
||||
{
|
||||
Archive: ArchiveProps
|
||||
Duration: TimeSpan
|
||||
Reverse: bool
|
||||
Status: JobStatus
|
||||
JobId: int option
|
||||
SimType: DriftersVariant
|
||||
SimFormat: DriftersFormat
|
||||
}
|
||||
static member Create (sim: SimulationModel) =
|
||||
let archive =
|
||||
{ ArchiveProps.empty with
|
||||
reference = sim.aid |> Some
|
||||
freq = sim.saveFreq.ToFloat() |> int
|
||||
startTime = sim.startTime
|
||||
}
|
||||
{
|
||||
Archive = archive
|
||||
Duration = sim.simDays |> TimeSpan.FromDays
|
||||
@@ -584,11 +694,12 @@ type SimArchive =
|
||||
| DownwellingSim -> DriftersVariant.Downwelling
|
||||
SimFormat = DriftersFormat.Particle
|
||||
}
|
||||
static member Create (ana: AnalysisModel) =
|
||||
|
||||
static member Create(ana: AnalysisModel) =
|
||||
let tStart = ana.startTime |> Option.defaultValue DateTime.Now
|
||||
let tEnd = ana.endTime |> Option.defaultValue DateTime.Now
|
||||
{
|
||||
Archive = { ArchiveProps.empty with freq = ana.saveFreq.ToFloat() |> int; startTime = tStart }
|
||||
Archive = { ArchiveProps.empty with freq = ana.saveFreq.ToFloat () |> int; startTime = tStart }
|
||||
Duration = tEnd - tStart
|
||||
Reverse = ana.reverse
|
||||
Status = JobStatus.New
|
||||
@@ -623,7 +734,7 @@ type PlumeSimModel = {
|
||||
name = ""
|
||||
fvcom = System.Guid.Empty
|
||||
start = DateTime.Now
|
||||
stop = DateTime.Now.AddDays(4)
|
||||
stop = DateTime.Now.AddDays (4)
|
||||
timeIdx = 0
|
||||
}
|
||||
|
||||
@@ -645,11 +756,7 @@ type PlumeTraits = {
|
||||
salt: float
|
||||
transport: float
|
||||
} with
|
||||
static member empty = {
|
||||
temp = 15.0
|
||||
salt = 0.0
|
||||
transport = 0.333
|
||||
}
|
||||
static member empty = { temp = 15.0; salt = 0.0; transport = 0.333 }
|
||||
|
||||
type PlumeType =
|
||||
| DefaultPlume
|
||||
@@ -661,46 +768,70 @@ type PlumeType =
|
||||
match this with
|
||||
| DefaultPlume -> "Plume"
|
||||
|
||||
type XtractType =
|
||||
| DefaultXtract
|
||||
override this.ToString (): string =
|
||||
match this with
|
||||
| DefaultXtract -> "xtract"
|
||||
|
||||
member this.ToLabel() =
|
||||
match this with
|
||||
| DefaultXtract -> "Xtract"
|
||||
|
||||
type XtractData = {
|
||||
name: string
|
||||
fvcom: System.Guid
|
||||
start: DateTime
|
||||
stop: DateTime
|
||||
} with
|
||||
static member empty = {
|
||||
name = ""
|
||||
fvcom = System.Guid.Empty
|
||||
start = DateTime.Now
|
||||
stop = DateTime.Now.AddDays (2)
|
||||
}
|
||||
|
||||
type SimControlKind =
|
||||
| Drifters of SimType
|
||||
| Plume of PlumeType
|
||||
| DataExtraction of XtractType
|
||||
override this.ToString (): string =
|
||||
match this with
|
||||
| Drifters simType -> string simType
|
||||
| Plume plumeType -> string plumeType
|
||||
| DataExtraction xtractType -> string xtractType
|
||||
member this.ToLabel() =
|
||||
match this with
|
||||
| Drifters simType -> simType.ToLabel ()
|
||||
| Plume plumeType -> plumeType.ToLabel ()
|
||||
| DataExtraction xtractType -> xtractType.ToLabel ()
|
||||
member this.simTypeOpt =
|
||||
match this with
|
||||
| Drifters simType -> Some simType
|
||||
| Plume plumeType -> None
|
||||
| DataExtraction xtractType -> None
|
||||
|
||||
// TODO: Not sure if anything but Mapster needs to know about this
|
||||
type SideNavMode =
|
||||
| OceanControls
|
||||
| SimControls of SimControlKind
|
||||
| AnalysisControls of SimArchive
|
||||
| ColorControls
|
||||
| LayerControls
|
||||
| StatsControls
|
||||
| CropControls
|
||||
// | NetworkControls
|
||||
with
|
||||
member this.ToLabel () =
|
||||
match this with
|
||||
| OceanControls -> "Ocean controls"
|
||||
| SimControls simControlKind -> $"Simulation controls {simControlKind.ToLabel ()}"
|
||||
| AnalysisControls _ -> "Analysis controls"
|
||||
| ColorControls -> "Color controls"
|
||||
| LayerControls -> "Layer controls"
|
||||
| StatsControls -> "Stats controls"
|
||||
| CropControls -> "Crop controls"
|
||||
| OceanControls
|
||||
| SimControls of SimControlKind
|
||||
| AnalysisControls of SimArchive
|
||||
| ColorControls
|
||||
| LayerControls
|
||||
| CropControls
|
||||
// | NetworkControls
|
||||
member this.ToLabel() =
|
||||
match this with
|
||||
| OceanControls -> "Ocean controls"
|
||||
| SimControls simControlKind -> $"Simulation controls {simControlKind.ToLabel ()}"
|
||||
| AnalysisControls _ -> "Analysis controls"
|
||||
| ColorControls -> "Color controls"
|
||||
| LayerControls -> "Layer controls"
|
||||
| CropControls -> "Crop controls"
|
||||
|
||||
type Props =
|
||||
{
|
||||
arrows: Arrow<float>[]
|
||||
}
|
||||
type Props = {
|
||||
arrows: Arrow<float>[]
|
||||
} with
|
||||
static member empty = { arrows = [||] }
|
||||
|
||||
module HeatMap =
|
||||
@@ -711,13 +842,15 @@ type SampleData = {
|
||||
name: string
|
||||
coord: Coordinate
|
||||
radius: float
|
||||
value: float
|
||||
value: float option
|
||||
nodes: NodeIdx array option
|
||||
} with
|
||||
static member empty = {
|
||||
coord = [| 0.0; 0.0 |]
|
||||
name = "Sample"
|
||||
radius = 5.0
|
||||
value = 0.0
|
||||
value = None
|
||||
nodes = None
|
||||
}
|
||||
|
||||
type LineData = {
|
||||
|
||||
@@ -6,8 +6,7 @@ open Fable.Core.JsInterop
|
||||
|
||||
type private IUmami =
|
||||
abstract track : payload: obj -> unit
|
||||
abstract track : eventName: string -> unit
|
||||
abstract track : eventName: string * data: obj -> unit
|
||||
abstract track : eventName: string * ?data: obj -> unit
|
||||
|
||||
type Umami =
|
||||
static member track(eventName: string) =
|
||||
@@ -38,9 +37,10 @@ module Umami =
|
||||
console.debug("[Umami] Mounting UmamiScript")
|
||||
)
|
||||
|
||||
JSX.html $"""
|
||||
<script async src="https://umami.srv.oceanbox.io/umami" onLoad={fun () -> handleLoad ()} />
|
||||
"""
|
||||
JSX.html
|
||||
$"""
|
||||
<script async src="https://umami.srv.oceanbox.io/umami" onLoad={fun () -> handleLoad ()} />
|
||||
"""
|
||||
|> toReact
|
||||
|
||||
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
module Utils
|
||||
|
||||
open System
|
||||
|
||||
open Browser
|
||||
open Fable.Core
|
||||
open Fable.Core.JsInterop
|
||||
open Fable.OpenLayers
|
||||
open FsToolkit.ErrorHandling
|
||||
|
||||
open Proj4
|
||||
|
||||
proj4.defs ("EPSG:25832", "+proj=utm +zone=32 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs")
|
||||
@@ -19,17 +22,27 @@ let epsg25832 = Proj.get "EPSG:25832"
|
||||
let dimap f (a, b) = f a, f b
|
||||
|
||||
let inline posToCoord (pos: 'a * 'a) : Coordinate = unbox pos
|
||||
let inline coordToPos (coord : Coordinate) = coord[0], coord[1]
|
||||
let inline coordToPos (coord: Coordinate) = coord[0], coord[1]
|
||||
|
||||
let inline tryMaxDefault d s = if (Seq.isEmpty s) then d else Seq.max s
|
||||
let inline tryParseFloat (s: string) : float option = s |> Double.TryParse |> function | true, f -> Some f | _ -> None
|
||||
let inline tryMaxDefault d s = if Seq.isEmpty s then d else Seq.max s
|
||||
let inline tryParseFloat (s: string) : float option =
|
||||
s
|
||||
|> Double.TryParse
|
||||
|> function
|
||||
| true, f -> Some f
|
||||
| _ -> None
|
||||
|
||||
let formatDigits n m = {| minimumFractionDigits = n; maximumFractionDigits = m |} |> JS.JSON.stringify
|
||||
let formatPercent n = {| style = "percent"; minimumFractionDigits = n; maximumFractionDigits = n |} |> JS.JSON.stringify
|
||||
let formatDigits n m =
|
||||
{| minimumFractionDigits = n; maximumFractionDigits = m |} |> JS.JSON.stringify
|
||||
let formatPercent n =
|
||||
{| style = "percent"; minimumFractionDigits = n; maximumFractionDigits = n |}
|
||||
|> JS.JSON.stringify
|
||||
|
||||
let toWgs84 (x, y) = Proj.transform (posToCoord(x, y), epsg3857, wgs84)
|
||||
let toWgs84 (x, y) =
|
||||
Proj.transform (posToCoord (x, y), epsg3857, wgs84)
|
||||
|
||||
let toEpsg3857 (x, y) = Proj.transform (posToCoord(x, y), wgs84, epsg3857)
|
||||
let toEpsg3857 (x, y) =
|
||||
Proj.transform (posToCoord (x, y), wgs84, epsg3857)
|
||||
|
||||
let inline toWgs84' pos = toWgs84 pos |> coordToPos
|
||||
|
||||
@@ -44,7 +57,24 @@ let coordToWgs84Pos = coordToWgs84 >> coordToPos
|
||||
let coordToEpsg3857Pos = coordToEpsg3857 >> coordToPos
|
||||
|
||||
let mercatorScaleFactor (lat: float) =
|
||||
1.0 / (lat * Math.PI / 180.0 |> Math.Cos)
|
||||
1.0 / (lat * Math.PI / 180.0 |> Math.Cos)
|
||||
|
||||
let toRadians degrees = degrees * Math.PI / 180.0
|
||||
|
||||
let haversineDistance (lon1: float, lat1: float) (lon2: float, lat2: float) =
|
||||
let earthRadius = 6.371e6 // Earth's radius in meters
|
||||
|
||||
let dLat = toRadians(lat2 - lat1)
|
||||
let dLon = toRadians(lon2 - lon1)
|
||||
let lat1 = toRadians(lat1)
|
||||
let lat2 = toRadians(lat2)
|
||||
|
||||
let a = Math.Sin(dLat/2.0) * Math.Sin(dLat/2.0) +
|
||||
Math.Sin(dLon/2.0) * Math.Sin(dLon/2.0) *
|
||||
Math.Cos(lat1) * Math.Cos(lat2)
|
||||
|
||||
let c = 2.0 * Math.Asin(Math.Sqrt(a))
|
||||
earthRadius * c // Returns distance in meters
|
||||
|
||||
[<Emit("atob($0)")>]
|
||||
let fromBase64String (s: string) : string = jsNative
|
||||
@@ -52,96 +82,38 @@ let fromBase64String (s: string) : string = jsNative
|
||||
[<Emit("btoa($0)")>]
|
||||
let toBase64String (s: string) : string = jsNative
|
||||
|
||||
let strNull = String.IsNullOrWhiteSpace
|
||||
let strNotNull = strNull >> not
|
||||
/// Helper function for testing whether a string is null or only whitespace
|
||||
let tryStr str =
|
||||
if String.IsNullOrWhiteSpace str then
|
||||
None
|
||||
else
|
||||
Some str
|
||||
let tryStr str = if strNotNull str then Some str else None
|
||||
|
||||
let tryGetElemRect id =
|
||||
let elem : Types.HTMLElement = document.getElementById id
|
||||
/// Uses js getElementById on the HTML id. Tests elem for isNullOrUndefined
|
||||
let tryElem (id: string) : Types.Element option =
|
||||
let elem = document.getElementById id
|
||||
|
||||
if isNullOrUndefined elem then
|
||||
None
|
||||
else
|
||||
elem.getBoundingClientRect()
|
||||
|> Some
|
||||
if isNullOrUndefined elem then None else Some elem
|
||||
|
||||
let tryGetElemRect (id: string) : Types.ClientRect option =
|
||||
tryElem id |> Option.map _.getBoundingClientRect()
|
||||
|
||||
module Result =
|
||||
let apply fRes xRes =
|
||||
match fRes, xRes with
|
||||
| Ok f, Ok x -> Ok (f x)
|
||||
| _ -> Error ()
|
||||
let prettyPrintCoord (p: float * float) : string =
|
||||
let lat, lng = toWgs84' p
|
||||
sprintf "%0.3f, %0.3f" lat lng
|
||||
|
||||
let mapError (f: 'a -> 'b) (res: Result<'T, 'a>) : Result<_, 'b> =
|
||||
match res with
|
||||
| Ok x -> Ok x
|
||||
| Error err ->
|
||||
f err
|
||||
|> Error
|
||||
|
||||
module Array =
|
||||
let (<*>) = Result.apply
|
||||
|
||||
let notEmpty (arr : 'T array) : bool =
|
||||
arr
|
||||
|> Array.isEmpty
|
||||
|> not
|
||||
|
||||
let traverseResult f array =
|
||||
let cons head tail = Array.append [| head |] tail
|
||||
let init = Ok [||]
|
||||
let folder head tail =
|
||||
Ok cons <*> f head <*> tail
|
||||
|
||||
Array.foldBack folder array init
|
||||
|
||||
let sequenceResults x = traverseResult id x
|
||||
|
||||
let depthsToCommon5m depths =
|
||||
let depthsToCommon5m (depths: single array) : (int * int) list =
|
||||
let dp = [| -5.f; -10.f; -15.f; -25.f; -50.f; -99999.f |]
|
||||
|
||||
((0, [ 0 ]), Array.indexed depths)
|
||||
||> Array.fold (fun (n, a) (l, x) ->
|
||||
if x > dp[n] then
|
||||
n, a
|
||||
else
|
||||
(n + 1, l :: a))
|
||||
||> Array.fold (fun (n, a) (l, x) -> if x > dp[n] then n, a else n + 1, l :: a)
|
||||
|> snd
|
||||
|> fun x -> (depths.Length - 1) :: x
|
||||
|> fun x -> depths.Length - 1 :: x
|
||||
|> List.rev
|
||||
|> List.map (fun n -> n, Math.Round(float depths[n] / 5.0) * 5.0 |> int)
|
||||
|
||||
let tryGetNearestNodeAndElement dataSvc aid (x, y) =
|
||||
async {
|
||||
let api = Remoting.proximityApi dataSvc
|
||||
let! nodes = api.GetNearestNodes (aid, [| x, y |])
|
||||
let! elems = api.GetNearestElements (aid, [| x, y |])
|
||||
let firstNode =
|
||||
nodes
|
||||
|> Array.tryHead
|
||||
|> Option.flatten
|
||||
let firstElem =
|
||||
elems
|
||||
|> Array.tryHead
|
||||
|> Option.flatten
|
||||
|
||||
return
|
||||
firstNode
|
||||
|> Option.bind (fun node ->
|
||||
firstElem
|
||||
|> Option.map (fun elem ->
|
||||
node, elem
|
||||
)
|
||||
)
|
||||
}
|
||||
|> List.map (fun n -> n, Math.Round (float depths[n] / 5.0) * 5.0 |> int)
|
||||
|
||||
let handleKeyPress key action =
|
||||
fun () ->
|
||||
let handleKeyup (ev: Browser.Types.Event) =
|
||||
let kev = ev :?> Browser.Types.KeyboardEvent
|
||||
let handleKeyup (ev: Types.Event) =
|
||||
let kev = ev :?> Types.KeyboardEvent
|
||||
if kev.key = key then
|
||||
action ()
|
||||
|
||||
@@ -150,8 +122,7 @@ let handleKeyPress key action =
|
||||
// the two functions between add and remove..
|
||||
window.addEventListener ("keyup", handleKeyup, true)
|
||||
|
||||
Lit.Hook.createDisposable (fun () ->
|
||||
window.removeEventListener ("keyup", handleKeyup, true))
|
||||
Lit.Hook.createDisposable (fun () -> window.removeEventListener ("keyup", handleKeyup, true))
|
||||
|
||||
let onEnterOrEscape onEnter onEscape (ev: Types.Event) =
|
||||
let kev = ev :?> Types.KeyboardEvent
|
||||
@@ -160,17 +131,22 @@ let onEnterOrEscape onEnter onEscape (ev: Types.Event) =
|
||||
| "Escape" -> onEscape ev
|
||||
| _ -> ()
|
||||
|
||||
let debounce delay (f: 'T -> unit) : 'T -> unit =
|
||||
let mutable timer = unbox null
|
||||
fun args ->
|
||||
do JS.clearTimeout timer
|
||||
do timer <- JS.setTimeout (fun () -> f args) delay
|
||||
|
||||
/// <summary>
|
||||
/// Calculate the ISO week number from a date. Taken from https://weeknumber.com/how-to/javascript since we cannot
|
||||
/// use https://learn.microsoft.com/en-us/dotnet/api/system.globalization.calendar.getweekofyear?view=net-8.0
|
||||
/// or https://learn.microsoft.com/en-us/dotnet/api/system.globalization.isoweek?view=net-8.0
|
||||
/// </summary>
|
||||
let getWeek (date: DateTime) : int =
|
||||
let findThursdayInWeek (day: int) (dayInWeek : int) : int =
|
||||
day + 3 - (dayInWeek + 6) % 7
|
||||
let thisWeeksThursday : int = findThursdayInWeek date.Day (int date.DayOfWeek)
|
||||
let findThursdayInWeek (day: int) (dayInWeek: int) : int = day + 3 - (dayInWeek + 6) % 7
|
||||
let thisWeeksThursday: int = findThursdayInWeek date.Day (int date.DayOfWeek)
|
||||
let thursdayThisWeek =
|
||||
DateTime(
|
||||
DateTime (
|
||||
year = date.Year,
|
||||
month = date.Month,
|
||||
day = thisWeeksThursday,
|
||||
@@ -180,18 +156,10 @@ let getWeek (date: DateTime) : int =
|
||||
kind = DateTimeKind.Utc
|
||||
)
|
||||
let weekOne =
|
||||
DateTime(
|
||||
year = date.Year,
|
||||
month = 1,
|
||||
day = 4,
|
||||
hour = 0,
|
||||
minute = 0,
|
||||
second = 0,
|
||||
kind = DateTimeKind.Utc
|
||||
)
|
||||
DateTime (year = date.Year, month = 1, day = 4, hour = 0, minute = 0, second = 0, kind = DateTimeKind.Utc)
|
||||
let thursday = findThursdayInWeek weekOne.Day (int weekOne.DayOfWeek)
|
||||
let thursdayWeekOne =
|
||||
DateTime(
|
||||
DateTime (
|
||||
year = date.Year,
|
||||
month = 1,
|
||||
day = thursday,
|
||||
@@ -203,38 +171,66 @@ let getWeek (date: DateTime) : int =
|
||||
let diff = thursdayThisWeek - thursdayWeekOne
|
||||
let week = 1 + int (diff.TotalDays / 7.0)
|
||||
|
||||
// TODO: Test when there are 53 weeks ...
|
||||
Int32.Clamp(week, 1, 52)
|
||||
// TODO: Test when there are 53 weeks...
|
||||
Int32.Clamp (week, 1, 52)
|
||||
|
||||
/// Given a list of indices, try adding a new one by adding the last one by 1, or return 1. If 1 it is then the first entry
|
||||
let tryAddIdx: int seq -> int =
|
||||
Seq.tryLast
|
||||
>> Option.map ((+) 1)
|
||||
>> Option.defaultValue 1
|
||||
module Archives =
|
||||
open Atlantis.Types
|
||||
|
||||
/// Given a list of indices, return last idx, or if there's a missing index in the sequence
|
||||
let findIdx (idxs: int seq) : int =
|
||||
// NOTE: We want the indices to start at 1
|
||||
(1, idxs)
|
||||
||> Seq.fold (fun index idx ->
|
||||
if index = idx then
|
||||
// NOTE: If the sequence advances as expected, increment the acc together with the indices ...
|
||||
idx + 1
|
||||
/// Calculates the time for the given frame
|
||||
let findFrameTime (archive: Atlantis.Types.ArchiveInfo) (frame: int) : DateTime =
|
||||
let duration = System.TimeSpan.FromSeconds (float frame * float archive.saveFreq)
|
||||
archive.startTime.ToUniversalTime() + duration
|
||||
|
||||
/// Calculates the time for the given frame. Must be within range of the archives' total frames.
|
||||
let tryFindFrameTime (archive: Atlantis.Types.ArchiveInfo) (frame: int) : DateTime option =
|
||||
if 0 <= frame && frame <= archive.frames then
|
||||
Some (findFrameTime archive frame)
|
||||
else
|
||||
// ... but if the sequence is broken, remember which index it stopped at, and remember that
|
||||
index
|
||||
)
|
||||
None
|
||||
|
||||
/// Given a list of indices, try to getting the very last key, otherwise return 1
|
||||
let tryLastIdx: int seq -> int = Seq.tryLast >> Option.defaultValue 1
|
||||
/// Calculates when the archive ends based on the total number of frames, the save frequency and the start time.
|
||||
let findEndTime (archive: Atlantis.Types.ArchiveInfo) : DateTime =
|
||||
let duration = System.TimeSpan.FromSeconds (float archive.frames * float archive.saveFreq)
|
||||
archive.startTime.ToUniversalTime() + duration
|
||||
|
||||
let initAtlantisSessionUrls () =
|
||||
async {
|
||||
let! aUrl = Remoting.servicesApi.GetArchiveService()
|
||||
console.log $"Archive service: {aUrl}"
|
||||
sessionStorage["archmaester_url"] <- aUrl
|
||||
let getMaxTimeStep (timeUnit: Atlantis.Types.TimeUnit) (archive: Atlantis.Types.ArchiveInfo) frame : int option =
|
||||
let availableSeconds = (archive.frames - frame) * archive.saveFreq |> float
|
||||
let ts = TimeSpan.FromSeconds availableSeconds
|
||||
|
||||
let! sUrl = Remoting.servicesApi.GetFileService()
|
||||
console.log $"Data service: {sUrl}"
|
||||
sessionStorage["sorcerer_url"] <- sUrl
|
||||
}
|
||||
match timeUnit with
|
||||
| TimeUnit.Hour -> if ts.Hours >= 1 then Some ts.Hours else None
|
||||
| TimeUnit.Day -> if ts.Days >= 1 then Some ts.Hours else None
|
||||
| TimeUnit.Week -> if ts.Days >= 7 then Some (ts.Days / 7) else None
|
||||
| TimeUnit.Month -> if ts.Days >= 30 then Some (ts.Days / 30) else None
|
||||
| TimeUnit.Quarter -> if ts.Days >= 90 then Some (ts.Days / 90) else None
|
||||
| TimeUnit.Year -> if ts.Days >= 365 then Some (ts.Days / 365) else None
|
||||
|
||||
module Result =
|
||||
let apply (fRes: Result<'T -> 'U, 'Err>) (xRes: Result<'T, 'Err>) : Result<'U, 'Err> =
|
||||
match fRes, xRes with
|
||||
| Ok f, Ok x -> Ok (f x)
|
||||
| Ok _, Error err -> Error err
|
||||
| Error err, Ok _ -> Error err
|
||||
// TODO: Which error should you return?
|
||||
| Error _, Error err -> Error err
|
||||
|
||||
let mapError (f: 'T -> 'U) (res: Result<'Ok, 'T>) : Result<'Ok, 'U> =
|
||||
match res with
|
||||
| Ok x -> Ok x
|
||||
| Error err -> Error (f err)
|
||||
|
||||
module Array =
|
||||
let (<*>) = Result.apply
|
||||
|
||||
let notEmpty (arr: 'T array) : bool = arr |> Array.isEmpty |> not
|
||||
|
||||
/// Monadic foldBack
|
||||
let traverseResult (f: 'T -> Result<'U, 'Err>) (array: 'T array) : Result<'U array, 'Err> =
|
||||
let cons head tail = tail |> Array.append [| head |]
|
||||
let init = Ok [||]
|
||||
let folder head tail = Ok cons <*> f head <*> tail
|
||||
|
||||
init |> Array.foldBack folder array
|
||||
|
||||
let sequenceResults x = traverseResult id x
|
||||
|
||||
@@ -5,17 +5,15 @@ open Fable.Core.JsInterop
|
||||
open Fable.OpenLayers
|
||||
|
||||
[<Erase>]
|
||||
type WebGLProp =
|
||||
interface
|
||||
end
|
||||
type WebGLProp = interface end
|
||||
|
||||
type WebGLLayer =
|
||||
inherit Ol.Layer.Layer
|
||||
abstract vertices: single [] with get, set
|
||||
abstract indices: int [] with get, set
|
||||
abstract props: single [] with get, set
|
||||
abstract vertices: single[] with get, set
|
||||
abstract indices: int[] with get, set
|
||||
abstract props: single[] with get, set
|
||||
|
||||
abstract updatePalette: (single * single * single) [] -> int -> (float * float) -> unit
|
||||
abstract updatePalette: (single * single * single)[] -> int -> (float * float) -> unit
|
||||
abstract updateOpacity: float -> unit
|
||||
abstract updateAttenuation: float -> unit
|
||||
abstract updateProps: single array -> unit
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"version": 1,
|
||||
"version": 2,
|
||||
"dependencies": {
|
||||
"net9.0": {
|
||||
"net10.0": {
|
||||
"Fable.Browser.IndexedDB": {
|
||||
"type": "Direct",
|
||||
"requested": "[2.2.0, )",
|
||||
@@ -193,6 +193,15 @@
|
||||
"resolved": "9.0.303",
|
||||
"contentHash": "6JlV8aD8qQvcmfoe/PMOxCHXc0uX4lR23u0fAyQtnVQxYULLoTZgwgZHSnRcuUHOvS3wULFWcwdnP1iwslH60g=="
|
||||
},
|
||||
"FsToolkit.ErrorHandling": {
|
||||
"type": "Direct",
|
||||
"requested": "[5.0.1, )",
|
||||
"resolved": "5.0.1",
|
||||
"contentHash": "93oG3WSogK05H4gkikAmx5pBf30TQJfO1Jky+o/N/nv+RTP3nfOfjlmCHzuyUjQCRFOQog/xQabcky+WBWceeQ==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "9.0.300"
|
||||
}
|
||||
},
|
||||
"Matplotlib.ColorMaps": {
|
||||
"type": "Direct",
|
||||
"requested": "[3.0.1, )",
|
||||
@@ -225,46 +234,6 @@
|
||||
"Fable.Core": "4.1.0"
|
||||
}
|
||||
},
|
||||
"Dapr.Actors": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.16.0",
|
||||
"contentHash": "s9v6VofXXYoRqZJQlQbvNYYSlGhkL+Z+bpqrx1TRo06kLhANeDmXA9yeVaD+1KwJIO1chUFj5O4iKuTxIkg1sA==",
|
||||
"dependencies": {
|
||||
"Dapr.Client": "1.16.0",
|
||||
"Dapr.Common": "1.16.0",
|
||||
"Google.Api.CommonProtos": "2.17.0",
|
||||
"Google.Protobuf": "3.32.0",
|
||||
"Grpc.Net.Client": "2.71.0",
|
||||
"Microsoft.Extensions.Configuration": "9.0.8",
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "9.0.8",
|
||||
"Microsoft.Extensions.DependencyInjection": "9.0.8",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.8",
|
||||
"Microsoft.Extensions.Http": "9.0.8",
|
||||
"Microsoft.Extensions.Logging": "9.0.8",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.8",
|
||||
"Microsoft.Extensions.Options": "9.0.8"
|
||||
}
|
||||
},
|
||||
"Dapr.Client": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.16.0",
|
||||
"contentHash": "dFDKol+mtQrk1lIKlEyCx3k6W0Pf+0wC6xcsaDqa0Bg+XCWDc4juROuDcSb0/L1Y+Ev6LSLDMC/FgzNWMw9YtQ==",
|
||||
"dependencies": {
|
||||
"Dapr.Common": "1.16.0",
|
||||
"Dapr.Protos": "1.16.0",
|
||||
"Google.Api.CommonProtos": "2.17.0",
|
||||
"Google.Protobuf": "3.32.0",
|
||||
"Grpc.Net.Client": "2.71.0",
|
||||
"Microsoft.Extensions.Configuration": "9.0.8",
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "9.0.8",
|
||||
"Microsoft.Extensions.DependencyInjection": "9.0.8",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.8",
|
||||
"Microsoft.Extensions.Http": "9.0.8",
|
||||
"Microsoft.Extensions.Logging": "9.0.8",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.8",
|
||||
"Microsoft.Extensions.Options": "9.0.8"
|
||||
}
|
||||
},
|
||||
"Dapr.Common": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.16.0",
|
||||
@@ -296,14 +265,6 @@
|
||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.8"
|
||||
}
|
||||
},
|
||||
"Drifters.Api": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.22.0",
|
||||
"contentHash": "EQguKE22Tfd3ayO/jdWiWMBK5R1uzcYo+8agG3ZzAJ1ltl72mIXHqr68BKqO4uhOLtiFs8ErZa4cZ9NVueYHWA==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "9.0.201"
|
||||
}
|
||||
},
|
||||
"Fable.AST": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.2.1",
|
||||
@@ -571,7 +532,7 @@
|
||||
"atlantis.api": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "[9.0.201, )",
|
||||
"FSharp.Core": "[9.0.303, )",
|
||||
"Hipster.Api": "[1.0.1, )",
|
||||
"Petimeter.Api": "[1.0.0, )"
|
||||
}
|
||||
@@ -581,29 +542,80 @@
|
||||
"dependencies": {
|
||||
"Dapr.Actors": "[1.16.0, )",
|
||||
"Drifters.Api": "[6.22.0, )",
|
||||
"FSharp.Core": "[9.0.201, )"
|
||||
"FSharp.Core": "[9.0.303, )"
|
||||
}
|
||||
},
|
||||
"Oceanbox.DataAgent.Api": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "[9.0.201, )"
|
||||
"FSharp.Core": "[9.0.303, )"
|
||||
}
|
||||
},
|
||||
"petimeter.api": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"Dapr.Actors": "[1.16.0, )",
|
||||
"FSharp.Core": "[9.0.201, )"
|
||||
"FSharp.Core": "[9.0.303, )"
|
||||
}
|
||||
},
|
||||
"sorcerer.api": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"Drifters.Api": "[6.22.0, )",
|
||||
"FSharp.Core": "[9.0.201, )",
|
||||
"FSharp.Core": "[9.0.303, )",
|
||||
"Oceanbox.DataAgent.Api": "[7.2.1, )"
|
||||
}
|
||||
},
|
||||
"Dapr.Actors": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[1.16.0, )",
|
||||
"resolved": "1.16.0",
|
||||
"contentHash": "s9v6VofXXYoRqZJQlQbvNYYSlGhkL+Z+bpqrx1TRo06kLhANeDmXA9yeVaD+1KwJIO1chUFj5O4iKuTxIkg1sA==",
|
||||
"dependencies": {
|
||||
"Dapr.Client": "1.16.0",
|
||||
"Dapr.Common": "1.16.0",
|
||||
"Google.Api.CommonProtos": "2.17.0",
|
||||
"Google.Protobuf": "3.32.0",
|
||||
"Grpc.Net.Client": "2.71.0",
|
||||
"Microsoft.Extensions.Configuration": "9.0.8",
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "9.0.8",
|
||||
"Microsoft.Extensions.DependencyInjection": "9.0.8",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.8",
|
||||
"Microsoft.Extensions.Http": "9.0.8",
|
||||
"Microsoft.Extensions.Logging": "9.0.8",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.8",
|
||||
"Microsoft.Extensions.Options": "9.0.8"
|
||||
}
|
||||
},
|
||||
"Dapr.Client": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[1.16.0, )",
|
||||
"resolved": "1.16.0",
|
||||
"contentHash": "dFDKol+mtQrk1lIKlEyCx3k6W0Pf+0wC6xcsaDqa0Bg+XCWDc4juROuDcSb0/L1Y+Ev6LSLDMC/FgzNWMw9YtQ==",
|
||||
"dependencies": {
|
||||
"Dapr.Common": "1.16.0",
|
||||
"Dapr.Protos": "1.16.0",
|
||||
"Google.Api.CommonProtos": "2.17.0",
|
||||
"Google.Protobuf": "3.32.0",
|
||||
"Grpc.Net.Client": "2.71.0",
|
||||
"Microsoft.Extensions.Configuration": "9.0.8",
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "9.0.8",
|
||||
"Microsoft.Extensions.DependencyInjection": "9.0.8",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.8",
|
||||
"Microsoft.Extensions.Http": "9.0.8",
|
||||
"Microsoft.Extensions.Logging": "9.0.8",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.8",
|
||||
"Microsoft.Extensions.Options": "9.0.8"
|
||||
}
|
||||
},
|
||||
"Drifters.Api": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[6.22.0, )",
|
||||
"resolved": "6.22.0",
|
||||
"contentHash": "EQguKE22Tfd3ayO/jdWiWMBK5R1uzcYo+8agG3ZzAJ1ltl72mIXHqr68BKqO4uhOLtiFs8ErZa4cZ9NVueYHWA==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "9.0.201"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
module Login
|
||||
|
||||
open Browser
|
||||
open Fable.Core.JsInterop
|
||||
open Lit
|
||||
open Maps
|
||||
open Remoting
|
||||
|
||||
importSideEffects "./public/style.scss"
|
||||
importSideEffects "@vaadin/login"
|
||||
|
||||
let register () = ()
|
||||
|
||||
[<LitElement("login-app")>]
|
||||
let LoginApp () =
|
||||
let _ = LitElement.init (fun cfg -> cfg.useShadowDom <- false)
|
||||
let loginFailed, setLoginFailed = Hook.useState false
|
||||
|
||||
let handleLogin credentials =
|
||||
async {
|
||||
match! authApi.Login credentials with
|
||||
| Ok t ->
|
||||
console.log "Logged in!"
|
||||
localStorage.setItem ("access_token", t.access_token)
|
||||
if t.refresh_token.Length > 0 then
|
||||
localStorage.setItem ("refresh_token", t.refresh_token)
|
||||
|
||||
// NOTE(SimenLK): It has to reload because the api is created on start up, when there is no access token..
|
||||
// Dom.window.location.reload ()
|
||||
Dom.window.location.href <- "/atlas.html"
|
||||
| Error e ->
|
||||
console.error e
|
||||
setLoginFailed true
|
||||
localStorage.removeItem "access_token"
|
||||
localStorage.removeItem "refresh_token"
|
||||
}
|
||||
|> Async.StartImmediate
|
||||
|
||||
Hook.useEffectOnce (fun () ->
|
||||
Auth.establishAuthenication ()
|
||||
// async {
|
||||
// match! authApi.IsAuthenticated() with
|
||||
// | Some _ ->
|
||||
// console.log "already authenticated"
|
||||
// Dom.window.location.href <- "/"
|
||||
// | None -> console.log "not yet authenticated"
|
||||
// }
|
||||
// |> Async.StartImmediate
|
||||
)
|
||||
|
||||
html
|
||||
$"""
|
||||
<vaadin-login-overlay
|
||||
title=""
|
||||
description=""
|
||||
opened
|
||||
no-autofocus
|
||||
?error={loginFailed}
|
||||
@login={Ev(fun x -> handleLogin { username = x?detail?username; password = x?detail?password })}
|
||||
@forgot-password={Ev(fun _ -> Dom.window.location.href <- "https://keycloak.dev.oceanbox.io/realms/Oceanbox/account/#/security/signingin")}
|
||||
></vaadin-login-overlay>
|
||||
"""
|
||||
@@ -1,27 +1,15 @@
|
||||
module Colors
|
||||
|
||||
open System
|
||||
open Fable.Remoting.MsgPack.Write
|
||||
|
||||
open Browser
|
||||
open Fable.Core.JsInterop
|
||||
open Matplotlib.ColorMaps
|
||||
|
||||
open Atlantis.Types
|
||||
open Drifters.ApiTypes
|
||||
open Model
|
||||
open Lit
|
||||
open Fable.Core.JsInterop
|
||||
|
||||
let getColormap colorMap (lo, hi) =
|
||||
let mode, cmap = colorMap
|
||||
|
||||
let cm =
|
||||
match cmap with
|
||||
| ColorMap.Ocean name -> ColorMap<single>(name, lo, hi, 256, Ocean.colors)
|
||||
| ColorMap.Color16 name -> ColorMap<single>(name, lo, hi, 256, Color16.colors)
|
||||
| ColorMap.Custom name -> failwith "not implemented"
|
||||
|
||||
match mode with
|
||||
| ColorMode.Normal -> cm
|
||||
| ColorMode.Mirror -> ColorMap<single>.mirrorPalette cm
|
||||
| ColorMode.Gray -> ColorMap<single>.toGrayscale cm
|
||||
|
||||
let getProp (layer: MapLayer) (layers: Map<MapLayer, ViewProp>) =
|
||||
match layer with
|
||||
@@ -30,94 +18,13 @@ let getProp (layer: MapLayer) (layers: Map<MapLayer, ViewProp>) =
|
||||
| Crop -> layers[Crop]
|
||||
| _ -> layers[Ocean]
|
||||
|
||||
let colorPalette model =
|
||||
let prop = getProp model.activeLayer model.glLayers
|
||||
let color = model.propColors.TryFind prop.PropType |> Option.defaultValue PropColor.empty
|
||||
let colorMap = getColormap color.ColorMap color.ViewRange
|
||||
let numberOfColors = color.Levels - 1
|
||||
let toSkip: int = colorMap.nColors / numberOfColors
|
||||
let flipped =
|
||||
match prop.PropType with
|
||||
| Prop.Bathy
|
||||
| Prop.SW -> true
|
||||
| _ -> false
|
||||
let rounding =
|
||||
match prop.PropType with
|
||||
| Prop.DW
|
||||
| Prop.SedV2
|
||||
| Prop.Conc2D
|
||||
| Prop.Conc3D
|
||||
| Prop.Sed _ -> true
|
||||
| _ -> false
|
||||
let unitStr = prop.PropType.unit
|
||||
|
||||
let roundToDecimal x =
|
||||
if x <= 0.0 then
|
||||
Decimal x
|
||||
else
|
||||
let y =
|
||||
x
|
||||
|> Math.Log10
|
||||
|> Math.Floor
|
||||
|> fun z -> Math.Pow (10.0, z)
|
||||
x
|
||||
|> fun z -> (z / y)
|
||||
|> Math.Round
|
||||
|> fun z -> z * y
|
||||
|> Decimal
|
||||
|
||||
let colors =
|
||||
// TODO(SimenLK): Do less work by figuring out the indices, instead of looping over all items
|
||||
colorMap.colors
|
||||
|> Array.indexed
|
||||
|> Array.choose (fun (i, c) -> if i % toSkip = 0 then Some c else None)
|
||||
|> Array.mapi (fun i (r, g, b) ->
|
||||
let diff = colorMap.max - colorMap.min
|
||||
let step = diff / float numberOfColors
|
||||
let propValue =
|
||||
let v = float i * step + colorMap.min
|
||||
match prop.PropType with
|
||||
| Prop.WC
|
||||
| Prop.SW -> abs v
|
||||
| _ -> v
|
||||
let valueStr =
|
||||
if color.LogScale then (10.0**propValue) else propValue
|
||||
|>
|
||||
if color.Scientific then
|
||||
sprintf "%.1e"
|
||||
elif rounding then
|
||||
roundToDecimal >> sprintf "%M"
|
||||
else
|
||||
sprintf "%.1f"
|
||||
let red = 256.0f * r
|
||||
let green = 256.0f * g
|
||||
let blue = 256.0f * b
|
||||
html $"""
|
||||
<div class="color-container" >
|
||||
<span class="prop-value-text">{valueStr}</span>
|
||||
<div
|
||||
class="color-box"
|
||||
style="background-color: rgb({red}, {green}, {blue});"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
""")
|
||||
|
||||
html $"""
|
||||
<div class="color-palette" ?flipped={flipped}>
|
||||
{colors}
|
||||
<div class="unit-box" ?flipped={flipped}>
|
||||
<span>{unitStr}</span>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
|
||||
let getPropCM prop =
|
||||
match prop with
|
||||
| Prop.Temp -> ColorMap.Ocean "thermal"
|
||||
| Prop.Salt -> ColorMap.Ocean "haline"
|
||||
| Prop.Dens -> ColorMap.Ocean "thermal"
|
||||
| Prop.WC -> ColorMap.Ocean "curl"
|
||||
| Prop.DW -> ColorMap.Ocean "curl"
|
||||
| Prop.DW _ -> ColorMap.Ocean "curl"
|
||||
| Prop.SedV2 -> ColorMap.Color16 "jet"
|
||||
| Prop.Sed _ -> ColorMap.Color16 "jet"
|
||||
| Prop.SW -> ColorMap.Color16 "jet"
|
||||
@@ -135,6 +42,7 @@ let defaultColors =
|
||||
Prop.Bathy
|
||||
Prop.Temp
|
||||
Prop.Salt
|
||||
Prop.Dens
|
||||
Prop.Zeta
|
||||
Prop.Speed
|
||||
Prop.Conc2D
|
||||
@@ -146,7 +54,8 @@ let defaultColors =
|
||||
Prop.Sed POP
|
||||
Prop.SW
|
||||
Prop.WC
|
||||
Prop.DW
|
||||
Prop.DW 1
|
||||
Prop.DW 2
|
||||
] |> List.map (fun y ->
|
||||
y, { PropColor.empty with
|
||||
ViewRange = y.viewRange
|
||||
@@ -175,14 +84,107 @@ let configPropColors (color: PropColor) (prop: ViewProp) =
|
||||
| Prop.SedV2, _ -> if color.LogScale then (-2.0, 3.0) else (0.0, 1000.0)
|
||||
| Prop.Sed _, Accumulated -> if color.LogScale then (0.0, 4.0) else (0.0, 10_000.0)
|
||||
| Prop.Sed _, _ -> if color.LogScale then (-2.0, 3.0) else (0.0, 1000.0)
|
||||
| Prop.DW, _ -> if color.LogScale then (1.0, 3.0) else (0.0, 1000.0)
|
||||
| Prop.DW 1, _ -> if color.LogScale then (1.0, 3.0) else (0.0, 1000.0)
|
||||
| Prop.DW 2, _ -> if color.LogScale then (0.0, 2.0) else (0.0, 100.0)
|
||||
| _ -> if color.LogScale then (-1.0, 3.0) else prop.PropType.viewRange
|
||||
|
||||
{ color with
|
||||
ViewRange = initialView
|
||||
PropRange = min, max
|
||||
ColorMap = ColorMode.Normal, getPropCM prop.PropType
|
||||
}
|
||||
|
||||
[<HookComponent>]
|
||||
let colorPalette model =
|
||||
let prop = getProp model.activeLayer model.glLayers
|
||||
let color = model.propColors.TryFind prop.PropType |> Option.defaultValue PropColor.empty
|
||||
let colorMap = Lib.Colors.getColormap color.ColorMap color.ViewRange
|
||||
let numberOfColors = color.Levels - 1
|
||||
let toSkip: int = colorMap.nColors / numberOfColors
|
||||
let flipped =
|
||||
match prop.PropType with
|
||||
| Prop.Bathy
|
||||
| Prop.SW -> true
|
||||
| _ -> false
|
||||
let rounding =
|
||||
match prop.PropType with
|
||||
| Prop.DW _
|
||||
| Prop.SedV2
|
||||
| Prop.Conc2D
|
||||
| Prop.Conc3D
|
||||
| Prop.Sed _ -> true
|
||||
| _ -> false
|
||||
let unitStr = prop.PropType.unit
|
||||
|
||||
let roundToDecimal x =
|
||||
if x <= 0.0 then
|
||||
Decimal x
|
||||
else
|
||||
let y =
|
||||
x
|
||||
|> Math.Log10
|
||||
|> Math.Floor
|
||||
|> fun z -> Math.Pow (10.0, z)
|
||||
x
|
||||
|> fun z -> z / y
|
||||
|> Math.Round
|
||||
|> fun z -> z * y
|
||||
|> Decimal
|
||||
// TODO: Get better formatting control with Intl.NumberFormat or D3
|
||||
let format =
|
||||
if color.Scientific then
|
||||
sprintf "%.1e"
|
||||
elif rounding then
|
||||
roundToDecimal >> sprintf "%M"
|
||||
else
|
||||
sprintf "%.1f"
|
||||
|
||||
let colors =
|
||||
colorMap.colors
|
||||
|> Array.indexed
|
||||
|> Array.choose (fun (i, c) -> if i % toSkip = 0 then Some c else None)
|
||||
|> Array.mapi (fun i (r, g, b) ->
|
||||
let diff = colorMap.max - colorMap.min
|
||||
let step = diff / float numberOfColors
|
||||
let propValue =
|
||||
let v = float i * step + colorMap.min
|
||||
match prop.PropType with
|
||||
| Prop.WC
|
||||
| Prop.SW -> abs v
|
||||
| _ -> v
|
||||
let valueStr =
|
||||
if color.LogScale then
|
||||
format (10.0 ** propValue)
|
||||
else
|
||||
format propValue
|
||||
let red = 256.0f * r
|
||||
let green = 256.0f * g
|
||||
let blue = 256.0f * b
|
||||
|
||||
html
|
||||
$"""
|
||||
<div class="color-container" >
|
||||
<span class="prop-value-text">{valueStr}</span>
|
||||
<div
|
||||
class="color-box"
|
||||
style="background-color: rgb({red}, {green}, {blue});"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
)
|
||||
|
||||
html
|
||||
$"""
|
||||
<div class="color-palette" ?flipped={flipped}>
|
||||
{colors}
|
||||
<div class="unit-box" ?flipped={flipped}>
|
||||
<span>{unitStr}</span>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
|
||||
[<HookComponent>]
|
||||
let colorAccordion (dispatch: Msg -> unit) model =
|
||||
let disabled = model.archive.id = Guid.Empty
|
||||
let prop = model.glLayers[model.activeLayer]
|
||||
@@ -207,25 +209,25 @@ let colorAccordion (dispatch: Msg -> unit) model =
|
||||
dispatch (SetCM(ColorMode.Normal, cm))
|
||||
else
|
||||
dispatch (SetCM(x, cm))
|
||||
let loHandler (e: Browser.Types.Event) =
|
||||
let loHandler (e: Types.Event) =
|
||||
let lox: float = !!e.target.Value
|
||||
if lox < v1 then
|
||||
let newColor = { color with ViewRange = (lox, v1) }
|
||||
setColor prop.PropType newColor
|
||||
else
|
||||
()
|
||||
let hiHandler (e: Browser.Types.Event) =
|
||||
let hiHandler (e: Types.Event) =
|
||||
let hix: float = !!e.target.Value
|
||||
if hix > v0 then
|
||||
let newColor = { color with ViewRange = (v0, hix) }
|
||||
setColor prop.PropType newColor
|
||||
else
|
||||
()
|
||||
let alphaHandler (e: Browser.Types.Event) =
|
||||
let alphaHandler (e: Types.Event) =
|
||||
let a: float = !!e.target.Value
|
||||
setAlpha model.activeLayer a
|
||||
|
||||
let levelsHandler (e: Browser.Types.Event) =
|
||||
let levelsHandler (e: Types.Event) =
|
||||
let a: int = !!e.target.Value
|
||||
{ color with Levels = a }
|
||||
|> setColor prop.PropType
|
||||
@@ -241,172 +243,136 @@ let colorAccordion (dispatch: Msg -> unit) model =
|
||||
|
||||
html
|
||||
$"""
|
||||
<sp-accordion size="s" allow-multiple>
|
||||
<div style="width: 90%%;">
|
||||
<sp-accordion-item ?disabled={disabled} ?open={true} label="Colors">
|
||||
<sp-switch label="Mirror" ?checked={m} @click={Ev(setColorMode ColorMode.Mirror)}>
|
||||
Mirror colors
|
||||
</sp-switch>
|
||||
<br>
|
||||
<sp-switch label="Gray" ?checked={g} @click={Ev(setColorMode ColorMode.Gray)}>
|
||||
Grayscale
|
||||
</sp-switch>
|
||||
<br>
|
||||
<sp-switch label="LogScale" ?checked={color.LogScale} @click={Ev(toggleLogScale)}>
|
||||
Logarithmic scale
|
||||
</sp-switch>
|
||||
<sp-switch label="Scientific" ?checked={color.Scientific} @click={Ev(toggleScientific)}>
|
||||
Scientific notation
|
||||
</sp-switch>
|
||||
<div>
|
||||
<sp-field-label for="model-color-options">Color field options</sp-field-label>
|
||||
<sp-field-group id="model-color-options" vertical>
|
||||
<sp-switch label="Mirror" ?checked={m} @click={Ev(setColorMode ColorMode.Mirror)}>
|
||||
Mirror colors
|
||||
</sp-switch>
|
||||
|
||||
<sp-slider label="Levels" min="{4}" max="{20}"
|
||||
step="2" value="{color.Levels}"
|
||||
@mousedown={Ev(fun e -> e.stopPropagation ())}
|
||||
@change={Ev(levelsHandler)}>
|
||||
</sp-slider>
|
||||
<sp-switch label="Gray" ?checked={g} @click={Ev(setColorMode ColorMode.Gray)}>
|
||||
Grayscale
|
||||
</sp-switch>
|
||||
|
||||
<sp-slider
|
||||
style="min-width: auto"
|
||||
step="{step}" min="{lo}" max="{hi}"
|
||||
@mousedown={Ev(fun e -> e.stopPropagation ())}
|
||||
>
|
||||
Range
|
||||
<sp-slider-handle
|
||||
slot="handle"
|
||||
name="min"
|
||||
label="Minimum"
|
||||
value="{v0}"
|
||||
@change={Ev(loHandler)}
|
||||
max="next"
|
||||
></sp-slider-handle>
|
||||
<sp-slider-handle
|
||||
slot="handle"
|
||||
name="max"
|
||||
label="Maximum"
|
||||
value="{v1}"
|
||||
@change={Ev(hiHandler)}
|
||||
min="previous"
|
||||
></sp-slider-handle>
|
||||
</sp-slider>
|
||||
<sp-switch label="LogScale" ?checked={color.LogScale} @click={Ev toggleLogScale}>
|
||||
Logarithmic scale
|
||||
</sp-switch>
|
||||
|
||||
<sp-slider label="Opacity" min="{0.0}" max="{1.0}"
|
||||
step="0.01" value="{prop.Alpha}"
|
||||
@mousedown={Ev(fun e -> e.stopPropagation ())}
|
||||
@change={Ev(alphaHandler)}>
|
||||
</sp-slider>
|
||||
</sp-accordion-item>
|
||||
</div>
|
||||
</sp-accordion>
|
||||
<sp-switch label="Scientific" ?checked={color.Scientific} @click={Ev toggleScientific}>
|
||||
Scientific notation
|
||||
</sp-switch>
|
||||
</sp-field-group>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<sp-slider
|
||||
label="Levels"
|
||||
min="{4}"
|
||||
max="{20}"
|
||||
step="2"
|
||||
value="{color.Levels}"
|
||||
@mousedown={Ev(fun e -> e.stopPropagation ())}
|
||||
@change={Ev levelsHandler}
|
||||
>
|
||||
</sp-slider>
|
||||
<sp-help-text size="s">
|
||||
Choose how many levels the color palette should be
|
||||
</sp-help-text>
|
||||
</div>
|
||||
|
||||
<sp-divider size="s"></sp-divider>
|
||||
|
||||
<div>
|
||||
<sp-slider
|
||||
step="{step}"
|
||||
min="{lo}"
|
||||
max="{hi}"
|
||||
@mousedown={Ev(fun e -> e.stopPropagation ())}
|
||||
>
|
||||
Range
|
||||
<sp-slider-handle
|
||||
slot="handle"
|
||||
name="min"
|
||||
label="Minimum"
|
||||
value="{v0}"
|
||||
@change={Ev(loHandler)}
|
||||
max="next"
|
||||
></sp-slider-handle>
|
||||
<sp-slider-handle
|
||||
slot="handle"
|
||||
name="max"
|
||||
label="Maximum"
|
||||
value="{v1}"
|
||||
@change={Ev(hiHandler)}
|
||||
min="previous"
|
||||
></sp-slider-handle>
|
||||
</sp-slider>
|
||||
</div>
|
||||
|
||||
<sp-divider size="s"></sp-divider>
|
||||
|
||||
<div>
|
||||
<sp-slider
|
||||
label="Opacity"
|
||||
min="{0.0}"
|
||||
max="{1.0}"
|
||||
step="0.01"
|
||||
value="{prop.Alpha}"
|
||||
@mousedown={Ev(fun e -> e.stopPropagation ())}
|
||||
@change={Ev alphaHandler}
|
||||
>
|
||||
</sp-slider>
|
||||
</div>
|
||||
"""
|
||||
|
||||
[<HookComponent>]
|
||||
let colormapAccordion (dispatch: Msg -> unit) model =
|
||||
let disabled = model.archive.id = Guid.Empty
|
||||
let prop =
|
||||
match model.activeLayer with
|
||||
| Crop -> model.glLayers[Crop]
|
||||
| Conc -> model.glLayers[Conc]
|
||||
| _ -> model.glLayers[Ocean]
|
||||
let color = model.propColors[prop.PropType]
|
||||
let setCm x _ =
|
||||
let cm = fst color.ColorMap
|
||||
dispatch (SetCM(cm, x))
|
||||
let selectedColorMode, colorMap = color.ColorMap
|
||||
let selectedColorSet : string =
|
||||
match colorMap with
|
||||
| ColorMap.Ocean _ -> "ocean"
|
||||
| ColorMap.Color16 _ -> "color16"
|
||||
| _ -> "custom"
|
||||
|
||||
let measuresHeight =
|
||||
Utils.tryGetElemRect "measures-controls"
|
||||
|> Option.map _.height
|
||||
|> Option.defaultValue 80
|
||||
let disabled = model.archive.id = Guid.Empty
|
||||
|
||||
let colorSamples cmap =
|
||||
let colorMap = getColormap (ColorMode.Normal, cmap) (0.0, 1.0)
|
||||
let numberOfColors = 10
|
||||
let toSkip: int = colorMap.nColors / numberOfColors
|
||||
colorMap.colors
|
||||
|> Array.indexed
|
||||
|> Array.choose (fun (i, c) -> if i % toSkip = 0 then Some c else None)
|
||||
|> Array.map (fun (r, g, b) ->
|
||||
let red = 256.0f * r
|
||||
let green = 256.0f * g
|
||||
let blue = 256.0f * b
|
||||
html $"""
|
||||
<div class="color-container" >
|
||||
<div
|
||||
class="color-box"
|
||||
style="background-color: rgb({red}, {green}, {blue});"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
""")
|
||||
let colorSetOptions : obj array = [|
|
||||
{| label = "Ocean"; value = "ocean"; |}
|
||||
{| label = "Matplotlib"; value = "color16"; |}
|
||||
|]
|
||||
|
||||
let selectedCM, setSelectedCM =
|
||||
color.ColorMap
|
||||
|> snd
|
||||
|> function | ColorMap.Ocean _ -> "ocean" | ColorMap.Color16 _ -> "color16" | _ -> "custom"
|
||||
|> Hook.useState
|
||||
let handleChangeMap (x: ColorMap) =
|
||||
dispatch (SetCM(selectedColorMode, x))
|
||||
|
||||
let colorMapRadios =
|
||||
match selectedCM with
|
||||
| "ocean" ->
|
||||
Ocean.colors.Keys
|
||||
|> Seq.map ColorMap.Ocean
|
||||
|> Seq.map (fun cmap ->
|
||||
html $"""
|
||||
<sp-radio style="padding-left: 5px; width: 125px" value="{cmap}" @change={Ev(setCm cmap)}>
|
||||
<div class="color-palette-sample">
|
||||
<div style="width: 100px">
|
||||
{cmap.ToLabel()}
|
||||
</div>
|
||||
{colorSamples cmap}
|
||||
</div>
|
||||
</sp-radio>
|
||||
""")
|
||||
| "color16" ->
|
||||
Color16.colors.Keys
|
||||
|> Seq.map ColorMap.Color16
|
||||
|> Seq.map (fun cmap ->
|
||||
html $"""
|
||||
<sp-radio style="padding-left: 5px; width: 125px" value="{cmap}" @change={Ev(setCm cmap)}>
|
||||
<div class="color-palette-sample">
|
||||
<div style="width: 100px">
|
||||
{cmap.ToLabel()}
|
||||
</div>
|
||||
{colorSamples cmap}
|
||||
</div>
|
||||
</sp-radio>
|
||||
""")
|
||||
| _ -> failwith "not implemented"
|
||||
let handleChangeSelect ev (data: obj) =
|
||||
let value: string = data?value
|
||||
console.debug("[Colors] Change color set: %s", value)
|
||||
let newColorSet =
|
||||
match value with
|
||||
| "ocean" -> ColorMap.Ocean "diff"
|
||||
| "color16" -> ColorMap.Color16 "accent"
|
||||
| _ -> failwith ""
|
||||
SetCM(selectedColorMode, newColorSet) |> dispatch
|
||||
|
||||
html
|
||||
$"""
|
||||
<div
|
||||
style="
|
||||
width: 100%%;
|
||||
flex-grow: 1;
|
||||
overflow-y: scroll;
|
||||
max-height: calc(100%% - ({measuresHeight}px));
|
||||
padding-bottom: 5px;
|
||||
"
|
||||
<sp-accordion-item
|
||||
open
|
||||
label="Colormaps"
|
||||
?disabled={disabled}
|
||||
>
|
||||
<sp-accordion size="s" allow-multiple>
|
||||
<sp-accordion-item
|
||||
?disabled={disabled} ?open={true} label="Colormaps">
|
||||
<sp-picker
|
||||
id="picker-m"
|
||||
size="m"
|
||||
style="
|
||||
width: 125px;
|
||||
padding-bottom: 5px;
|
||||
"
|
||||
value="{selectedCM}"
|
||||
@change="{EvVal(setSelectedCM)}"
|
||||
>
|
||||
<sp-menu-item value="ocean">Ocean</sp-menu-item>
|
||||
<sp-menu-item value="color16">Matplotlib</sp-menu-item>
|
||||
</sp-picker>
|
||||
<sp-radio-group vertical selected="{snd color.ColorMap}">
|
||||
{colorMapRadios}
|
||||
</sp-radio-group>
|
||||
</sp-accordion-item>
|
||||
</sp-accordion>
|
||||
</div>
|
||||
<div class="flex-column gap-16">
|
||||
{FluentUI.Lit.Select(selectedColorSet, colorSetOptions, handleChangeSelect)}
|
||||
|
||||
<div style="overflow-y: auto; max-height: 512px;">
|
||||
{FluentUI.Lit.ColormapSelect (colorMap, handleChangeMap)}
|
||||
</div>
|
||||
</div>
|
||||
</sp-accordion-item>
|
||||
"""
|
||||
@@ -13,8 +13,7 @@ let private titleLabel text =
|
||||
$"""
|
||||
<div
|
||||
style="
|
||||
padding-top: 20px;
|
||||
padding-bottom: 5px;
|
||||
padding-top: 10px;
|
||||
"
|
||||
>
|
||||
<sp-field-label>
|
||||
@@ -143,7 +142,6 @@ let private styleEditor (fill, onChangeFill) (width, onChangeWidth) (dash, onCha
|
||||
</sp-field-group>
|
||||
</sp-field-group>
|
||||
</div>
|
||||
|
||||
"""
|
||||
|
||||
let private colorEditor (black, toggleBlack) (hue, onChangeHue) =
|
||||
@@ -253,7 +251,7 @@ let private deleteContourButton onClick =
|
||||
|
||||
|
||||
[<HookComponent>]
|
||||
let tzComponents downloadUrl map contours dispatch =
|
||||
let tzComponents downloadUrl map (contours: Map<int, ContourModel>) dispatch =
|
||||
let edit, setEdit =
|
||||
contours
|
||||
|> Map.map (fun _ _ -> false)
|
||||
@@ -302,24 +300,24 @@ let tzComponents downloadUrl map contours dispatch =
|
||||
|> RemoveTzContourModel
|
||||
|> dispatch
|
||||
|
||||
let tzValueContour (id, contour) =
|
||||
let tzValueContour (id, contour: ContourModel) =
|
||||
let edit = edit[id]
|
||||
let hue, saturation, lightness = contour.hsl
|
||||
let hue, saturation, lightness = contour.style.hsl
|
||||
|
||||
let black = contour.hsl |> fun (_, _, l) -> (l <= 0.f)
|
||||
let black = contour.style.hsl |> fun (_, _, l) -> (l <= 0.f)
|
||||
let toggleBlack _ =
|
||||
let l' = if black then 0.5f else 0.0f
|
||||
(id, { contour with hsl = (hue, saturation, l') })
|
||||
(id, { contour with style.hsl = (hue, saturation, l') })
|
||||
|> AddTzContourModel
|
||||
|> dispatch
|
||||
|
||||
let setCutoff v = updateContourModel id (fun c -> { c with cutoff = v })
|
||||
let setLineDash v = updateContourModel id (fun c -> { c with lineDash = v })
|
||||
let setLineWidth v = updateContourModel id (fun c -> { c with lineWidth = v })
|
||||
let setColorFill v = updateContourModel id (fun c -> { c with fill = v })
|
||||
let setColorHue v = updateContourModel id (fun c -> { c with hsl = (v, saturation, lightness) })
|
||||
let setLineDash v = updateContourModel id (fun c -> { c with style.lineDash = v })
|
||||
let setLineWidth v = updateContourModel id (fun c -> { c with style.lineWidth = v })
|
||||
let setColorFill v = updateContourModel id (fun c -> { c with style.fill = v })
|
||||
let setColorHue v = updateContourModel id (fun c -> { c with style.hsl = (v, saturation, lightness) })
|
||||
|
||||
let editStyle _ = styleEditor (contour.fill, setColorFill) (contour.lineWidth, setLineWidth) (contour.lineDash, setLineDash)
|
||||
let editStyle _ = styleEditor (contour.style.fill, setColorFill) (contour.style.lineWidth, setLineWidth) (contour.style.lineDash, setLineDash)
|
||||
let editColor _ = colorEditor (black, toggleBlack) (hue, setColorHue)
|
||||
|
||||
html
|
||||
@@ -345,7 +343,7 @@ let tzComponents downloadUrl map contours dispatch =
|
||||
"""
|
||||
|
||||
[<HookComponent>]
|
||||
let azeComponents downloadUrl map contour dispatch =
|
||||
let azeComponents downloadUrl map (contour: ContourModel) dispatch =
|
||||
let edit, setEdit = Hook.useState false
|
||||
let toggleEdit _ = setEdit (not edit)
|
||||
|
||||
@@ -353,22 +351,22 @@ let azeComponents downloadUrl map contour dispatch =
|
||||
let toggleShow _ = setShow (not show)
|
||||
Hook.useEffectOnChange(show, setLayerVisible map contour.layer)
|
||||
|
||||
let hue, saturation, lightness = contour.hsl
|
||||
let black = contour.hsl |> fun (_, _, l) -> (l <= 0.f)
|
||||
let hue, saturation, lightness = contour.style.hsl
|
||||
let black = contour.style.hsl |> fun (_, _, l) -> (l <= 0.f)
|
||||
let toggleBlack _ =
|
||||
let l' = if black then 0.5f else 0.0f
|
||||
{ contour with hsl = (hue, saturation, l') }
|
||||
{ contour with style.hsl = (hue, saturation, l') }
|
||||
|> SetAzeContourModel
|
||||
|> dispatch
|
||||
|
||||
let setCutoff v = { contour with cutoff = v } |> SetAzeContourModel |> dispatch
|
||||
let setLineDash v = { contour with lineDash = v } |> SetAzeContourModel |> dispatch
|
||||
let setLineWidth v = { contour with lineWidth = v } |> SetAzeContourModel |> dispatch
|
||||
let setColorFill v = { contour with fill = v } |> SetAzeContourModel |> dispatch
|
||||
let setColorHue v = { contour with hsl = (v, saturation, lightness) } |> SetAzeContourModel |> dispatch
|
||||
let setContourKind v = { contour with kind = v } |> SetAzeContourModel |> dispatch
|
||||
let setLineDash v = { contour with style.lineDash = v } |> SetAzeContourModel |> dispatch
|
||||
let setLineWidth v = { contour with style.lineWidth = v } |> SetAzeContourModel |> dispatch
|
||||
let setColorFill v = { contour with style.fill = v } |> SetAzeContourModel |> dispatch
|
||||
let setColorHue v = { contour with style.hsl = (v, saturation, lightness) } |> SetAzeContourModel |> dispatch
|
||||
|
||||
let editStyle _ = styleEditor (contour.fill, setColorFill) (contour.lineWidth, setLineWidth) (contour.lineDash, setLineDash)
|
||||
let editStyle _ = styleEditor (contour.style.fill, setColorFill) (contour.style.lineWidth, setLineWidth) (contour.style.lineDash, setLineDash)
|
||||
let editColor _ = colorEditor (black, toggleBlack) (hue, setColorHue)
|
||||
let pickKind _ = contourKindPicker (contour.kind, setContourKind)
|
||||
|
||||
@@ -387,7 +385,7 @@ let azeComponents downloadUrl map contour dispatch =
|
||||
"""
|
||||
|
||||
[<HookComponent>]
|
||||
let isoComponents map contours dispatch =
|
||||
let isoComponents map (contours: Map<int, ContourModel>) dispatch =
|
||||
let edit, setEdit =
|
||||
contours
|
||||
|> Map.map (fun _ _ -> false)
|
||||
@@ -434,24 +432,24 @@ let isoComponents map contours dispatch =
|
||||
|> RemoveIsoContourModel
|
||||
|> dispatch
|
||||
|
||||
let isoValueContour (id, contour) =
|
||||
let isoValueContour (id, contour: ContourModel) =
|
||||
let edit = edit[id]
|
||||
let hue, saturation, lightness = contour.hsl
|
||||
let hue, saturation, lightness = contour.style.hsl
|
||||
|
||||
let black = contour.hsl |> fun (_, _, l) -> (l <= 0.f)
|
||||
let black = contour.style.hsl |> fun (_, _, l) -> (l <= 0.f)
|
||||
let toggleBlack _ =
|
||||
let l' = if black then 0.5f else 0.0f
|
||||
(id, { contour with hsl = (hue, saturation, l') })
|
||||
(id, { contour with style.hsl = (hue, saturation, l') })
|
||||
|> AddIsoContourModel
|
||||
|> dispatch
|
||||
|
||||
let setCutoff v = updateContourModel id (fun c -> { c with cutoff = v })
|
||||
let setLineDash v = updateContourModel id (fun c -> { c with lineDash = v })
|
||||
let setLineWidth v = updateContourModel id (fun c -> { c with lineWidth = v })
|
||||
let setColorFill v = updateContourModel id (fun c -> { c with fill = v })
|
||||
let setColorHue v = updateContourModel id (fun c -> { c with hsl = (v, saturation, lightness) })
|
||||
let setLineDash v = updateContourModel id (fun c -> { c with style.lineDash = v })
|
||||
let setLineWidth v = updateContourModel id (fun c -> { c with style.lineWidth = v })
|
||||
let setColorFill v = updateContourModel id (fun c -> { c with style.fill = v })
|
||||
let setColorHue v = updateContourModel id (fun c -> { c with style.hsl = (v, saturation, lightness) })
|
||||
|
||||
let editStyle _ = styleEditor (contour.fill, setColorFill) (contour.lineWidth, setLineWidth) (contour.lineDash, setLineDash)
|
||||
let editStyle _ = styleEditor (contour.style.fill, setColorFill) (contour.style.lineWidth, setLineWidth) (contour.style.lineDash, setLineDash)
|
||||
let editColor _ = colorEditor (black, toggleBlack) (hue, setColorHue)
|
||||
|
||||
html
|
||||
@@ -474,5 +472,3 @@ let isoComponents map contours dispatch =
|
||||
</sp-field-group>
|
||||
{addContourButton addIsoValue}
|
||||
"""
|
||||
|
||||
|
||||
|
||||
@@ -1,220 +0,0 @@
|
||||
module ContourPlots
|
||||
|
||||
open Browser
|
||||
open Fable.Remoting.Client
|
||||
open Lit
|
||||
|
||||
open Atlantis.Types
|
||||
open Remoting
|
||||
open Model
|
||||
|
||||
let private nSamples = 50
|
||||
|
||||
let private makeSamplingLine n p1 p2 =
|
||||
let p1x, p1y = p1
|
||||
let p2x, p2y = p2
|
||||
let n' = float n
|
||||
let d = (p2x - p1x) ** 2.0 + (p2y - p1y) ** 2.0 |> sqrt
|
||||
let vx, vy = (p2x - p1x) / d, (p2y - p1y) / d
|
||||
let d' = d / (n' - 1.0)
|
||||
let a =
|
||||
Array.unfold
|
||||
(fun n ->
|
||||
if n < n' then
|
||||
let v' = {| lng = p1x + vx * n * d'; lat = p1y + vy * n * d' |}
|
||||
Some(v', n + 1.0)
|
||||
else
|
||||
None)
|
||||
0.0
|
||||
|> Array.map (fun x -> (x.lng, x.lat))
|
||||
a
|
||||
|
||||
let private fetchContourData (prop: Prop) t (model: Model) (coords: (float * float) []) =
|
||||
async {
|
||||
let prox = proximityApi model.dataSvc
|
||||
let fvcom = FvcomApi model.dataSvc
|
||||
let aid = model.archive.id
|
||||
let! ixs =
|
||||
match prop with
|
||||
| Prop.Temp
|
||||
| Prop.Salt -> prox.GetNearestNodes (aid, coords)
|
||||
| Prop.Speed -> prox.GetNearestElements (aid, coords)
|
||||
| _ -> async { return [||] }
|
||||
let idx = // remove duplicates (same node/element)
|
||||
ixs
|
||||
|> Array.fold
|
||||
(fun (i, a) x ->
|
||||
let a' =
|
||||
if (i + 1) >= ixs.Length then
|
||||
a
|
||||
else
|
||||
let y = ixs[i + 1]
|
||||
match x, y with
|
||||
| None, _ -> x :: a
|
||||
| Some x', Some y' when x' <> y' -> x :: a
|
||||
| _ -> a
|
||||
i + 1, a')
|
||||
(0, [])
|
||||
|> snd
|
||||
|> Array.ofList
|
||||
|> Array.rev
|
||||
let idx' =
|
||||
idx
|
||||
|> Array.fold (fun a x -> if x.IsSome then x.Value :: a else a) List.empty
|
||||
|> Array.ofList
|
||||
|> Array.rev
|
||||
let! depths =
|
||||
match prop with
|
||||
| Prop.Temp
|
||||
| Prop.Salt -> fvcom.Batch.GetNodeDepths aid idx'
|
||||
| Prop.Speed -> fvcom.Batch.GetElementDepths aid idx'
|
||||
| _ -> async { return [||] }
|
||||
let! props =
|
||||
match prop with
|
||||
| Prop.Temp -> fvcom.Batch.GetTemp aid t idx'
|
||||
| Prop.Salt -> fvcom.Batch.GetSalinity aid t idx'
|
||||
| Prop.Speed -> fvcom.Batch.GetSpeed aid t idx'
|
||||
| _ -> async { return Array.empty }
|
||||
let reify (prop: single [] []) =
|
||||
if prop.Length > 0 then
|
||||
let nil = Array.create prop[0].Length 10984f // depth > marianas trench means it's on land
|
||||
idx
|
||||
|> Array.fold (fun (i, a) x -> if x.IsSome then i + 1, prop[i] :: a else i, nil :: a) (0, List.empty)
|
||||
|> snd
|
||||
|> Array.ofList
|
||||
|> Array.rev
|
||||
else [||]
|
||||
let depths' = reify depths
|
||||
let props' = reify props
|
||||
let pdata: Plotly.PlotData list =
|
||||
depths'
|
||||
|> Array.mapi (fun i x -> {
|
||||
Plotly.PlotData.empty with
|
||||
x = Array.map float x
|
||||
y = Array.map float props'[i]
|
||||
})
|
||||
|> List.ofArray
|
||||
return pdata
|
||||
}
|
||||
|
||||
let private csvDownload (prop: Prop) (data: Plotly.PlotData list) _ =
|
||||
let dataToCsv (p0, p1) =
|
||||
Array.zip p0 p1
|
||||
|> Array.fold (fun a (x, y) -> a + $"\n{x}, {y}") "\n"
|
||||
let csv =
|
||||
"data:text/csv;charset=utf-8,"
|
||||
+ (data
|
||||
|> List.fold (fun a x -> a + dataToCsv (x.x, x.y)) "")
|
||||
let encUri = Fable.Core.JS.encodeURI csv
|
||||
let link = Dom.document.createElement "a"
|
||||
link.setAttribute ("href", encUri)
|
||||
link.setAttribute ("download", $"{prop.ToString()}-range.csv")
|
||||
Dom.document.body.appendChild link |> ignore
|
||||
link.click ()
|
||||
|
||||
[<HookComponent>]
|
||||
let contourPlot model prop l =
|
||||
let plotData, setPlotData = Hook.useState<Plotly.PlotData list> []
|
||||
let isLoading, setLoading = Hook.useState true
|
||||
|
||||
let frame =
|
||||
match prop with
|
||||
// | Prop.ConcV2 _ -> Drifters.calcDriftersFrame model model.selectedDrifter.Value model.frame
|
||||
| _ -> model.frame
|
||||
|
||||
let sampler n (coords: (float * float) []) =
|
||||
async {
|
||||
if coords.Length = 2 then
|
||||
let p0 = coords[0]
|
||||
let p1 = coords[1]
|
||||
let pts = makeSamplingLine n p0 p1
|
||||
let! d = fetchContourData prop frame model pts
|
||||
setPlotData d
|
||||
setLoading false
|
||||
}
|
||||
|
||||
Hook.useEffectOnChange (
|
||||
model.frame,
|
||||
fun _ ->
|
||||
setLoading true
|
||||
sampler nSamples model.pickLine
|
||||
|> Async.StartImmediate
|
||||
)
|
||||
|
||||
let info: Plotly.PlotInfo = {
|
||||
title = prop.ToLabel()
|
||||
xlegend = "m"
|
||||
ylegend = "depth"
|
||||
}
|
||||
|
||||
let colorscale =
|
||||
match prop with
|
||||
| Prop.Temp -> "RdBu"
|
||||
| Prop.Salt -> "Viridis"
|
||||
| Prop.Speed -> "RdBu"
|
||||
| _ -> ""
|
||||
|
||||
console.log $"contour {id} with #{plotData.Length} pts over {l} m"
|
||||
if isLoading then
|
||||
Plotly.spinner2
|
||||
else
|
||||
Plotly.displayContourPlot colorscale info l plotData
|
||||
|
||||
let downloadButton model =
|
||||
if model.pickLine.Length = 2 then
|
||||
let lng0, lat0 = Utils.toWgs84' model.pickLine[0] |> fun (x, y) -> $"%.3f{x}", $"%.3f{y}"
|
||||
let lng0', lat0' = model.pickLine[0] |> fun (x, y) -> $"%.3f{x}", $"%.3f{y}"
|
||||
let lng1', lat1' = model.pickLine[1] |> fun (x, y) -> $"%.3f{x}", $"%.3f{y}"
|
||||
let zipName = $"contour-{lng0},{lat0}.zip"
|
||||
let frame = model.frame
|
||||
let aid = model.archive.id
|
||||
let n = 1
|
||||
|
||||
html $"""
|
||||
<a href="{model.dataSvc}/download/contour/{aid}/{lng0'},{lat0'}/{lng1'},{lat1'}/{frame},{n}/{zipName}">
|
||||
<sp-action-button static="primary"> Download </sp-action-button>
|
||||
</a>
|
||||
"""
|
||||
else
|
||||
html $"""<sp-action-button ?disabled={true} static="primary"> Download </sp-action-button>"""
|
||||
|
||||
// TODO: This does not need to be a component, and the caller should maybe decide whether it should be rendered
|
||||
[<HookComponent>]
|
||||
let contourPlots dispatch model =
|
||||
let isOpen, setIsOpen = Hook.useState false
|
||||
|
||||
Hook.useEffectOnChange (
|
||||
model.pickLine,
|
||||
fun coords ->
|
||||
if coords.Length = 2 then
|
||||
setIsOpen true
|
||||
else
|
||||
setIsOpen false
|
||||
)
|
||||
|
||||
let handleClose _ =
|
||||
setIsOpen false
|
||||
ProbeLine Array.empty |> dispatch
|
||||
|
||||
Hook.useEffectOnce (fun _ ->
|
||||
window.addEventListener ("close", handleClose)
|
||||
|
||||
Hook.createDisposable (fun () ->
|
||||
window.removeEventListener("close", handleClose)))
|
||||
|
||||
if not isOpen || model.pickLine.Length <> 2 then
|
||||
Lit.nothing
|
||||
else
|
||||
let l =
|
||||
[|
|
||||
[| fst model.pickLine[0]; snd model.pickLine[0] |]
|
||||
[| fst model.pickLine[1]; snd model.pickLine[1] |]
|
||||
|]
|
||||
let line = Fable.OpenLayers.Geometry.lineString l //Fable.OpenLayers.GeometryLayout.XY
|
||||
let length = Fable.OpenLayers.Sphere.getLength line
|
||||
|
||||
html $"""
|
||||
<div class="plot-half-box">{contourPlot model Prop.Temp length}</div>
|
||||
<div class="plot-half-box">{contourPlot model Prop.Salt length}</div>
|
||||
<div class="plot-half-box">{contourPlot model Prop.Speed length}</div>
|
||||
"""
|
||||
585
src/Atlantis/src/Client/Mapster/DataExtraction.fs
Normal file
585
src/Atlantis/src/Client/Mapster/DataExtraction.fs
Normal file
@@ -0,0 +1,585 @@
|
||||
module DataExtraction
|
||||
|
||||
open System
|
||||
open Browser
|
||||
open Fable.Core
|
||||
open Fable.Core.JsInterop
|
||||
open Fable.OpenLayers
|
||||
open Lit
|
||||
open Lit.Elmish
|
||||
open Maps
|
||||
open Remoting
|
||||
|
||||
open Atlantis.Types
|
||||
open Atlantis.Shared.Notification
|
||||
open Utils
|
||||
open Hipster.Job
|
||||
open Model
|
||||
open Layers
|
||||
|
||||
//
|
||||
// === Elmish ===
|
||||
//
|
||||
|
||||
type SiteIdx = int
|
||||
|
||||
type private XtractMsg =
|
||||
| SetExtractionSite of (float * float) option
|
||||
| SetData of XtractData
|
||||
| SetStarted of bool * int option
|
||||
| ResetModel of XtractModel
|
||||
| Noop of unit
|
||||
|
||||
let statusMessage (job: JobInfo) =
|
||||
match job.status with
|
||||
| JobStatus.New -> Note.info "New extraction"
|
||||
| JobStatus.Waiting -> Note.info "Waiting..."
|
||||
| JobStatus.Running -> Note.info "Running..."
|
||||
| JobStatus.Completed -> Note.success "Extraction finished"
|
||||
| JobStatus.Unknown -> Note.warn "Job status is unknown"
|
||||
| _ (*Failed*) -> Note.error "Extraction failed"
|
||||
|
||||
let private update (msg: XtractMsg) (model: XtractModel) =
|
||||
match msg with
|
||||
| SetExtractionSite pos ->
|
||||
console.debug ("[DataExtraction] SetExtractionSite msg:", pos)
|
||||
{ model with position = pos }, Elmish.Cmd.none
|
||||
| SetData s ->
|
||||
console.debug ("[DataExtraction] SetData msg:", s)
|
||||
{ model with data = s }, Elmish.Cmd.none
|
||||
| XtractMsg.SetStarted (started, jobIdOpt) -> { model with start = started, jobIdOpt }, Elmish.Cmd.none
|
||||
| XtractMsg.ResetModel m -> { m with data.name = model.data.name }, Elmish.Cmd.none
|
||||
| XtractMsg.Noop () -> model, Elmish.Cmd.none
|
||||
|
||||
//
|
||||
// === Views and components ===
|
||||
//
|
||||
|
||||
[<HookComponent>]
|
||||
let placingToggleButton (disabled: bool) (map: OlMap) (onPlace: Coordinate -> unit) =
|
||||
let placing, setPlacing = Hook.useState false
|
||||
let mapClickKey = Hook.useRef<Event.EventsKey> ()
|
||||
|
||||
let releaseClickHandler (e: Event.MapBrowserEvent) =
|
||||
onPlace e.coordinate
|
||||
setPlacing false
|
||||
|
||||
Hook.useEffectOnce (fun () ->
|
||||
Hook.createDisposable (fun () ->
|
||||
let elem = map.getTargetElement ()
|
||||
elem?style?cursor <- ""
|
||||
mapClickKey.contents |> Option.iter Observable.unByKey
|
||||
)
|
||||
)
|
||||
|
||||
Hook.useEffectOnChange (placing, crossHairSelect map mapClickKey releaseClickHandler)
|
||||
|
||||
html
|
||||
$"""
|
||||
<sp-action-button
|
||||
style="width: 300px"
|
||||
?disabled="{disabled}"
|
||||
?selected={placing}
|
||||
@click={Ev (fun _ -> setPlacing (not placing))}
|
||||
>
|
||||
<sp-icon-target slot="icon"></sp-icon-target>
|
||||
Add extraction point
|
||||
</sp-action-button>
|
||||
"""
|
||||
|
||||
/// <summary>
|
||||
/// Update the extraction site marker on the map
|
||||
/// </summary>
|
||||
let updateExtractionSite (posOpt: (float * float) option, map) =
|
||||
map
|
||||
|> updateBaseLayer
|
||||
MapLayer.SelectedReleaseGroup
|
||||
(fun baseLayer ->
|
||||
let layer = baseLayer :?> VectorLayer
|
||||
let source = layer.getSource () :?> VectorSource
|
||||
source.clear ()
|
||||
|
||||
match posOpt with
|
||||
| Some pos ->
|
||||
let p' = pos |> posToCoord
|
||||
let point =
|
||||
Geometry.point [ geometry.coordinates p'; geometry.layout GeometryLayout.XY ]
|
||||
let feature = Feature.feature [ feature.geometryOrProperties point ]
|
||||
source.addFeature (feature)
|
||||
| _ -> ()
|
||||
)
|
||||
|
||||
[<HookComponent>]
|
||||
let private extractionSiteControls (dispatch': XtractMsg -> unit) (xmodel': XtractModel) =
|
||||
let tryFence (pos: float * float) : (float * float) option =
|
||||
match xmodel'.fence with
|
||||
| None -> Some pos
|
||||
| Some pts ->
|
||||
let radius = sessionStorage["fence_radius"] |> float
|
||||
let coords' = toEpsg3857 pts[0]
|
||||
let radius' = radius * mercatorScaleFactor (snd pts[0])
|
||||
let circle = Geometry.circle coords' radius' GeometryLayout.XY
|
||||
if circle.intersectsCoordinate (pos |> posToCoord) then
|
||||
Some pos
|
||||
else
|
||||
console.error ("[DataExtraction] Trying to place extraction point outside of fencing radius")
|
||||
None
|
||||
|
||||
let handleMapPlaceExtraction (coords: Coordinate) =
|
||||
console.debug ($"[DataExtraction] Click add site: %s{coords.ToString ()}")
|
||||
coordToPos coords |> tryFence |> SetExtractionSite |> dispatch'
|
||||
|
||||
let setPosition (pos: float * float) : unit =
|
||||
Some pos |> SetExtractionSite |> dispatch'
|
||||
|
||||
let selectedPos = xmodel'.position |> Option.defaultValue (0.0, 0.0) |> toWgs84'
|
||||
|
||||
let deleteSite (_: Browser.Types.Event) = None |> SetExtractionSite |> dispatch'
|
||||
|
||||
let latitudeBox =
|
||||
let latitude = snd selectedPos
|
||||
let disabled = xmodel'.position.IsNone
|
||||
html
|
||||
$"""
|
||||
<sp-field-group vertical>
|
||||
<sp-field-label for="latitude">
|
||||
Latitude
|
||||
</sp-field-label>
|
||||
<sp-number-field
|
||||
id="latitude"
|
||||
style="width: 140px"
|
||||
size="m"
|
||||
step="0.000001"
|
||||
format-options="{formatDigits 6 6}"
|
||||
value={latitude}
|
||||
?disabled="{disabled}"
|
||||
@change={EvVal (
|
||||
unbox<float>
|
||||
>> fun v ->
|
||||
let newPos = toEpsg3857' (fst selectedPos, v)
|
||||
setPosition newPos
|
||||
)}
|
||||
>
|
||||
</sp-number-field>
|
||||
</sp-field-group>
|
||||
"""
|
||||
|
||||
let longitudeBox =
|
||||
let longitude = fst selectedPos
|
||||
let disabled = xmodel'.position.IsNone
|
||||
html
|
||||
$"""
|
||||
<sp-field-group vertical>
|
||||
<sp-field-label for="longitude">
|
||||
Longitude
|
||||
</sp-field-label>
|
||||
<sp-number-field
|
||||
id="longitude"
|
||||
style="width: 140px"
|
||||
size="m"
|
||||
step="0.000001"
|
||||
format-options="{formatDigits 6 6}"
|
||||
value={longitude}
|
||||
?disabled="{disabled}"
|
||||
@change={EvVal (
|
||||
unbox<float>
|
||||
>> fun v ->
|
||||
let newPos = toEpsg3857' (v, snd selectedPos)
|
||||
setPosition newPos
|
||||
)}
|
||||
></sp-number-field>
|
||||
</sp-field-group>
|
||||
"""
|
||||
|
||||
let siteDisplay =
|
||||
match xmodel'.position with
|
||||
| Some _ ->
|
||||
let lon, lat = selectedPos
|
||||
html
|
||||
$"""
|
||||
<div style="padding-top: 10px; padding-bottom: 10px">
|
||||
<sp-divider style="width: 300px"></sp-divider>
|
||||
</div>
|
||||
<div style="padding-top: 5px">
|
||||
<sp-field-label>Extraction Point</sp-field-label>
|
||||
<div style="display: flex; gap: 8px; padding-top: 5px">
|
||||
<sp-action-button style="width: 110px">
|
||||
{lat |> sprintf "%.6f"}
|
||||
</sp-action-button>
|
||||
<sp-action-button style="width: 110px">
|
||||
{lon |> sprintf "%.6f"}
|
||||
</sp-action-button>
|
||||
<sp-action-button
|
||||
style="width: 35px"
|
||||
@click={Ev deleteSite}
|
||||
>
|
||||
<sp-icon-delete slot="icon"></sp-icon-delete>
|
||||
<sp-tooltip placement="right" self-managed>Remove point</sp-tooltip>
|
||||
</sp-action-button>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
| None -> Lit.nothing
|
||||
|
||||
html
|
||||
$"""
|
||||
<sp-field-group vertical>
|
||||
<sp-field-group horizontal style="padding-top: 5px; padding-bottom: 20px">
|
||||
{latitudeBox}
|
||||
{longitudeBox}
|
||||
</sp-field-group>
|
||||
</sp-field-group>
|
||||
|
||||
<div style="padding-top: 10px; padding-bottom: 5px">
|
||||
{placingToggleButton xmodel'.position.IsSome xmodel'.openLayersMap handleMapPlaceExtraction}
|
||||
</div>
|
||||
|
||||
{siteDisplay}
|
||||
"""
|
||||
|
||||
[<HookComponent>]
|
||||
let private extractionControls (dispatch': XtractMsg -> unit) (xmodel': XtractModel) =
|
||||
html
|
||||
$"""
|
||||
<sp-accordion-item class="extraction-site" ?open={true} label="Extraction point">
|
||||
{extractionSiteControls dispatch' xmodel'}
|
||||
</sp-accordion-item>
|
||||
"""
|
||||
|
||||
[<HookComponent>]
|
||||
let controls xtractType (dispatch: Msg -> unit) (model: Model) =
|
||||
let archive = model.archive
|
||||
let xtractModelOpt = model.xtractModelOpt
|
||||
let map = model.map
|
||||
let currentFrame = model.frame
|
||||
let archiveStartUTC = archive.startTime.ToUniversalTime ()
|
||||
let archiveEndT =
|
||||
archiveStartUTC.AddSeconds (archive.frames * archive.saveFreq |> float)
|
||||
let archiveStartT =
|
||||
archiveStartUTC.AddSeconds (currentFrame * archive.saveFreq |> float)
|
||||
let submitted, setSubmitted = Hook.useState false
|
||||
|
||||
let createNewModel () : XtractModel =
|
||||
let data =
|
||||
match xtractModelOpt with
|
||||
| Some existing -> { existing.data with fvcom = archive.id }
|
||||
| None -> {
|
||||
XtractData.empty with
|
||||
fvcom = archive.id
|
||||
start = archiveStartT
|
||||
stop = archiveStartT.AddDays 2.0
|
||||
}
|
||||
{
|
||||
fence = archive.polygon
|
||||
start = false, None
|
||||
kind = xtractType
|
||||
data = data
|
||||
position = None
|
||||
openLayersMap = map
|
||||
}
|
||||
|
||||
let xmodel', dispatch' =
|
||||
Hook.useElmish (
|
||||
(fun () ->
|
||||
let xmodel' =
|
||||
match xtractModelOpt with
|
||||
| Some existingModel -> existingModel
|
||||
| None -> createNewModel ()
|
||||
xmodel', Elmish.Cmd.none
|
||||
),
|
||||
update
|
||||
)
|
||||
|
||||
let modelRef = Hook.useRef (Some xmodel')
|
||||
|
||||
Hook.useEffectOnChange (
|
||||
xmodel',
|
||||
fun newModel ->
|
||||
modelRef.contents <- Some newModel
|
||||
SetXtractModel (Some newModel) |> dispatch
|
||||
)
|
||||
|
||||
|
||||
let setStartDateTime (dt: DateTime) =
|
||||
// Set time to midday (12:00)
|
||||
let startDate = DateTime (dt.Year, dt.Month, dt.Day, 12, 0, 0)
|
||||
let stopDate = xmodel'.data.stop
|
||||
let newStop = if startDate >= stopDate then startDate.AddDays(1.0) else stopDate
|
||||
console.debug ("[DataExtraction] setStartDateTime:", startDate, "stop:", newStop)
|
||||
SetData { xmodel'.data with start = startDate; stop = newStop } |> dispatch'
|
||||
|
||||
let setStopDateTime (dt: DateTime) =
|
||||
let endDate = DateTime (dt.Year, dt.Month, dt.Day, 12, 0, 0)
|
||||
let startDate = xmodel'.data.start
|
||||
let newStart = if endDate <= startDate then endDate.AddDays(-1.0) else startDate
|
||||
console.debug ("[DataExtraction] setStopDateTime:", endDate, "start:", newStart)
|
||||
SetData { xmodel'.data with stop = endDate; start = newStart } |> dispatch'
|
||||
|
||||
let setName (s: string) =
|
||||
let currentModel = modelRef.contents |> Option.defaultValue xmodel'
|
||||
SetData { currentModel.data with name = s } |> dispatch'
|
||||
|
||||
let minStartDate = archiveStartUTC
|
||||
let maxStartDate = archiveEndT
|
||||
let minEndDate = archiveStartUTC
|
||||
// maxEndDate is one year after the selected start date
|
||||
let maxEndDate =
|
||||
let startDate = xmodel'.data.start
|
||||
startDate.AddYears(1)
|
||||
|
||||
let metaControls =
|
||||
html
|
||||
$"""
|
||||
<div style="margin: 10px; padding-bottom: 5px;">
|
||||
<sp-field-label>
|
||||
Name (required)
|
||||
</sp-field-label>
|
||||
<sp-field-group horizontal id="vw" style="padding-bottom: 5px">
|
||||
<sp-textfield
|
||||
id="xtract-name"
|
||||
label="Name"
|
||||
placeholder="extraction-name"
|
||||
required="true"
|
||||
value="{xmodel'.data.name}"
|
||||
@change={EvVal (setName)}
|
||||
style="width: 300px"
|
||||
></sp-textfield>
|
||||
</sp-field-group>
|
||||
</div>
|
||||
|
||||
<div class="grow m-8">
|
||||
<div style="padding-bottom: 5px; padding-top: 20px">
|
||||
<sp-field-group style="flex-grow: 1;" vertical>
|
||||
<sp-field-label size="s" for="vertical">
|
||||
Start date
|
||||
</sp-field-label>
|
||||
<sp-field-group horizontal style="padding-bottom: 10px;">
|
||||
{FluentUI.Lit.DatePicker (false, xmodel'.data.start, setStartDateTime, minStartDate, maxStartDate)}
|
||||
</sp-field-group>
|
||||
</sp-field-group>
|
||||
|
||||
<sp-field-group style="flex-grow: 1;" vertical>
|
||||
<sp-field-label size="s" for="vertical">
|
||||
End date
|
||||
</sp-field-label>
|
||||
<sp-field-group horizontal style="padding-bottom: 10px;">
|
||||
{FluentUI.Lit.DatePicker (false, xmodel'.data.stop, setStopDateTime, minEndDate, maxEndDate)}
|
||||
</sp-field-group>
|
||||
</sp-field-group>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
|
||||
let submit _ =
|
||||
if archive.id <> Guid.Empty && xmodel'.position.IsSome then
|
||||
let data' = xmodel'.data
|
||||
let pos' = xmodel'.position.Value |> toWgs84'
|
||||
let id = Guid.NewGuid ()
|
||||
|
||||
let payload: XtractPayload = {
|
||||
id = id
|
||||
name = data'.name
|
||||
archiveId = archive.id
|
||||
positions = { Lat = snd pos'; Long = fst pos' }
|
||||
start = data'.start
|
||||
stop = data'.stop
|
||||
basePath = "" // Set by server
|
||||
caseName = "" // Set by server
|
||||
projection = "" // Set by server
|
||||
files = [||] // Set by server
|
||||
}
|
||||
|
||||
console.log $"\n-------------- Data Extraction input ----------------"
|
||||
console.log $"Name: {data'.name}"
|
||||
console.log $"%A{payload}"
|
||||
|
||||
let api = xtractApi ()
|
||||
async {
|
||||
let! job = api.startXtract payload None
|
||||
setSubmitted true
|
||||
|
||||
do
|
||||
Lib.Umami.track (
|
||||
"mapster-submit-extraction",
|
||||
{|
|
||||
archiveId = string archive.id
|
||||
archiveName = string archive.name
|
||||
status = if job.IsSome then "success" else "failed"
|
||||
|}
|
||||
)
|
||||
|
||||
match job with
|
||||
| None ->
|
||||
Note.failure "[DataExtraction] Job submission failed"
|
||||
|> SetNotification
|
||||
|> dispatch
|
||||
|
||||
XtractMsg.SetStarted (true, Some 0) |> dispatch'
|
||||
| Some j ->
|
||||
j |> statusMessage |> SetNotification |> dispatch
|
||||
|
||||
if
|
||||
j.status = JobStatus.Waiting
|
||||
|| j.status = JobStatus.Running
|
||||
|| j.status = JobStatus.Completed
|
||||
|| j.status = JobStatus.New
|
||||
then
|
||||
XtractMsg.SetStarted (true, Some j.jobId) |> dispatch'
|
||||
|
||||
let msg: Petimeter.Inbox.InboxItem = {
|
||||
id = j.archiveId
|
||||
content =
|
||||
Thoth.Json.Encode.Auto.toString<JobMessage> (
|
||||
{
|
||||
aid = j.archiveId
|
||||
job = j.jobId
|
||||
name = j.name
|
||||
status = j.status
|
||||
}
|
||||
: JobMessage
|
||||
)
|
||||
unread = true
|
||||
type' = Petimeter.Inbox.MessageType.Xtract
|
||||
created = DateTime.Now
|
||||
}
|
||||
|
||||
msg
|
||||
|> Atlantis.Shared.Hub.InboxMsg.Post
|
||||
|> Atlantis.Shared.Hub.Action.Inbox
|
||||
|> HubMsg
|
||||
|> dispatch
|
||||
}
|
||||
|> Async.StartImmediate
|
||||
|
||||
let reset (_: Browser.Types.Event) =
|
||||
console.debug ("[DataExtraction] Reset extraction")
|
||||
clearFeatures map MapLayer.SelectedReleaseGroup
|
||||
|
||||
createNewModel () |> XtractMsg.ResetModel |> dispatch'
|
||||
|
||||
let cancel (_: Browser.Types.Event) =
|
||||
console.debug ("[DataExtraction] Cancel extraction")
|
||||
clearFeatures map MapLayer.SelectedReleaseGroup
|
||||
|
||||
modelRef.contents <- None
|
||||
SetXtractModel None |> dispatch
|
||||
SetMode Mode.Moot |> dispatch
|
||||
|
||||
let submitButtons =
|
||||
let noName = String.IsNullOrWhiteSpace (xmodel'.data.name)
|
||||
let noSite = xmodel'.position.IsNone
|
||||
|
||||
html
|
||||
$"""
|
||||
<div
|
||||
id="extraction-submit-controls"
|
||||
style="
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
margin: 2px;
|
||||
padding: 5px;
|
||||
"
|
||||
>
|
||||
<sp-action-group
|
||||
horizontal
|
||||
compact
|
||||
size="m"
|
||||
>
|
||||
<sp-action-button
|
||||
static="primary"
|
||||
style="width: 100px;"
|
||||
?disabled={noName || noSite || submitted}
|
||||
@click={Ev (submit)}
|
||||
>
|
||||
Submit
|
||||
</sp-action-button>
|
||||
<sp-action-button
|
||||
static="primary"
|
||||
style="width: 100px;"
|
||||
@click={Ev (reset)}
|
||||
>
|
||||
Reset
|
||||
</sp-action-button>
|
||||
<sp-action-button
|
||||
static="primary"
|
||||
style="width: 100px;"
|
||||
@click={Ev (cancel)}
|
||||
>
|
||||
Cancel
|
||||
</sp-action-button>
|
||||
</sp-action-group>
|
||||
</div>
|
||||
"""
|
||||
|
||||
Hook.useEffectOnce (fun () ->
|
||||
console.debug ("[DataExtraction] === mounting ===")
|
||||
|
||||
Hook.createDisposable (fun () ->
|
||||
console.log "[DataExtraction] Leaving extraction controls"
|
||||
modelRef.contents |> SetXtractModel |> dispatch
|
||||
)
|
||||
)
|
||||
|
||||
Hook.useEffectOnChange (
|
||||
xmodel',
|
||||
fun newModel ->
|
||||
console.debug ("[DataExtraction] Model changed", newModel)
|
||||
modelRef.contents <- Some newModel
|
||||
)
|
||||
|
||||
Hook.useEffectOnChange (
|
||||
xmodel'.position,
|
||||
fun posOpt ->
|
||||
console.debug ("[DataExtraction] Position changed", posOpt)
|
||||
updateExtractionSite (posOpt, xmodel'.openLayersMap) |> ignore
|
||||
)
|
||||
|
||||
Hook.useEffectOnChange (
|
||||
xmodel'.start,
|
||||
fun (updatedStarted, _) ->
|
||||
if updatedStarted then
|
||||
console.log ("[DataExtraction] Extraction started: resetting")
|
||||
do clearFeatures map MapLayer.SelectedReleaseGroup
|
||||
Msg.SetSideNavMode OceanControls |> dispatch
|
||||
modelRef.contents <- None
|
||||
)
|
||||
|
||||
let measuresHeight =
|
||||
tryGetElemRect "measures-controls"
|
||||
|> Option.map _.height
|
||||
|> Option.defaultValue 80
|
||||
|
||||
html
|
||||
$"""
|
||||
<div
|
||||
style="
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
height: 95%%;
|
||||
"
|
||||
>
|
||||
<h3>Data Extraction</h3>
|
||||
<div
|
||||
style="
|
||||
width: 100%%;
|
||||
max-height: calc(100%% - ({measuresHeight}px));
|
||||
flex-grow: 1;
|
||||
overflow-y: scroll;
|
||||
border-bottom: 1px solid #eaeaea;
|
||||
border-top: 1px solid #eaeaea;
|
||||
"
|
||||
>
|
||||
<sp-accordion
|
||||
allow-multiple
|
||||
size="s"
|
||||
density="spacious"
|
||||
>
|
||||
{metaControls}
|
||||
{extractionControls dispatch' xmodel'}
|
||||
</sp-accordion>
|
||||
</div>
|
||||
</div>
|
||||
{submitButtons}
|
||||
"""
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,5 @@
|
||||
module DriftersPlots
|
||||
|
||||
#nowarn "57"
|
||||
|
||||
open Browser
|
||||
open Lit
|
||||
open Remoting
|
||||
@@ -9,197 +7,98 @@ open Remoting
|
||||
open Archmaester.Dto
|
||||
open Atlantis.Types
|
||||
open Drifters.ApiTypes
|
||||
open Sorcerer.Types
|
||||
open Model
|
||||
open Plotly
|
||||
open PropertyPlots
|
||||
|
||||
let private hmr = HMR.createToken ()
|
||||
|
||||
let private listFromBoolMap m =
|
||||
let private arrayFromBoolMap m =
|
||||
m
|
||||
|> Map.filter (fun _ value -> value)
|
||||
|> Map.keys
|
||||
|> Seq.toList
|
||||
|
||||
// [<HookComponent>]
|
||||
// let private azeSeriesPlot (model: Model) =
|
||||
// let plotInfo, setPlotInfo = Hook.useState PlotInfo.empty
|
||||
// let plotData, setPlotData = Hook.useState<LineData list> []
|
||||
// let isLoading, setLoading = Hook.useState false
|
||||
//
|
||||
// let driftersApi = DriftersApi model.dataSvc
|
||||
// let prop = model.glLayers[model.activeLayer].PropType
|
||||
// let id = $"{string prop}-aze"
|
||||
//
|
||||
// console.log $"ID : {id}"
|
||||
//
|
||||
// let fetchData idx =
|
||||
// match model.selectedPostdrift with
|
||||
// | None ->
|
||||
// async.Return [||]
|
||||
// | Some d ->
|
||||
// let did = d.Archive.archiveId
|
||||
// let frames =
|
||||
// let f0, f1, _ = getFrameRange model.probePoint.timeUnit model model.archive.saveFreq
|
||||
// let df0 = Drifters.calcDriftersFrame model.archive d f0
|
||||
// let df1 = Drifters.calcDriftersFrame model.archive d f1
|
||||
// df0, df1
|
||||
// async {
|
||||
// match prop, d.SimFormat with
|
||||
// // | Prop.Sed field, Field2D -> return! driftersApi.Sedimentation.GetAzeSeries did frames field TPM model.azeCutoff
|
||||
// | Prop.Sed field, Field2D -> return! driftersApi.Sedimentation.GetSeries did frames field TPM 100
|
||||
// | _ -> return [||]
|
||||
// }
|
||||
//
|
||||
// let freq =
|
||||
// model.selectedPostdrift
|
||||
// |> Option.map (fun d -> d.Archive.freq)
|
||||
// |> Option.defaultValue 0 // TODO: check this!
|
||||
//
|
||||
// let fetchSeries timeUnit idx =
|
||||
// async {
|
||||
// let f0, f1, _ = getFrameRange timeUnit model freq
|
||||
// let! data = fetchData idx
|
||||
// let info: PlotInfo = {
|
||||
// title = string $"{prop.ToLabel()} - {string model.probePoint.timeUnit}"
|
||||
// xlegend = ""
|
||||
// ylegend = prop.unit
|
||||
// }
|
||||
// setPlotInfo info
|
||||
// match prop with
|
||||
// | Prop.WC _
|
||||
// | Prop.DW _
|
||||
// | Prop.Sed _
|
||||
// | Prop.Conc _ ->
|
||||
// data
|
||||
// |> Array.Parallel.map (fun x -> model.scalingFactor * x)
|
||||
// |> toTimeSeries2D model.time freq (f0, f1, 1)
|
||||
// |> fun p -> setPlotData [ p ]
|
||||
// | _ -> ()
|
||||
// }
|
||||
//
|
||||
// let reload timeUnit idx =
|
||||
// async {
|
||||
// setLoading true
|
||||
// do! fetchSeries timeUnit idx
|
||||
// setLoading false
|
||||
// }
|
||||
// |> Async.StartImmediate
|
||||
//
|
||||
// Hook.useEffectOnChange (
|
||||
// (model.frame, model.probePoint.idx, model.probePoint.timeUnit),
|
||||
// fun (_, idxOpt, timeUnit) ->
|
||||
// idxOpt
|
||||
// |> Option.iter (fun (nodeIdx, _) ->
|
||||
// reload timeUnit nodeIdx)
|
||||
// )
|
||||
//
|
||||
// html $"""
|
||||
// <div
|
||||
// class="full-width-center invisible"
|
||||
// ?invisible={not isLoading}
|
||||
// >
|
||||
// <sp-progress-circle size="m" label="loading-drifters-plot" indeterminate ></sp-progress-circle>
|
||||
// </div>
|
||||
// <div
|
||||
// class="invisible"
|
||||
// ?invisible="{isLoading}"
|
||||
// style="width: 100%%"
|
||||
// >
|
||||
// {linePlot plotInfo plotData id}
|
||||
// </div>
|
||||
// """
|
||||
|> Seq.toArray
|
||||
|
||||
[<HookComponent>]
|
||||
let private seriesPlot (model: Model) (postdrift: SimArchive) =
|
||||
let dataExtracted, setDataExtracted = Hook.useState false
|
||||
let extractedData, setExtractedData = Hook.useState ""
|
||||
let plotInfo, setPlotInfo = Hook.useState PlotInfo.empty
|
||||
let plotData, setPlotData = Hook.useState<PlotData []> [||]
|
||||
let plotControls model dispatch postdrift =
|
||||
Hook.useHmr hmr
|
||||
|
||||
let setTimeUnit newUnit =
|
||||
{ model.probePoint with timeUnit = newUnit }
|
||||
|> SetProbing
|
||||
|> dispatch
|
||||
|
||||
html
|
||||
$"""
|
||||
<div>
|
||||
<div style="display: flex; flex-flow: column;">
|
||||
<sp-field-label>Time series</sp-field-label>
|
||||
<div class="timeseries-box2">
|
||||
<sp-radio-group selected="{string model.probePoint.timeUnit}" vertical>
|
||||
<sp-radio
|
||||
value="day"
|
||||
@change={Ev(fun _ -> setTimeUnit TimeUnit.Day)}
|
||||
>
|
||||
Day
|
||||
</sp-radio>
|
||||
|
||||
<sp-radio
|
||||
value="week"
|
||||
@change={Ev(fun _ -> setTimeUnit TimeUnit.Week)}
|
||||
>
|
||||
Week
|
||||
</sp-radio>
|
||||
|
||||
<sp-radio
|
||||
value="month"
|
||||
@change={Ev(fun _ -> setTimeUnit TimeUnit.Month)}
|
||||
>
|
||||
Month
|
||||
</sp-radio>
|
||||
|
||||
<sp-radio
|
||||
value="quarter"
|
||||
@change={Ev(fun _ -> setTimeUnit TimeUnit.Quarter)}
|
||||
>
|
||||
Quarter
|
||||
</sp-radio>
|
||||
<sp-radio
|
||||
value="year"
|
||||
@change={Ev(fun _ -> setTimeUnit TimeUnit.Year)}
|
||||
>
|
||||
Year
|
||||
</sp-radio>
|
||||
</sp-radio-group>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
|
||||
[<HookComponent>]
|
||||
let drifterPlots model postdrift =
|
||||
Hook.useHmr hmr
|
||||
|
||||
let isLoading, setLoading = Hook.useState false
|
||||
let plotData, setPlotData = Hook.useState<Plotly.PlotData array> [||]
|
||||
let showExtractedData, setDataExtracted = Hook.useState false
|
||||
let extractedData, setExtractedData = Hook.useState ""
|
||||
|
||||
let driftersApi = DriftersApi model.dataSvc
|
||||
let viewProp = model.glLayers[model.activeLayer]
|
||||
let viewProp = model.glLayers[Conc]
|
||||
|
||||
let fetchData idx =
|
||||
let did = postdrift.Archive.archiveId
|
||||
let field = viewProp.FieldKind
|
||||
let frames =
|
||||
let f0, f1, _ = getFrameRange model.probePoint.timeUnit model model.archive.saveFreq
|
||||
let df0 = Drifters.calcDriftersFrame model.archive postdrift f0
|
||||
let df1 = Drifters.calcDriftersFrame model.archive postdrift f1
|
||||
df0, df1
|
||||
let groups =
|
||||
model.fieldsFilter.showGroupKind
|
||||
|> listFromBoolMap
|
||||
let kinds =
|
||||
model.fieldsFilter.showParticleKind
|
||||
|> listFromBoolMap
|
||||
let layers =
|
||||
model.fieldsFilter.showDepthLayer
|
||||
|> listFromBoolMap
|
||||
async {
|
||||
match viewProp.PropType with
|
||||
| Prop.Sed sediment -> return! driftersApi.Sedimentation.GetSeries did frames field sediment idx
|
||||
| Prop.SW ->
|
||||
let! poc = driftersApi.Sedimentation.GetSeries did frames field POC idx
|
||||
return poc |> Array.Parallel.map (Postdrift.toShannonWiener >> abs >> (min 3.7f))
|
||||
| Prop.WC -> return! driftersApi.Field2D.GetWaterContactSeries did frames field groups kinds AnyState idx
|
||||
| Prop.DW -> return! driftersApi.Field3D.GetSeries did frames field groups kinds Expired layers idx
|
||||
| Prop.SedV2 -> return! driftersApi.Field2D.GetSeries did frames field groups kinds Sedimented idx
|
||||
| Prop.Conc2D -> return! driftersApi.Field2D.GetSeries did frames field groups kinds Sedimented idx
|
||||
| Prop.Conc3D -> return! driftersApi.Field3D.GetSeries did frames field groups kinds AnyState layers idx
|
||||
| _ -> return [||]
|
||||
}
|
||||
let info: Plotly.PlotInfoWithTraces = {
|
||||
title = string $"{viewProp.PropType.ToLabel()} - {string model.probePoint.timeUnit}"
|
||||
xlegend = ""
|
||||
ylegend = viewProp.PropType.unit
|
||||
autoRange = true
|
||||
range = model.probePoint.propRange
|
||||
traces = plotData |> Array.collect (Plotly.hLine None)
|
||||
}
|
||||
|
||||
let freq = postdrift.Archive.freq
|
||||
let fetchSeries timeUnit idx =
|
||||
async {
|
||||
let f0, f1, _ = getFrameRange timeUnit model freq
|
||||
let! data = fetchData idx
|
||||
let info: PlotInfo = {
|
||||
title = string $"{viewProp.PropType.ToLabel()} - {string model.probePoint.timeUnit}"
|
||||
xlegend = ""
|
||||
ylegend = viewProp.PropType.unit
|
||||
}
|
||||
setPlotInfo info
|
||||
match viewProp.PropType with
|
||||
| Prop.WC
|
||||
| Prop.DW
|
||||
| Prop.SW
|
||||
| Prop.Sed _
|
||||
| Prop.SedV2
|
||||
| Prop.Conc2D
|
||||
| Prop.Conc3D ->
|
||||
data
|
||||
|> Array.Parallel.map (fun x -> model.scalingFactor * x)
|
||||
|> toTimeSeries2D model.time freq (f0, f1, 1)
|
||||
|> fun p -> setPlotData [| p |]
|
||||
| _ -> ()
|
||||
}
|
||||
|
||||
let reload timeUnit idx =
|
||||
setDataExtracted false
|
||||
setExtractedData ""
|
||||
async {
|
||||
setLoading true
|
||||
do! fetchSeries timeUnit idx
|
||||
setLoading false
|
||||
}
|
||||
|> Async.StartImmediate
|
||||
|
||||
Hook.useEffectOnChange (
|
||||
(model.frame, model.probePoint.idx, model.probePoint.timeUnit),
|
||||
fun (_, idxOpt, timeUnit) ->
|
||||
idxOpt
|
||||
|> Option.iter (fun (nodeIdx, _) ->
|
||||
reload timeUnit nodeIdx)
|
||||
)
|
||||
|
||||
let handleClearData _ =
|
||||
let handleClearData () =
|
||||
setDataExtracted false
|
||||
setExtractedData ""
|
||||
|
||||
let handleExtractData _ =
|
||||
let handleExtractData () =
|
||||
setDataExtracted true
|
||||
plotData
|
||||
|> Array.map (fun data ->
|
||||
@@ -211,192 +110,129 @@ let private seriesPlot (model: Model) (postdrift: SimArchive) =
|
||||
|> String.concat "\n"
|
||||
|> setExtractedData
|
||||
|
||||
let extractDataButton =
|
||||
html $"""
|
||||
<div style="margin: 10px;">
|
||||
<sp-action-button
|
||||
style="width: 600px;"
|
||||
static="primary"
|
||||
@click={Ev(fun _ -> handleExtractData ())}
|
||||
>Extract data</sp-action-button>
|
||||
</div>
|
||||
"""
|
||||
let fetchData idx =
|
||||
let did = postdrift.Archive.archiveId
|
||||
let frames =
|
||||
let f0, f1, _ = Plots.Utils.getFrameRange model.frame model.archive model.probePoint.timeUnit
|
||||
let df0 = Drifters.calcDriftersFrame model.archive postdrift f0
|
||||
let df1 = Drifters.calcDriftersFrame model.archive postdrift f1
|
||||
df0, df1
|
||||
let filter = {
|
||||
field = viewProp.FieldKind
|
||||
state = AnyState
|
||||
groups = model.fieldsFilter.showGroupKind |> arrayFromBoolMap
|
||||
layers = model.fieldsFilter.showDepthLayer |> arrayFromBoolMap
|
||||
particles = model.fieldsFilter.showParticleKind |> arrayFromBoolMap
|
||||
}
|
||||
|
||||
let clearDataButton =
|
||||
html $"""
|
||||
<div style="margin: 10px;">
|
||||
<sp-action-button
|
||||
style="width: 600px;"
|
||||
static="primary"
|
||||
@click={Ev(fun _ -> handleClearData ())}
|
||||
>Clear data</sp-action-button>
|
||||
</div>
|
||||
"""
|
||||
async {
|
||||
match viewProp.PropType with
|
||||
| Prop.Sed sediment -> return! driftersApi.Sedimentation.GetSeries did frames viewProp.FieldKind sediment idx
|
||||
| Prop.SW ->
|
||||
let! poc = driftersApi.Sedimentation.GetSeries did frames viewProp.FieldKind POC idx
|
||||
return poc |> Array.Parallel.map (Postdrift.toShannonWiener >> abs >> min 3.7f)
|
||||
| Prop.WC -> return! driftersApi.WaterContact.GetSeries did frames filter idx
|
||||
| Prop.DW _ -> return! driftersApi.Field3D.GetSeries did frames { filter with state = Expired } idx
|
||||
| Prop.SedV2 -> return! driftersApi.Field2D.GetSeries did frames { filter with state = Sedimented } idx
|
||||
| Prop.Conc2D -> return! driftersApi.Field2D.GetSeries did frames { filter with state = Sedimented } idx
|
||||
| Prop.Conc3D -> return! driftersApi.Field3D.GetSeries did frames filter idx
|
||||
| _ -> return [||]
|
||||
}
|
||||
|
||||
let showExtractedData =
|
||||
html $"""
|
||||
<sp-textfield
|
||||
size="l"
|
||||
id="sample-coords"
|
||||
style="width: 600px; padding-top:10px; padding-bottom: 10px; padding-left: 10px"
|
||||
multiline
|
||||
grows
|
||||
value="{extractedData}"
|
||||
>
|
||||
</sp-textfield>
|
||||
"""
|
||||
let fetchSeries timeUnit idx =
|
||||
async {
|
||||
let freq = postdrift.Archive.freq
|
||||
let time = Utils.Archives.findFrameTime model.archive model.frame
|
||||
let f0, f1, _ = Plots.Utils.getFrameRange model.frame model.archive timeUnit
|
||||
let df0 = Drifters.calcDriftersFrame model.archive postdrift f0
|
||||
let df1 = Drifters.calcDriftersFrame model.archive postdrift f1
|
||||
let! data = fetchData idx
|
||||
match viewProp.PropType with
|
||||
| Prop.WC
|
||||
| Prop.DW _
|
||||
| Prop.SW
|
||||
| Prop.Sed _
|
||||
| Prop.SedV2
|
||||
| Prop.Conc2D
|
||||
| Prop.Conc3D ->
|
||||
let data =
|
||||
data
|
||||
|> Array.Parallel.map ((*) viewProp.ScalingFactor)
|
||||
|> Plotly.toTimeSeries2D time freq (df0, df1, 1)
|
||||
|> Array.singleton
|
||||
|
||||
html $"""
|
||||
<div
|
||||
class="full-width-center invisible"
|
||||
?invisible={not isLoading}
|
||||
>
|
||||
<sp-progress-circle size="m" label="loading-drifters-plot" indeterminate ></sp-progress-circle>
|
||||
</div>
|
||||
<div
|
||||
class="invisible"
|
||||
?invisible="{isLoading}"
|
||||
style="width: 100%%"
|
||||
>
|
||||
{linePlot plotInfo plotData}
|
||||
{if dataExtracted then clearDataButton else extractDataButton}
|
||||
{if dataExtracted then showExtractedData else Lit.nothing}
|
||||
</div>
|
||||
"""
|
||||
// TODO: Send out to the model
|
||||
setPlotData data
|
||||
| _ -> ()
|
||||
}
|
||||
|
||||
let reload timeUnit idx =
|
||||
async {
|
||||
setLoading true
|
||||
do! fetchSeries timeUnit idx
|
||||
setLoading false
|
||||
}
|
||||
|> Async.StartImmediate
|
||||
|
||||
[<HookComponent>]
|
||||
let plotControls model dispatch postdrift =
|
||||
let selectedCoord, setSelectedCoord = Hook.useState<float * float> ((0.0, 0.0))
|
||||
|
||||
let setTimeUnit newUnit =
|
||||
{ model.probePoint with timeUnit = newUnit }
|
||||
|> SetProbePoint
|
||||
|> dispatch
|
||||
|
||||
// TODO: Do this top-level which the side nav reads? Ah, no, the controls need info which we get from searching for the node and>
|
||||
Hook.useEffectOnChange (
|
||||
model.probePoint.point,
|
||||
Option.iter (fun p ->
|
||||
async {
|
||||
let aid =
|
||||
if model.concGrid.IsSome then
|
||||
postdrift.Archive.archiveId
|
||||
else
|
||||
model.archive.id
|
||||
let! idx' = Utils.tryGetNearestNodeAndElement model.dataSvc aid p
|
||||
return!
|
||||
match idx' with
|
||||
| None ->
|
||||
{ model.probePoint with
|
||||
timeUnit = TimeUnit.Day
|
||||
series = true
|
||||
idx = None }
|
||||
|> SetProbePoint
|
||||
|> dispatch
|
||||
|
||||
async.Return ()
|
||||
| Some (node, elemIdx) ->
|
||||
async {
|
||||
setSelectedCoord p
|
||||
|
||||
{ model.probePoint with
|
||||
timeUnit = TimeUnit.Day
|
||||
series = true
|
||||
idx = Some (node, elemIdx)
|
||||
}
|
||||
|> SetProbePoint
|
||||
|> dispatch
|
||||
}
|
||||
}
|
||||
|> Async.StartImmediate)
|
||||
(model.frame, model.probePoint.idx, model.probePoint.timeUnit),
|
||||
fun (_, idxOpt, timeUnit) ->
|
||||
idxOpt
|
||||
|> Option.iter (Array.unzip >> fun (nodeIdx, _) ->
|
||||
reload timeUnit nodeIdx
|
||||
if showExtractedData then
|
||||
handleExtractData ()
|
||||
)
|
||||
)
|
||||
|
||||
let dlButton =
|
||||
// match model.selectedDrifterFields with
|
||||
// | None ->
|
||||
// Lit.nothing
|
||||
// | Some d ->
|
||||
// let sorcererUrl = WebStorage.sessionStorage["sorcerer_url"]
|
||||
// let plotType =
|
||||
// match d.SimType with
|
||||
// | Sedimentation -> "sed"
|
||||
// | _ -> "conc"
|
||||
// let frame, n =
|
||||
// let f0, f1, _ = getFrameRange model.probePoint.timeUnit model model.archive.saveFreq
|
||||
// let df0 = Drifters.calcDriftersFrame model d f0
|
||||
// let df1 = Drifters.calcDriftersFrame model d f1
|
||||
// df0, df1 - df0
|
||||
// let aidStr = string d.Archive.archiveId
|
||||
// match model.probePoint.idx with
|
||||
// | Some _ ->
|
||||
// let lng, lat = Utils.toWgs84' selectedCoord |> fun (x, y) -> $"%.3f{x}", $"%.3f{y}"
|
||||
// let lng', lat' = selectedCoord |> fun (x, y) -> $"%.5f{x}", $"%.5f{y}"
|
||||
// let zipName = $"%s{plotType}-%s{lng},%s{lat}.zip"
|
||||
// let url =
|
||||
// $"%s{sorcererUrl}/download/drifters/%s{plotType}/%s{aidStr}/%s{lng'},%s{lat'}/%i{frame}/%i{n}/%s{zipName}"
|
||||
//
|
||||
// html $"""
|
||||
// <a href="{url}">
|
||||
// <sp-action-button static="primary">Download</sp-action-button>
|
||||
// </a>
|
||||
// """
|
||||
// | None ->
|
||||
html $"""<sp-action-button ?disabled="{true}" static="primary"> Download </sp-action-button>"""
|
||||
|
||||
|
||||
|
||||
let timeSeriesSelectors =
|
||||
html $"""
|
||||
<div style="display: flex; flex-flow: column;">
|
||||
<sp-field-label>Time series</sp-field-label>
|
||||
<div class="timeseries-box2">
|
||||
<sp-radio-group selected="{string model.probePoint.timeUnit}" vertical>
|
||||
<sp-radio
|
||||
value="day"
|
||||
@change={Ev(fun _ -> setTimeUnit TimeUnit.Day)}
|
||||
>
|
||||
Day
|
||||
</sp-radio>
|
||||
|
||||
<sp-radio
|
||||
value="week"
|
||||
@change={Ev(fun _ -> setTimeUnit TimeUnit.Week)}
|
||||
>
|
||||
Week
|
||||
</sp-radio>
|
||||
|
||||
<sp-radio
|
||||
value="month"
|
||||
@change={Ev(fun _ -> setTimeUnit TimeUnit.Month)}
|
||||
>
|
||||
Month
|
||||
</sp-radio>
|
||||
|
||||
<sp-radio
|
||||
value="quarter"
|
||||
@change={Ev(fun _ -> setTimeUnit TimeUnit.Quarter)}
|
||||
>
|
||||
Quarter
|
||||
</sp-radio>
|
||||
<sp-radio
|
||||
value="year"
|
||||
@change={Ev(fun _ -> setTimeUnit TimeUnit.Year)}
|
||||
>
|
||||
Year
|
||||
</sp-radio>
|
||||
</sp-radio-group>
|
||||
let spinner () =
|
||||
html
|
||||
$"""
|
||||
<div class="spinner-container">
|
||||
<sp-progress-circle size="m" label="Loading drifters plots" indeterminate></sp-progress-circle>
|
||||
</div>
|
||||
"""
|
||||
|
||||
let extractedTextfield () =
|
||||
html $"""<textarea id="sample-coords" class="textarea grow" style="height: 90%%;" readonly>{extractedData}</textarea>"""
|
||||
|
||||
let content () =
|
||||
if showExtractedData then
|
||||
extractedTextfield ()
|
||||
else
|
||||
Plotly.LinePlot info
|
||||
|
||||
let footer () =
|
||||
// TODO: Add copy button when looking at the csv
|
||||
if showExtractedData then
|
||||
html
|
||||
$"""
|
||||
<sp-action-button
|
||||
static="primary"
|
||||
@click={Ev(fun _ -> handleClearData ())}
|
||||
>
|
||||
Cancel
|
||||
</sp-action-button>
|
||||
"""
|
||||
else
|
||||
html
|
||||
$"""
|
||||
<sp-action-button
|
||||
static="primary"
|
||||
@click={Ev(fun _ -> handleExtractData ())}
|
||||
>
|
||||
Extract data
|
||||
</sp-action-button>
|
||||
"""
|
||||
|
||||
html
|
||||
$"""
|
||||
<div class="chart-content-container">
|
||||
{if isLoading then spinner () else Lit.nothing}
|
||||
{content ()}
|
||||
</div>
|
||||
"""
|
||||
|
||||
|
||||
html $"""
|
||||
<div style="display: flex; flex-direction: column;">
|
||||
{timeSeriesSelectors}
|
||||
<div slot="footer">
|
||||
{footer ()}
|
||||
</div>
|
||||
"""
|
||||
|
||||
[<HookComponent>]
|
||||
let drifterPlots model postdrift =
|
||||
seriesPlot model postdrift
|
||||
"""
|
||||
@@ -8,6 +8,10 @@ open Thoth.Json
|
||||
open Atlantis.Types
|
||||
open Utils
|
||||
|
||||
[<Literal>]
|
||||
let api = "https://api.fiskeridir.no/pub-aqua/api/v1"
|
||||
|
||||
let bordersUrl id = sprintf "%s/sites/%i/borders" api id
|
||||
|
||||
/// See docs/fiskeridir-locality-borders-example-payload.json
|
||||
type FiskeridirLocalityBorder = {
|
||||
@@ -28,7 +32,7 @@ and Point = {
|
||||
|
||||
let fetchLocalityBorders (map: OlMap) (localityId: int) =
|
||||
Fetch.tryGet<unit, FiskeridirLocalityBorder array>(
|
||||
url = $"https://api.fiskeridir.no/pub-aqua/api/v1/sites/{localityId}/borders",
|
||||
url = bordersUrl localityId,
|
||||
headers = [
|
||||
Fetch.Types.HttpRequestHeaders.Accept "application/json"
|
||||
Fetch.Types.HttpRequestHeaders.AcceptCharset "UTF-8"
|
||||
@@ -94,4 +98,4 @@ let fetchLocalityBorders (map: OlMap) (localityId: int) =
|
||||
|
||||
Layers.addFeatures map MapLayer.Aquaculture [| feature |]
|
||||
|
||||
())
|
||||
())
|
||||
127
src/Atlantis/src/Client/Mapster/FluentUI/ColormapSelect.fs
Normal file
127
src/Atlantis/src/Client/Mapster/FluentUI/ColormapSelect.fs
Normal file
@@ -0,0 +1,127 @@
|
||||
namespace FluentUI
|
||||
|
||||
open Atlantis.Types
|
||||
|
||||
module private ReactLib =
|
||||
open Browser
|
||||
open Fable.Core
|
||||
open Fable.Core.JsInterop
|
||||
open Feliz
|
||||
open Matplotlib.ColorMaps
|
||||
|
||||
let private webLightTheme: obj = import "webLightTheme" "@fluentui/react-components"
|
||||
|
||||
import "FluentProvider" "@fluentui/react-components"
|
||||
import "List" "@fluentui/react-components"
|
||||
import "ListItem" "@fluentui/react-components"
|
||||
import "Text" "@fluentui/react-components"
|
||||
|
||||
[<JSX.Component>]
|
||||
let private ColorSamples (colorMap: ColorMap) =
|
||||
let matplotMap = Lib.Colors.getColormap (ColorMode.Normal, colorMap) (0.0, 1.0)
|
||||
let numberOfColors = 10
|
||||
let toSkip: int = matplotMap.nColors / numberOfColors
|
||||
|
||||
matplotMap.colors
|
||||
|> Array.indexed
|
||||
|> Array.filter (fun (i, _) -> i % toSkip = 0)
|
||||
|> Array.map (fun (i, (r, g, b)) ->
|
||||
let red = 256.0f * r
|
||||
let green = 256.0f * g
|
||||
let blue = 256.0f * b
|
||||
let style = {|
|
||||
backgroundColor = sprintf "rgb(%f, %f, %f)" red green blue
|
||||
|}
|
||||
|
||||
JSX.html
|
||||
$"""
|
||||
<div
|
||||
key={i}
|
||||
className="color-box"
|
||||
style={style}
|
||||
>
|
||||
</div>
|
||||
"""
|
||||
)
|
||||
|
||||
let private handleSelectionChange (callback: obj -> unit) (ev: Types.Event) (data: obj) : unit =
|
||||
console.debug("[ColormapSelect] Handle selection %o", data)
|
||||
callback data
|
||||
|
||||
[<JSX.Component>]
|
||||
let ColormapSelect (selectedColorMap: ColorMap, onChange: ColorMap -> unit) =
|
||||
let colorSetMaps, selectedColorMapName =
|
||||
match selectedColorMap with
|
||||
| ColorMap.Ocean name -> Ocean.colors.Keys |> Seq.toArray, name
|
||||
| ColorMap.Color16 name -> Color16.colors.Keys |> Seq.toArray, name
|
||||
| _ -> failwith "not implemented"
|
||||
|
||||
let handleChangeMap colorMap ev =
|
||||
console.debug("[ColormapSelect] Change colormap: %s", colorMap)
|
||||
|
||||
let handleSelection (data: obj) =
|
||||
console.debug("[ColormapSelect] List change %o", data)
|
||||
let colorMapStr: string = unbox<string array> data?selectedItems |> Array.head
|
||||
let newColorMap =
|
||||
match selectedColorMap with
|
||||
| ColorMap.Ocean _ -> ColorMap.Ocean colorMapStr
|
||||
| ColorMap.Color16 _ -> ColorMap.Color16 colorMapStr
|
||||
| _ -> ColorMap.Ocean colorMapStr
|
||||
onChange newColorMap
|
||||
|
||||
let handleSelectionFunction = emitJsExpr (handleSelectionChange handleSelection) "$0"
|
||||
|
||||
let colors =
|
||||
colorSetMaps
|
||||
|> Array.map (fun name ->
|
||||
let cmap =
|
||||
match selectedColorMap with
|
||||
| ColorMap.Ocean _ -> ColorMap.Ocean name
|
||||
| ColorMap.Color16 _ -> ColorMap.Color16 name
|
||||
| _ -> failwith "not implemented"
|
||||
let label = cmap.ToLabel()
|
||||
|
||||
// NOTE: The "Text$" with `$`. The import does strange things.
|
||||
JSX.html
|
||||
$"""
|
||||
<ListItem
|
||||
key={name}
|
||||
value={name}
|
||||
>
|
||||
<div className="color-palette-sample">
|
||||
<div className="grow">
|
||||
<Text$>{label}</Text$>
|
||||
</div>
|
||||
|
||||
<div className="colormap-container">
|
||||
{ColorSamples cmap}
|
||||
</div>
|
||||
</div>
|
||||
</ListItem>
|
||||
"""
|
||||
)
|
||||
|
||||
React.useEffect (
|
||||
(fun () ->
|
||||
console.debug("[ColormapSelect] Colormap changed! %o", selectedColorMap)
|
||||
),
|
||||
[|box selectedColorMap|]
|
||||
)
|
||||
|
||||
JSX.html
|
||||
$"""
|
||||
<FluentProvider theme={webLightTheme}>
|
||||
<List
|
||||
selectionMode="single"
|
||||
selectedItems={[|selectedColorMapName|]}
|
||||
onSelectionChange={handleSelectionFunction}
|
||||
>
|
||||
{colors}
|
||||
</List>
|
||||
</FluentProvider>
|
||||
"""
|
||||
|
||||
module Lit =
|
||||
open Lit
|
||||
|
||||
let ColormapSelect = React.toLit (ReactLib.ColormapSelect >> Lib.React.fromJsx)
|
||||
100
src/Atlantis/src/Client/Mapster/FluentUI/DatePicker.fs
Normal file
100
src/Atlantis/src/Client/Mapster/FluentUI/DatePicker.fs
Normal file
@@ -0,0 +1,100 @@
|
||||
namespace FluentUI
|
||||
|
||||
module private ReactLib =
|
||||
open Browser
|
||||
open Fable.Core
|
||||
open Fable.Core.JsInterop
|
||||
open Feliz
|
||||
open System
|
||||
|
||||
import "DatePicker" "@fluentui/react-datepicker-compat"
|
||||
|
||||
import "FluentProvider" "@fluentui/react-components"
|
||||
import "Field" "@fluentui/react-components"
|
||||
|
||||
[<JSX.Component>]
|
||||
let DatePicker
|
||||
(
|
||||
disabled: bool,
|
||||
value: DateTime,
|
||||
onSelectDate: DateTime -> unit,
|
||||
minDate: DateTime,
|
||||
maxDate: DateTime
|
||||
) =
|
||||
let onSelectDateRef = React.useRef onSelectDate
|
||||
let internalValue, setInternalValue = React.useState value
|
||||
let isUserInteractionRef = React.useRef false
|
||||
|
||||
// Update ref on every render to always have the latest callback
|
||||
React.useEffect ((fun () -> onSelectDateRef.current <- onSelectDate), [| box onSelectDate |])
|
||||
|
||||
// Only update internal value when external value changes AND it's not from user interaction
|
||||
React.useEffect (
|
||||
(fun () ->
|
||||
if not isUserInteractionRef.current && internalValue <> value then
|
||||
setInternalValue value
|
||||
isUserInteractionRef.current <- false
|
||||
),
|
||||
[| box value |]
|
||||
)
|
||||
|
||||
let handleSelectDate =
|
||||
React.useCallback (
|
||||
(fun (data: obj) ->
|
||||
let date = data?value
|
||||
if not (isNull date) then
|
||||
let selectedDate: DateTime = unbox date
|
||||
// Validate against min/max constraints
|
||||
let isValid =
|
||||
if selectedDate >= minDate && selectedDate <= maxDate then
|
||||
true
|
||||
else
|
||||
false
|
||||
|
||||
if isValid then
|
||||
isUserInteractionRef.current <- true
|
||||
setInternalValue selectedDate
|
||||
onSelectDateRef.current selectedDate
|
||||
),
|
||||
[| box minDate; box maxDate |]
|
||||
)
|
||||
|
||||
let onSelectDateHandler =
|
||||
React.useCallback (
|
||||
emitJsExpr
|
||||
handleSelectDate
|
||||
"""
|
||||
(ev, data) => {
|
||||
if (data != null) {
|
||||
$0(data);
|
||||
} else {
|
||||
$0({ value: ev });
|
||||
}
|
||||
}
|
||||
""",
|
||||
[| box handleSelectDate |]
|
||||
)
|
||||
|
||||
JSX.html
|
||||
$"""
|
||||
<FluentProvider theme={Lib.webLightTheme}>
|
||||
<Field>
|
||||
<DatePicker
|
||||
inlinePopup
|
||||
firstDayOfWeek={1}
|
||||
showGoToToday={false}
|
||||
showWeekNumbers={true}
|
||||
disabled={disabled}
|
||||
value={internalValue}
|
||||
onSelectDate={onSelectDateHandler}
|
||||
minDate={minDate}
|
||||
maxDate={maxDate}
|
||||
/>
|
||||
</Field>
|
||||
</FluentProvider>
|
||||
"""
|
||||
|
||||
module Lit =
|
||||
open Lit
|
||||
|
||||
let DatePicker = React.toLit (ReactLib.DatePicker >> Lib.React.fromJsx)
|
||||
8
src/Atlantis/src/Client/Mapster/FluentUI/Lib.fs
Normal file
8
src/Atlantis/src/Client/Mapster/FluentUI/Lib.fs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace FluentUI
|
||||
|
||||
module Lib =
|
||||
open Fable.Core
|
||||
open Fable.Core.JsInterop
|
||||
|
||||
let webLightTheme: obj = import "webLightTheme" "@fluentui/react-components"
|
||||
let useId () : int = import "useId" "@fluentui/react-components"
|
||||
75
src/Atlantis/src/Client/Mapster/FluentUI/PeriodCalendar.fs
Normal file
75
src/Atlantis/src/Client/Mapster/FluentUI/PeriodCalendar.fs
Normal file
@@ -0,0 +1,75 @@
|
||||
namespace FluentUI
|
||||
|
||||
module private ReactLib =
|
||||
open Browser
|
||||
open Fable.Core
|
||||
open Fable.Core.JsInterop
|
||||
open Feliz
|
||||
|
||||
let webLightTheme: obj = import "webLightTheme" "@fluentui/react-components"
|
||||
|
||||
import "Calendar" "@fluentui/react-calendar-compat"
|
||||
|
||||
import "FluentProvider" "@fluentui/react-components"
|
||||
import "Switch" "@fluentui/react-components"
|
||||
|
||||
let private getMonth year (period : Sorcerer.Types.Period) : System.DateTime option =
|
||||
match period with
|
||||
| Sorcerer.Types.Year -> None
|
||||
| Sorcerer.Types.Month m -> Some (System.DateTime(year, m + 1, 1))
|
||||
|
||||
[<JSX.Component>]
|
||||
let PeriodCalendar
|
||||
(
|
||||
key: 'T,
|
||||
archive: Atlantis.Types.ArchiveInfo,
|
||||
onSelect: Sorcerer.Types.Period -> unit,
|
||||
period: Sorcerer.Types.Period
|
||||
)
|
||||
=
|
||||
let maxDate =
|
||||
let length = System.TimeSpan.FromSeconds (float archive.saveFreq * float archive.frames)
|
||||
archive.startTime + length
|
||||
|
||||
let selectedDate, setSelectedDate = React.useState<System.DateTime option> (period |> getMonth archive.startTime.Year)
|
||||
|
||||
let handleYearChange () =
|
||||
onSelect Sorcerer.Types.Period.Year
|
||||
setSelectedDate None
|
||||
let handleSelectDate =
|
||||
React.useCallback (
|
||||
(fun (date: System.DateTime) ->
|
||||
console.debug("[FluentUI] PeriodCalendar select date: %o", date)
|
||||
setSelectedDate (Some date)
|
||||
onSelect (Sorcerer.Types.Period.Month (date.Month - 1))
|
||||
),
|
||||
[||]
|
||||
)
|
||||
|
||||
React.useEffect (
|
||||
(fun () ->
|
||||
console.debug("[FluentUI] Mounting PeriodCalendar with period %o", period)
|
||||
),
|
||||
[||]
|
||||
)
|
||||
|
||||
JSX.html $"""
|
||||
<react.StrictMode>
|
||||
<FluentProvider theme={webLightTheme}>
|
||||
<Switch label="Year" checked={period = Sorcerer.Types.Period.Year} onChange={handleYearChange}></Switch>
|
||||
<Calendar
|
||||
value={selectedDate}
|
||||
onSelectDate={handleSelectDate}
|
||||
firstDayOfWeek={1}
|
||||
isDayPickerVisible={false}
|
||||
highlightSelectedMonth={selectedDate.IsSome}
|
||||
showGoToToday={false}
|
||||
/>
|
||||
</FluentProvider>
|
||||
</react.StrictMode>
|
||||
"""
|
||||
|
||||
module Lit =
|
||||
open Lit
|
||||
|
||||
let PeriodCalendar<'T> = React.toLit (ReactLib.PeriodCalendar >> Lib.React.fromJsx)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user