Compare commits

...

16 Commits

Author SHA1 Message Date
Moritz Jörg
06632b4413 fun 2025-08-18 22:23:07 +02:00
2ff9dafc13 Move catalog into Atlantis vite project 2025-08-18 16:45:22 +02:00
6a24342322 Add fsautocomplete and fantomas to shell pkgs 2025-08-18 09:43:23 +02:00
d05015decf Add map to FluentUI 2025-08-18 09:42:48 +02:00
b66cc67802 Start on FluentUI 2025-08-17 13:37:05 +02:00
ac27a0544d Add map and some navigation to Carbon 2025-08-17 13:36:59 +02:00
3ba01c882b Add OL map to Carbon 2025-08-17 13:36:27 +02:00
649b0af9d4 Start on Carbon 2025-08-17 13:36:27 +02:00
918041e063 Deploy Catalog to atlantis public
Static site only in bundledebug to test deploying the component
playground
2025-08-17 13:36:27 +02:00
c3c6bb06df Add back .build/Helpers.fs :) 2025-08-17 13:36:27 +02:00
647302778f Add InstallClient to Atlantis Fake build step 2025-08-17 13:36:27 +02:00
e53de54c13 Add *.jsx to .gitignore 2025-08-17 13:36:26 +02:00
8e9d975cca Make *.html files 2 indent size 2025-08-17 13:36:09 +02:00
e8990fa1d4 Remove outmost build project 2025-08-17 13:36:09 +02:00
ff4f3aebab Continue on Mui example
Trying to create a nice sidebar that can be collapsed
2025-08-17 13:36:08 +02:00
4ddbe133b0 Add Mui jsx stub in Catalog
Creating a playground for trying out different component libraries.
Focusing on React ones this time around.
2025-08-17 13:35:34 +02:00
73 changed files with 6847 additions and 635 deletions

View File

@@ -1,38 +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 bun "install" "."
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

View File

@@ -124,4 +124,4 @@ let runOrDefault args =
0
with e ->
printfn "%A" e
1
1

View File

@@ -7,6 +7,9 @@ charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = false
[*.html]
indent_size = 2
[*.js]
indent_size = 2
max_line_length= 80

3
.gitignore vendored
View File

@@ -29,4 +29,5 @@ NuGet.Config
sync.list
packages.lock.json
package-lock.json
*.nupkg
*.nupkg
*.jsx

View File

@@ -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>

713
bun.lock

File diff suppressed because it is too large Load Diff

View File

@@ -15,17 +15,29 @@
"@sentry/vite-plugin": "^3.5.0",
"@vitejs/plugin-react": "^5.0.0",
"rollup-plugin-scss": "^4.0.1",
"sass": "^1.89.2",
"@tauri-apps/cli": "^2.7.1",
"semantic-release": "^24.2.5",
"semantic-release-dotnet": "^1.0.0",
"vite": "npm:rolldown-vite@latest",
"vite-plugin-mkcert": "^1.17.8"
},
"dependencies": {
"@carbon/icons-react": "^11.65.0",
"@carbon/react": "^1.89.0",
"@carbon/type": "^11.45.0",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@fluentui/react-components": "^9.68.3",
"@fluentui/react-icons": "^2.0.307",
"@fluentui/web-components": "^3.0.0-beta.115",
"@fontsource/roboto": "^5.2.6",
"@fortawesome/fontawesome-free": "^6.7.2",
"@ibm/plex-sans": "^1.1.0",
"@lit-labs/motion": "^1.0.8",
"@lit/context": "^1.1.5",
"@microsoft/signalr": "^8.0.7",
"@mui/icons-material": "^7.3.1",
"@mui/material": "^7.3.1",
"@sentry/browser": "^9.30.0",
"@shoelace-style/shoelace": "^2.20.1",
"@spectrum-web-components/accordion": "^1.7.0",
@@ -62,6 +74,8 @@
"@spectrum-web-components/underlay": "^1.6.0",
"@turf/bezier-spline": "^7.2.0",
"@vaadin/login": "^24.8.0",
"@tauri-apps/api": "^2.7.0",
"@tauri-apps/plugin-shell": "^2",
"lit": "^3.3.0",
"lit-html": "^3.3.0",
"ol": "^10.6.0",
@@ -70,6 +84,7 @@
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-plotly.js": "^2.6.0",
"sass": "^1.90.0",
"vis-timeline": "^7.7.4"
}
}

View File

@@ -15,6 +15,9 @@ pkgs.mkShellNoCC {
buildInputs = [ dotnet-sdk ];
packages = [
pkgs.fsautocomplete
pkgs.fantomas
# JavaScript
pkgs.bun
pkgs.nodejs-slim

View File

@@ -5,12 +5,12 @@ open Farmer.Builders
open Helpers
initializeContext()
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 testPath = Path.getFullName "test"
let libPath = Path.getFullName "src/Interfaces" |> Some
let distPath = Path.getFullName "dist"
let packPath = Path.getFullName "packages"
@@ -25,66 +25,63 @@ let fableWatch = $"fable watch -e .jsx -o build --run {vite}"
Target.create "Clean" (fun _ -> Shell.cleanDir distPath)
Target.create "InstallClient" (fun _ ->
run bun "install" "."
run dotnet "tool restore" ".")
Target.create "Bundle" (fun _ ->
[ "server", dotnet $"build -tl -c Release -o {distPath} -p:DefineConstants=" serverPath
"client", dotnet (fable "-m production") clientPath ]
|> runParallel
)
runParallel [
"server", dotnet $"build -tl -c Release -o {distPath} -p:DefineConstants=" serverPath
"client", dotnet (fable "-m production") clientPath
])
Target.create "BundleDebug" (fun _ ->
[ "server", dotnet $"build -tl -c Debug -o {distPath} -p:DefineConstants=" serverPath
"client", dotnet (fable "-m development --minify false --sourcemap true") clientPath ]
|> runParallel
Trace.log "--- Building Server ---"
run dotnet $"build -tl -c Debug -o {distPath} -p:DefineConstants=" serverPath
Trace.log "--- Building Frontend ---"
run dotnet (fable "-m development --minify false --sourcemap true") clientPath
)
Target.create "Pack" (fun _ ->
match libPath with
| Some p -> run dotnet $"pack -c Release -o \"{packPath}\"" p
| None -> ()
)
| None -> ())
Target.create "Run" (fun _ ->
[ "server", dotnet "watch run" serverPath
"client", dotnet fableWatch clientPath ]
|> runParallel
)
runParallel [
"server", dotnet "watch run" serverPath
"client", dotnet fableWatch clientPath
])
Target.create "Client" (fun _ ->
run dotnet fableWatch clientPath
)
Target.create "Client" (fun _ -> run dotnet fableWatch clientPath)
Target.create "Format" (fun _ ->
run dotnet "fantomas . -r" "src"
)
Target.create "Format" (fun _ -> run dotnet "fantomas . -r" "src")
Target.create "Test" (fun _ ->
if System.IO.Directory.Exists testPath then
[ "server", dotnet "run" (testPath + "/Server")
"client", dotnet $"fable -e .jsx -o build --run {vite}" (testPath + "/Client") ]
|> runParallel
else ()
)
runParallel [
"server", dotnet "run" (testPath + "/Server")
"client", dotnet $"fable -e .jsx -o build --run {vite}" (testPath + "/Client")
]
else
())
open Fake.Core.TargetOperators
let dependencies = [
"Clean"
==> "Bundle"
"Clean" ==> "InstallClient" ==> "Bundle"
"Clean"
==> "BundleDebug"
"Clean" ==> "InstallClient" ==> "BundleDebug"
"Clean"
==> "Test"
"Clean" ==> "InstallClient" ==> "Test"
"Clean"
==> "Run"
"Clean" ==> "InstallClient" ==> "Run"
"Clean"
==> "Pack"
"Clean" ==> "InstallClient" ==> "Pack"
"Client"
]
[<EntryPoint>]
let main args = runOrDefault args
let main args = runOrDefault args

View File

@@ -6,8 +6,35 @@ let
port = 8000;
shell = pkgs.callPackage ./../../shell.nix { };
in
pkgs.mkShellNoCC {
pkgs.mkShell rec {
inputsFrom = [ shell ];
packages = [
pkgs.rustc
pkgs.cargo
pkgs.cargo-tauri
pkgs.gobject-introspection
pkgs.pkg-config
];
buildInputs = [
pkgs.at-spi2-atk
pkgs.atkmm
pkgs.cairo
pkgs.gdk-pixbuf
pkgs.glib
pkgs.gtk3
pkgs.harfbuzz
pkgs.librsvg
pkgs.libsoup_3
pkgs.pango
pkgs.webkitgtk_4_1
pkgs.openssl
];
RUST_BACKTRACE = "1";
LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath buildInputs;
LOG_LEVEL = "verbose";
REDIS = "localhost:6379";

View File

@@ -1,8 +1,10 @@
module App
open Browser
open Fable.Core
open Fable.Core.JsInterop
open Lit
open Remoting
let initSentry = Array.contains window.location.hostname Sentry.hostTargets
@@ -11,20 +13,26 @@ if initSentry then
console.debug "Pushing to Sentry"
Sentry.init ()
let testAuthenticated () =
promise {
let! authenticated = authApi.IsAuthenticated() |> Async.StartAsPromise
if authenticated.IsSome && not (isNullOrUndefined sessionStorage["archive_id"]) then
do! Utils.initAtlantisSessionUrls () |> Async.StartAsPromise
else
do sessionStorage.removeItem "archive_id"
do console.info("Redirecting to /atlas.html")
return ()
}
[<LitElement("init-app")>]
let InitApp () =
let _ = LitElement.init (fun cfg -> cfg.useShadowDom <- false)
Hook.useEffectOnce (fun () ->
async {
let! authenticated = authApi.IsAuthenticated()
if authenticated.IsSome && not (isNullOrUndefined sessionStorage["archive_id"]) then
do! Utils.initAtlantisSessionUrls ()
window.location.href <- "/map.html"
else
sessionStorage.removeItem "archive_id"
window.location.href <- "/atlas.html"
return ()
} |> Async.StartImmediate
testAuthenticated ()
|> Promise.start
)
Lit.nothing
Lit.nothing

View File

@@ -9,11 +9,11 @@ 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"

View File

@@ -1,165 +0,0 @@
module App
open Archmaester
open Archmaester.Dto
open Browser
open Fable.Remoting.Client
open Lit
let archmaester =
Remoting.createApi ()
|> Remoting.withRouteBuilder Api.apiRouteBuilder
|> Remoting.buildProxy<Api.Inventory>
let fetchArchives modelAreaId callback =
async {
let! res = archmaester.getArchive modelAreaId
match res with
| Error err -> console.error $"Fetch archives error: {err}"
| Ok archives -> Some [| archives |] |> callback
}
|> Async.StartImmediate
let fetchModelAreas callback =
async {
let! res = archmaester.getModelAreaArchives (HelloWorld, ArchiveType.FromString "*:*:*")
match res with
| Error err -> console.error $"Fetch model areas error: {err}"
| Ok modelAreas -> Some modelAreas |> callback
}
[<LitElement("archive-listing")>]
let ArchiveListing () =
let _, _ = LitElement.init (fun init -> init.useShadowDom <- false)
let (modelAreas: ArchiveProps[] option), setModelAreas = Hook.useState None
Hook.useEffectOnce (fun _ -> fetchModelAreas setModelAreas |> Async.StartImmediate)
let archiveId (a: Archmaester.Dto.ArchiveProps) = a.archiveId.ToString ()
let archiveFiles files =
let fileEntry (fileName, _) = html $"""<li>{fileName}</li>"""
files |> Array.sortBy fst |> Lit.mapUnique fst fileEntry
let archiveOwners (acl: ArchiveAcl option) =
let owners =
match acl with
| Some x -> x.users
| None -> [||]
let userEntry name = html $"""<li>{name}</li>"""
let users' = owners |> Array.map userEntry |> Lit.ofArray
if Array.isEmpty owners then
Lit.nothing
else
html
$"""
<span>Owners:</span>
<div class="overflow-y-scroll">
<ul class="list-disc list-inside">
{users'}
</ul>
</div>
"""
let archiveUsers (acl: ArchiveAcl option) =
let users =
match acl with
| Some x -> x.users
| None -> [||]
let userEntry name = html $"""<li>{name}</li>"""
let users' = users |> Array.map userEntry |> Lit.ofArray
if Array.isEmpty users then
Lit.nothing
else
html
$"""
<span>Users:</span>
<div class="overflow-y-scroll">
<ul class="list-disc list-inside">
{users'}
</ul>
</div>
"""
let archiveGroups (acl: ArchiveAcl option) =
let groups =
match acl with
| Some x -> x.users
| None -> [||]
let groupEntry name = html $"""<li>{name}</li>"""
let groups' = groups |> Array.map groupEntry |> Lit.ofArray
if Array.isEmpty groups then
Lit.nothing
else
html
$"""
<span>Groups:</span>
<div class="overflow-y-scroll max-h-96">
<ul class="list-disc list-inside">
{groups'}
</ul>
</div>
"""
let archiveItem (item: ArchiveProps) =
html
$"""
<div class="card w-full shadow-xl">
<div class="card-body">
<h2 class="card-title">
{item.name}
<div class="badge badge-secondary">{item.archiveType}</div>
</h2>
<h3>Start date: {item.startTime.ToShortDateString ()} {item.startTime.ToShortTimeString ()}</h3>
<span>Projection: {item.projection}</span>
{{archiveOwners item.acl}}
{{archiveUsers item.acl}}
{{archiveGroups item.acl}}
<h4>Start date: {item.startTime}</h4>
<h4>End date: {item.endTime}</h4>
</div>
</div>
"""
let archiveName archiveName =
html
$"""
<p>{archiveName}</p>
"""
let modelAreaItem (item: ArchiveProps) =
// let archiveList =
// Array.map archiveName item.archiveType
html
$"""
<div class="card w-full shadow-xl">
<div class="card-body">
<h2 class="card-title">
{item.name}
</h2>
{item}
</div>
</div>
"""
let modelAreaList =
match modelAreas with
| None -> html $"""<progress class="progress w-56"></progress>"""
| Some [||] -> html $"""<h1>No ModelAreas</h1>"""
| Some models ->
html
$"""
{models |> Lit.mapUnique (fun m -> m.name) modelAreaItem}
"""
html
$"""
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
{modelAreaList}
</div>
"""

View File

@@ -1,20 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Version>6.20.0</Version>
<RootNamespace>Archivist</RootNamespace>
</PropertyGroup>
<ItemGroup>
<Content Include="input.css" />
<Content Include="index.html" />
<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" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\Interfaces\Archmaester\Archmaester.Api.fsproj" />
</ItemGroup>
</Project>

View File

@@ -1,42 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<title>Fable</title>
<meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" href="fable.ico" />
<link href="./build/client/style.css" rel="stylesheet">
</head>
<body>
<!-- navbar -->
<div class="navbar bg-base-300">
<div class="lg:hidden">
<label for="drawer-toggle" class="btn btn-square btn-ghost">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="inline-block w-6 h-6 stroke-current"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path></svg>
</label>
</div>
<div class="px-2 mx-2">
Archmeister
</div>
</div>
<div class="drawer drawer-mobile">
<input id="drawer-toggle" type="checkbox" class="drawer-toggle">
<div class="drawer-content">
<!-- content -->
<div class="flex justify-center">
<archive-listing></archive-listing>
</div>
</div>
<div class="drawer-side">
<label for="drawer-toggle" class="drawer-overlay"></label>
<ul class="menu p-4 w-80 bg-base-100 text-base-content">
<li><a>Models</a></li>
<li><a>Archives</a></li>
<li><a>Groups</a></li>
<li><a>Users</a></li>
</ul>
</div>
</div>
<script type="module" src="./build/App.jsx"></script>
</body>
</html>

View File

@@ -1,3 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@@ -1,4 +0,0 @@
#!/usr/bin/env bash
dotnet fable watch -o build/client --run vite -c ../../vite.config.js

View File

@@ -21,7 +21,8 @@
<PackageReference Update="FSharp.Core" Version="9.0.201"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="catalog\Catalog.fsproj"/>
<ProjectReference Include="Atlas\Atlas.fsproj"/>
<ProjectReference Include="Mapster\Mapster.fsproj"/>
</ItemGroup>
</Project>
</Project>

View File

@@ -0,0 +1,35 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Version>6.20.0</Version>
<RootNamespace>Archivist</RootNamespace>
</PropertyGroup>
<ItemGroup>
<Compile Include="Utils.fs" />
<Compile Include="Map.fs" />
<Compile Include="carbon/components/Header.fs" />
<Compile Include="carbon/Oceanography.fs" />
<Compile Include="carbon/Home.fs" />
<Compile Include="carbon/Carbon.fs" />
<Compile Include="carbon/Index.fs" />
<Compile Include="mui/Mui.fs" />
<Compile Include="mui/Index.fs" />
<Compile Include="fluentui/Archives.fs" />
<Compile Include="fluentui/Oceanography.fs" />
<Compile Include="fluentui/FluentUI.fs" />
<Compile Include="fluentui/Index.fs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Fable.Browser.Url" Version="1.4.0" />
<PackageReference Include="Fable.Core" Version="4.5.0" />
<PackageReference Include="Fable.OpenLayers" Version="2.18.0" />
<PackageReference Include="Fable.Remoting.Client" Version="7.32.0" />
<PackageReference Update="FSharp.Core" Version="9.0.201" />
<PackageReference Include="Feliz" Version="2.9.0" />
<PackageReference Include="Feliz.Router" Version="4.0.0" />
<PackageReference Include="Feliz.UseMediaQuery" Version="1.5.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Lib\Lib.fsproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,27 @@
module Oceanbox.Catalog.Map
open Browser
open Fable.OpenLayers
let create () : unit =
let map =
let osmLayer : Layer =
Layer.tileLayer [
layer.source (Source.osm [])
]
let view =
View.view [
view.center [| 0; 0 |]
view.zoom 2
]
OlMap.map [
map.layers [|
osmLayer
|]
map.view view
]
do map.setTarget "map"
do console.debug("Create map %o", map)
()

View File

@@ -0,0 +1,17 @@
module Oceanbox.Catalog.Utils
open Browser
open Fable.Core
open Fable.Core.JsInterop
open Feliz
let inline toReact (elem: JSX.Element) : ReactElement = unbox elem
let inline toStyle (styles: IStyleAttribute seq) : obj = createObj (unbox styles)
[<Emit("Object.entries($0)")>]
let inline spreadStyles object : IStyleAttribute array = jsNative
let toLocaleString (date: System.DateTime) : string =
let jsDate = unbox<JS.Date> date
jsDate.toLocaleString()

View File

@@ -0,0 +1,43 @@
module Oceanbox.Catalog.Carbon.App
open System
open Browser
open Fable.Core
open Fable.Core.JsInterop
open Fable.OpenLayers
open Feliz
let private Content: obj = import "Content" "@carbon/react"
let private BuoyIcon: obj = import "Buoy" "@carbon/icons-react"
type Page =
| Home
| Oceanography
[<JSX.Component>]
let View () =
let isOpen, setOpen = React.useState false
let currentPage, setPage = React.useState Home
let page =
match currentPage with
| Home -> Home.View ()
| Oceanography -> Oceanography.View ()
let handleNavigate (str: string) ev =
console.info("Navigate from header: %s, %o", str, ev)
match str with
| "home" -> setPage Home
| "oceanography" -> setPage Oceanography
| _ -> console.error("Invalid page from navigation callback: %s", str)
JSX.html $"""
<>
{Header.MainHeader handleNavigate}
<Content>
{page}
</Content>
</>
"""

View File

@@ -0,0 +1,77 @@
module Oceanbox.Catalog.Carbon.Home
open Browser
open Fable.Core
open Fable.Core.JsInterop
open Fable.OpenLayers
open Feliz
open Oceanbox
let private Button: obj = import "Button" "@carbon/react"
let private Column: obj = import "Column" "@carbon/react"
let private Grid: obj = import "Grid" "@carbon/react"
let private AddIcon: obj = import "Add" "@carbon/react/icons"
[<JSX.Component>]
let View () =
React.useEffectOnce (fun () ->
console.debug("Mounting Home")
do Catalog.Map.create ()
()
)
JSX.html $"""
<Grid className="home-grid" fullWidth>
<Column
sm={4}
md={8}
lg={ {| span = 16; offset = 4; |} }
xlg={ {| span = 16; offset = 3; |} }
max={ {| span = 16; offset = 2; |} }
>
<h2>Carbon</h2>
</Column>
<Column
sm={4}
md={8}
lg={ {| span = 16; offset = 4; |} }
xlg={ {| span = 16; offset = 3; |} }
max={ {| span = 16; offset = 2; |} }
>
<Grid>
<Column sm={4} md={8} lg={16} max={4}>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Rhoncus dolor purus non
enim praesent elementum facilisis leo vel. Risus at ultrices mi tempus
imperdiet. Semper risus in hendrerit gravida rutrum quisque non tellus.
Convallis convallis tellus id interdum velit laoreet id donec ultrices.
Odio morbi quis commodo odio aenean sed adipiscing. Amet nisl suscipit
adipiscing bibendum est ultricies integer quis. Cursus euismod quis viverra
nibh cras. Metus vulputate eu scelerisque felis imperdiet proin fermentum
leo. Mauris commodo quis imperdiet massa tincidunt. Cras tincidunt lobortis
feugiat vivamus at augue. At augue eget arcu dictum varius duis at
consectetur lorem. Velit sed ullamcorper morbi tincidunt. Lorem donec massa
sapien faucibus et molestie ac.
</p>
</Column>
<Column sm={4} md={8} lg={16} max={12}>
<div id="map"></div>
</Column>
</Grid>
</Column>
<Column
sm={4}
lg={ {| span = 6; offset = 4; |} }
xlg={ {| span = 16; offset = 3; |} }
max={ {| span = 16; offset = 2; |} }
>
<Button size="sm" kind="secondary">Cancel</Button>
<Button size="sm" renderIcon={AddIcon}>Hello, Carbon!</Button>
</Column>
</Grid>
"""

View File

@@ -0,0 +1,24 @@
module Oceanbox.Catalog.Carbon.Index
open System
open Browser
open Fable.Core
open Fable.Core.JsInterop
open Feliz
open Oceanbox.Catalog
let React : obj = importAll "react"
let root = ReactDOM.createRoot (document.getElementById "root")
root.render (
JSX.html
$"""
<React.StrictMode>
{App.View () |> Utils.toReact}
</React.StrictMode>
"""
|> Utils.toReact
)

View File

@@ -0,0 +1,35 @@
module Oceanbox.Catalog.Carbon.Oceanography
open Browser
open Fable.Core
open Fable.Core.JsInterop
open Fable.OpenLayers
open Feliz
open Oceanbox
let private Column: obj = import "Column" "@carbon/react"
let private Grid: obj = import "Grid" "@carbon/react"
[<JSX.Component>]
let View () =
React.useEffectOnce (fun () ->
console.debug("Mounting Oceanography")
do Catalog.Map.create ()
()
)
JSX.html $"""
<Grid className="oceanography-grid" narrow>
<Column
sm={4}
md={8}
lg={ {| span = 16; offset = 4; |} }
xlg={ {| span = 16; offset = 3; |} }
max={ {| span = 16; offset = 2; |} }
>
<div id="map"></div>
</Column>
</Grid>
"""

View File

@@ -0,0 +1,118 @@
module Oceanbox.Catalog.Carbon.Header
open Browser
open Fable.Core
open Fable.Core.JsInterop
open Feliz
let private CarbonHeader: obj = import "Header" "@carbon/react"
let private HeaderContainer: obj = import "HeaderContainer" "@carbon/react"
let private HeaderGlobalAction: obj = import "HeaderGlobalAction" "@carbon/react"
let private HeaderGlobalBar: obj = import "HeaderGlobalBar" "@carbon/react"
let private HeaderMenuButton: obj = import "HeaderMenuButton" "@carbon/react"
let private HeaderMenuItem: obj = import "HeaderMenuItem" "@carbon/react"
let private HeaderName: obj = import "HeaderName" "@carbon/react"
let private HeaderNavigation: obj = import "HeaderNavigation" "@carbon/react"
let private HeaderSideNavItems: obj = import "HeaderSideNavItems" "@carbon/react"
let private SideNav: obj = import "SideNav" "@carbon/react"
let private SideNavItems: obj = import "SideNavItems" "@carbon/react"
let private SideNavLink: obj = import "SideNavLink" "@carbon/react"
let private SideNavDivider: obj = import "SideNavDivider" "@carbon/react"
let private SideNavMenu: obj = import "SideNavMenu" "@carbon/react"
let private SideNavMenuItem: obj = import "SideNavMenuItem" "@carbon/react"
let private SkipToContent: obj = import "SkipToContent" "@carbon/react"
let private BuoyIcon: obj = import "Buoy" "@carbon/icons-react"
let private HomeIcon: obj = import "Home" "@carbon/icons-react"
let private IbmCloudHpcIcon: obj = import "IbmCloudHpc" "@carbon/icons-react"
let private NotificationIcon: obj = import "Notification" "@carbon/icons-react"
let private SwitcherIcon: obj = import "Switcher" "@carbon/icons-react"
let private urlHashtag (str: string) : string =
let url = URL.Create(str)
console.debug("URI: %s, hash: %s", url, url.hash)
url.hash
[<JSX.Component>]
let MainHeader (navigate: string -> obj -> unit) =
let currentPage = urlHashtag document.URL
let inner props =
let isSideNavExpanded = props?isSideNavExpanded
let onClickSideNavExpand = props?onClickSideNavExpand
JSX.html $"""
<Header aria-label="Main header">
<SkipToContent />
<HeaderMenuButton
aria-label="Open menu"
isActive={isSideNavExpanded}
onClick={onClickSideNavExpand}
/>
<HeaderName href="/catalog" prefix="IO">
Oceanbox
</HeaderName>
<HeaderNavigation aria-label="Navigation">
<HeaderMenuItem href="/">Atlantis</HeaderMenuItem>
<HeaderMenuItem href="/catalog">Home</HeaderMenuItem>
<HeaderMenuItem href="../mui/index.html">Mui</HeaderMenuItem>
<HeaderMenuItem href="index.html">Carbon</HeaderMenuItem>
<HeaderMenuItem href="../fluentui/index.html">FluentUI</HeaderMenuItem>
</HeaderNavigation>
<HeaderGlobalBar>
<HeaderGlobalAction
aria-label="Notifications"
>
<NotificationIcon size={20} />
</HeaderGlobalAction>
<HeaderGlobalAction
aria-label="App switcher"
>
<SwitcherIcon size={20} />
</HeaderGlobalAction>
</HeaderGlobalBar>
<SideNav
aria-label="Side navigation"
isPersistent={true}
expanded={isSideNavExpanded}
>
<SideNavItems>
<HeaderSideNavItems>
<HeaderMenuItem href="/">Atlantis</HeaderMenuItem>
<HeaderMenuItem href="/catalog">Home</HeaderMenuItem>
<HeaderMenuItem href="../mui/index.html">Mui</HeaderMenuItem>
<HeaderMenuItem href="index.html">Carbon</HeaderMenuItem>
<HeaderMenuItem href="../fluentui/index.html">FluentUI</HeaderMenuItem>
</HeaderSideNavItems>
<SideNavLink
href="#"
renderIcon={HomeIcon}
isActive={currentPage = ""}
onClick={navigate "home"}
>
Home
</SideNavLink>
<SideNavLink
href="#oceanography"
renderIcon={BuoyIcon}
isActive={currentPage = "#oceanography"}
onClick={navigate "oceanography"}
>
Oceanography
</SideNavLink>
<SideNavLink href="#compute" renderIcon={IbmCloudHpcIcon}>
Compute
</SideNavLink>
<SideNavDivider />
</SideNavItems>
</SideNav>
</Header>
"""
JSX.html $"""
<HeaderContainer render={inner}>
</HeaderContainer>
"""

View File

@@ -0,0 +1,26 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Fable - Carbon</title>
<meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" href="../react.svg" />
<link href="../index.css" rel="stylesheet">
<link href="style.scss" rel="stylesheet">
</head>
<body>
<header>
<nav class="navigation">
<a href="/">Atlantis</a>
<a href="/catalog">Home</a>
<a href="../mui/index.html">Mui</a>
<a href="index.html">Carbon</a>
<a href="../fluentui/index.html">FluentUI</a>
</nav>
</header>
<div id="root"></div>
<script type="module" src="../../build/catalog/carbon/Index.jsx"></script>
</body>
</html>

View File

@@ -0,0 +1,35 @@
/* TODO(simkir): Import only what I use */
@use "@carbon/react" with (
$font-path: "@ibm/plex"
);
@use "ol/ol";
html,
body,
#root,
main {
height: 100%;
}
p {
text-align: justify;
}
#map {
width: 100%;
min-height: 320px;
height: 100%;
}
.navigation {
display: none;
}
.home-grid {
row-gap: 16px;
}
.oceanography-grid {
height: 100%;
}

View File

@@ -0,0 +1,176 @@
module Oceanbox.Catalog.FluentUI.Archives
open Browser
open Fable.Core
open Fable.Core.JsInterop
open Feliz
open Oceanbox.Catalog
open Remoting
let private createTableColumn: obj -> unit = import "createTableColumn" "@fluentui/react-components"
let private Button: obj = import "Button" "@fluentui/react-components"
let private DataGrid: obj = import "DataGrid" "@fluentui/react-components"
let private DataGridBody: obj = import "DataGridBody" "@fluentui/react-components"
let private DataGridCell: obj = import "DataGridCell" "@fluentui/react-components"
let private DataGridHeader: obj = import "DataGridHeader" "@fluentui/react-components"
let private DataGridHeaderCell: obj = import "DataGridHeaderCell" "@fluentui/react-components"
let private DataGridRow: obj = import "DataGridRow" "@fluentui/react-components"
let private Dialog: obj = import "Dialog" "@fluentui/react-components"
let private DialogActions: obj = import "DialogActions" "@fluentui/react-components"
let private DialogBody: obj = import "DialogBody" "@fluentui/react-components"
let private DialogContent: obj = import "DialogContent" "@fluentui/react-components"
let private DialogSurface: obj = import "DialogSurface" "@fluentui/react-components"
let private DialogTitle: obj = import "DialogTitle" "@fluentui/react-components"
let private DialogTrigger: obj = import "DialogTrigger" "@fluentui/react-components"
let private TableCellLayout: obj = import "TableCellLayout" "@fluentui/react-components"
type private NameCell = {
label: string
}
type private LastUpdatedCell = {
timestamp: System.DateTime
}
type private Item = {
Name: NameCell
LastUpdated: LastUpdatedCell
}
let private items: Item array = [|
{ Name = { label = "test" }; LastUpdated = { timestamp = System.DateTime.Now } }
{ Name = { label = "test1" }; LastUpdated = { timestamp = System.DateTime.Now } }
{ Name = { label = "test2" }; LastUpdated = { timestamp = System.DateTime.Now } }
{ Name = { label = "test3" }; LastUpdated = { timestamp = System.DateTime.Now } }
|]
let columns: obj array = [|
createTableColumn {|
columnId = "Name"
renderHeaderCell = fun () -> "Name"
renderCell = fun (item: obj) ->
JSX.html $"""
<TableCellLayout>
{item?Name?label}
</TableCellLayout>
"""
|}
createTableColumn {|
columnId = "LastUpdated"
renderHeaderCell = fun () -> "Last updated"
renderCell = fun (item: Item) -> Utils.toLocaleString item.LastUpdated.timestamp
|}
|]
let fetchHelloWorldArchive () =
let archiveUrl = sessionStorage["archmaester_url"]
async {
if isNullOrUndefined archiveUrl then
return None
else
let api = ArchivesApi archiveUrl
return Some "hello"
}
let fetchArchives () =
async {
do! Utils.initAtlantisSessionUrls ()
let! _ = fetchHelloWorldArchive ()
return ()
}
[<JSX.Component>]
let Table() =
let renderCell (item: obj) (props: obj) =
JSX.html $"""
<DataGridCell>{props?renderCell item}</DataGridCell>
"""
let renderHeader props =
let renderHeaderCell: unit -> obj = props?renderHeaderCell
JSX.html $"""
<DataGridHeaderCell>{renderHeaderCell()}</DataGridHeaderCell>
"""
let renderRow row =
let item = row?item
let rowId: int = row?rowId
JSX.html $"""
<DataGridRow
key={rowId}
>
{renderCell item}
</DataGridRow>
"""
JSX.html $"""
<DataGrid
items={items}
columns={columns}
getRowId={fun item -> item?Name?label}
style={{{{minWidth: "550px"}}}}
>
<DataGridHeader>
<DataGridRow>
{renderHeader}
</DataGridRow>
</DataGridHeader>
<DataGridBody>
{renderRow}
</DataGridBody>
</DataGrid>
"""
[<JSX.Component>]
let private DialogTest () =
JSX.html """
<Dialog>
<DialogTrigger disableButtonEnchancement>
<Button appearance="primary">Dialog</Button>
</DialogTrigger>
<DialogSurface>
<DialogBody>
<DialogTitle>Dialog Title</DialogTitle>
<DialogContent>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Rhoncus dolor purus non enim praesent elementum facilisis leo vel. Risus at ultrices mi tempus imperdiet. Semper risus in hendrerit gravida rutrum quisque non tellus. Convallis convallis tellus id interdum velit laoreet id donec ultrices. Odio morbi quis commodo odio aenean sed adipiscing. Amet nisl suscipit adipiscing bibendum est ultricies integer quis. Cursus euismod quis viverra nibh cras. Metus vulputate eu scelerisque felis imperdiet proin fermentum leo. Mauris commodo quis imperdiet massa tincidunt. Cras tincidunt lobortis feugiat vivamus at augue. At augue eget arcu dictum varius duis at consectetur lorem. Velit sed ullamcorper morbi tincidunt. Lorem donec massa sapien faucibus et molestie ac.
</DialogContent>
<DialogActions>
<Button appearance="primary">Do something</Button>
<DialogTrigger disableButtonEnchancement>
<Button appearance="secondary">Close</Button>
</DialogTrigger>
</DialogActions>
</DialogBody>
</DialogSurface>
</Dialog>
"""
[<ReactComponent>]
let View () =
React.useEffectOnce (fun () ->
fetchArchives ()
|> Async.StartImmediate
)
Html.div [
prop.style [
style.padding (length.px 16)
]
prop.children [
Html.h1 "Archives"
Table() |> Utils.toReact
DialogTest () |> Utils.toReact
]
]

View File

@@ -0,0 +1,140 @@
module Oceanbox.Catalog.FluentUI.App
open Browser
open Fable.Core
open Fable.Core.JsInterop
open Feliz
open Feliz.UseMediaQuery
open Feliz.Router
open Oceanbox.Catalog
let private webLightTheme: obj = import "webLightTheme" "@fluentui/react-components"
let private makeStyles: obj -> obj = import "makeStyles" "@fluentui/react-components"
let private bundleIcon: obj -> obj = import "bundleIcon" "@fluentui/react-icons"
let private AppItem: obj = import "AppItem" "@fluentui/react-components"
let private Button: obj = import "Button" "@fluentui/react-components"
let private FluentProvider: obj = import "FluentProvider" "@fluentui/react-components"
let private Hamburger: obj = import "Hamburger" "@fluentui/react-components"
let private NavDrawer: obj = import "NavDrawer" "@fluentui/react-components"
let private NavDrawerBody: obj = import "NavDrawerBody" "@fluentui/react-components"
let private NavDrawerHeader: obj = import "NavDrawerHeader" "@fluentui/react-components"
let private NavDivider: obj = import "NavDivider" "@fluentui/react-components"
let private NavItem: obj = import "NavItem" "@fluentui/react-components"
let private Tooltip: obj = import "Tooltip" "@fluentui/react-components"
let private WaterRegularIcon: obj = import "Water20Regular" "@fluentui/react-icons"
let private ArchiveRegularIcon: obj = import "Archive20Regular" "@fluentui/react-icons"
let private useStyles: obj = makeStyles {|
root = {|
height = "100%"
flexGrow = "1"
|}
appIcon = {|
width = "32px"
height = "32px"
|}
navDrawer = {|
borderRight = "2px solid gainsboro"
|}
portalContainer = {|
position = "fixed"
top = "0.5em"
left = "2.5em"
|}
|}
[<JSX.Component>]
let View () =
let styles: obj = emitJsExpr () "useStyles()"
let responsive = React.useResponsive ()
let currentUrl, updateUrl = React.useState(Router.currentUrl())
// TODO(simkir): Close nav when smaller screen
let isOpen, setOpen = React.useState true
let screenLarge =
responsive = ScreenSize.WideScreen
|| responsive = ScreenSize.Desktop
|| responsive = ScreenSize.Tablet
console.debug("Router currentUrl: %o", currentUrl)
let router =
React.router [
router.onUrlChanged updateUrl
router.children [
match currentUrl with
| [ "oceanography" ] -> Oceanography.View isOpen (fun () -> setOpen true) |> Utils.toReact
| [ "archives" ] -> Archives.View()
| otherwise -> Html.h1 "404 Not found"
]
]
let pageId =
match currentUrl with
| [ "oceanography" ] -> "1"
| [ "archives" ] -> "2"
| otherwise -> "1"
JSX.html $"""
<FluentProvider id="fluent-provider" className={styles?provider} theme={webLightTheme}>
<NavDrawer
className={styles?navDrawer}
open={screenLarge && isOpen}
type="inline"
position="start"
selectedValue={pageId}
>
<NavDrawerHeader>
<Tooltip relationship="label" content="Close navigation">
<Hamburger onClick={fun () -> setOpen (not isOpen)} />
</Tooltip>
</NavDrawerHeader>
<NavDrawerBody>
<AppItem as="a" href="/">
<img className={styles?appIcon} src="../ob.svg"></img>
Oceanbox hmr test
</AppItem>
<AppItem as="a" href="/catalog">
Home
</AppItem>
<AppItem as="a" href="../mui/index.html">
Mui
</AppItem>
<AppItem as="a" href="../carbon/index.html">
Carbon
</AppItem>
<AppItem as="a" href="index.html">
FluentUI
</AppItem>
<NavDivider />
<NavItem
value="1"
icon={{<WaterRegularIcon />}}
onClick={fun () -> Router.navigate "oceanography"}
>
Oceanography
</NavItem>
<NavItem
value="2"
icon={{<ArchiveRegularIcon />}}
onClick={fun () -> Router.navigate "archives"}
>
Archives
</NavItem>
</NavDrawerBody>
</NavDrawer>
<main className={styles?root}>
{router}
</main>
</FluentProvider>
"""

View File

@@ -0,0 +1,21 @@
module Oceanbox.Catalog.FluentUI.Index
open Browser
open Fable.Core
open Fable.Core.JsInterop
open Feliz
open Oceanbox.Catalog
let React : obj = importAll "react"
let root = ReactDOM.createRoot (document.getElementById "root")
root.render (
JSX.html
$"""
<React.StrictMode>
{App.View () |> Utils.toReact}
</React.StrictMode>
"""
|> Utils.toReact
)

View File

@@ -0,0 +1,60 @@
module Oceanbox.Catalog.FluentUI.Oceanography
open Browser
open Fable.Core
open Fable.Core.JsInterop
open Fable.OpenLayers
open Feliz
open Oceanbox
let private makeStyles: obj -> obj = import "makeStyles" "@fluentui/react-components"
let private Button: obj = import "Button" "@fluentui/react-components"
let private Portal: obj = import "Portal" "@fluentui/react-components"
let private WindowColumnOneFourthLeftFocusLeftFilledIcon: obj = import "WindowColumnOneFourthLeftFocusLeftFilled" "@fluentui/react-icons"
let private useStyles: obj = makeStyles {|
portalContainer = {|
position = "fixed"
top = "0.5em"
left = "2.5em"
|}
|}
[<JSX.Component>]
let View sideNavOpen onClick =
let styles: obj = emitJsExpr () "useStyles()"
let mountNode, setMountNode = React.useState<Types.HTMLElement option> None
let mapBackButton =
if sideNavOpen then
JSX.nothing
else
JSX.html $"""
<Portal mountNode={mountNode}>
<div className={styles?portalContainer}>
<Button
icon={{<WindowColumnOneFourthLeftFocusLeftFilled />}}
onClick={onClick}
>
</Button>
</div>
</Portal>
"""
React.useEffectOnce (fun () ->
console.debug("Mounting Oceanography")
do Catalog.Map.create ()
()
)
JSX.html $"""
<>
<div id="map" ref={setMountNode}></div>
{mapBackButton}
</>
"""

View File

@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Fable - FluentUI</title>
<meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" href="../react.svg" />
<link href="../index.css" rel="stylesheet">
<link href="style.scss" rel="stylesheet">
</head>
<body>
<nav id="catalog-nav">
<a href="/">Atlantis</a>
<a href="/catalog">Home</a>
<a href="../mui/index.html">Mui</a>
<a href="../carbon/index.html">Carbon</a>
<a href="index.html">FluentUI</a>
</nav>
<div id="root"></div>
<script type="module" src="../../build/catalog/fluentui/Index.jsx"></script>
</body>
</html>

View File

@@ -0,0 +1,26 @@
@use "ol/ol";
html, body, #root, #fluent-provider {
height: 100%;
}
body {
margin: 0;
}
main {
padding: 0;
}
#catalog-nav {
display: none;
}
div #fluent-provider {
display: flex;
}
#map {
width: 100%;
height: 100%;
}

View File

@@ -0,0 +1,11 @@
nav {
width: 100%;
background-color: white;
border-bottom: solid gainsboro;
}
main {
padding: 8px;
}

View File

@@ -0,0 +1,25 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Catalog</title>
<meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" href="react.svg" />
<link href="index.css" rel="stylesheet">
</head>
<body>
<header>
<nav class="navigation">
<a href="/">Atlantis</a>
<a href="/catalog">Home</a>
<a href="/catalog/mui/index.html">Mui</a>
<a href="/catalog/carbon/index.html">Carbon</a>
<a href="/catalog/fluentui/index.html">FluentUI</a>
</nav>
</header>
<main>
<p>Welcome to this oceanbox playground. Click on one of the component libraries to test their feel and look</p>
</main>
</body>
</html>

View File

@@ -0,0 +1,25 @@
module Oceanbox.Catalog.Mui.Index
open Browser
open Fable.Core
open Fable.Core.JsInterop
open Feliz
open Oceanbox.Catalog
importAll "react"
import "StyledEngineProvider" "@mui/material/styles"
let root = ReactDOM.createRoot (document.getElementById "root")
root.render (
JSX.html
$"""
<react.StrictMode>
<StyledEngineProvider injectFirst>
{App.View () |> Utils.toReact}
</StyledEngineProvider>
</react.StrictMode>
"""
|> Utils.toReact
)

View File

@@ -0,0 +1,292 @@
module Oceanbox.Catalog.Mui.App
open Browser
open Fable.Core
open Fable.Core.JsInterop
open Feliz
open Oceanbox.Catalog
importSideEffects "@fontsource/roboto/300.css"
importSideEffects "@fontsource/roboto/400.css"
importSideEffects "@fontsource/roboto/500.css"
importSideEffects "@fontsource/roboto/700.css"
let private MuiAppBar: obj = importDefault "@mui/material/AppBar"
importDefault "@mui/material/Box"
importDefault "@mui/material/Button"
importDefault "@mui/material/CssBaseline"
importDefault "@mui/material/Divider"
importDefault "@mui/material/Drawer"
importDefault "@mui/material/IconButton"
importDefault "@mui/material/List"
importDefault "@mui/material/ListItem"
importDefault "@mui/material/ListItemButton"
importDefault "@mui/material/ListItemIcon"
importDefault "@mui/material/ListItemText"
importDefault "@mui/material/Toolbar"
importDefault "@mui/material/Typography"
// NOTE: Renaming the import without mangling the name
let private ChevronRightIcon: obj = importDefault "@mui/icons-material/ChevronRight"
let private MenuIcon: obj = importDefault "@mui/icons-material/Menu"
let private StorageIcon: obj = importDefault "@mui/icons-material/Storage"
let private WaterIcon: obj = importDefault "@mui/icons-material/Water"
let private QueryStatsIcon: obj = importDefault "@mui/icons-material/QueryStats"
let private ThemeProvider: obj = import "ThemeProvider" "@mui/material/styles"
let private createTheme: obj -> unit = import "createTheme" "@mui/material/styles"
let private useTheme: unit -> unit = import "useTheme" "@mui/material/styles"
let private styled (comp: string) : obj -> unit = import "styled" "@mui/material/styles"
let private styledWithOptions (comp: string) (options: obj) : obj -> unit = import "styled" "@mui/material/styles"
type Style =
[<Import("styled", "@mui/material/styles")>]
static member inline styled(comp: string, ?options: obj) : obj -> obj = jsNative
[<Import("styled", "@mui/material/styles")>]
static member inline styled(comp: obj, ?options: obj) : obj -> obj = jsNative
let private drawerWidth = 260
let private darkTheme =
createTheme (createObj [ "palette" ==> createObj [ "mode" ==> "light" ] ])
let private MyComponent =
Style.styled
("div", {| shouldForwardProp = fun prop -> prop <> "open" |})
(Utils.toStyle [
style.color "darkslategray"
style.backgroundColor "aliceblue"
style.padding 8
style.borderRadius 4
])
let private Main =
styledWithOptions "main" {| shouldForwardProp = fun prop -> prop <> "open" |} (fun (props: obj) ->
let theme = props?theme
Utils.toStyle [
style.flexGrow 1
style.padding (props?theme?spacing 3 |> unbox<Styles.ICssUnit>)
style.custom (
"transition", props?theme?transitions?create("margin", createObj [
"easing" ==> theme?transitions?easing?sharp
"duration" ==> theme?transitions?duration?leavingScreen
])
)
style.marginLeft (length.px -drawerWidth)
style.custom (
"variants", [|
createObj [
"props" ==> fun props -> props?``open``
"style" ==> Utils.toStyle [
style.custom (
"transition", props?theme?transitions?create("margin", createObj [
"easing" ==> theme?transitions?easing?easeOut
"duration" ==> theme?transitions?duration?enteringScreen
])
)
style.marginLeft 0
]
]
|]
)
])
let private AppBar =
let f (props: obj) =
let theme = props?theme
Utils.toStyle [
style.custom (
"transition", props?theme?transitions?create(("margin", "width"), createObj [
"easing" ==> theme?transitions?easing?sharp
"duration" ==> theme?transitions?duration?leavingScreen
])
)
style.custom (
"variants", [|
createObj [
"props" ==> fun props -> props?``open``
"style" ==> Utils.toStyle [
style.width (length.calc $"100%% - {drawerWidth}px")
style.marginLeft (length.px drawerWidth)
style.custom (
"transition", props?theme?transitions?create(("margin", "width"), createObj [
"easing" ==> theme?transitions?easing?easeOut
"duration" ==> theme?transitions?duration?enteringScreen
])
)
]
]
|]
)
]
Style.styled(MuiAppBar, {| shouldForwardProp = fun prop -> prop <> "open" |}) f
let private DrawerHeader: unit =
styled "div" (fun (props: obj) ->
let theme: obj = props?theme
let toolbar = Utils.spreadStyles theme?mixins?toolbar
let style = [|
style.display.flex
style.alignItems.center
style.justifyContent.flexEnd
style.padding (theme?spacing (0, 1) |> unbox<Styles.ICssUnit>)
|]
console.debug("[Mui] Styled DrawerHeader theme %o, toolbar: %o", theme, toolbar)
// NOTE(simkir): Not sure how to do spreading in a nice way. If you do `yield!` Fable turns the expression lazy,
// kinda.
Utils.toStyle (Array.append style toolbar))
let pages = [|
"/", "Atlantis"
"/catalog/index.html", "Home"
"index.html", "Mui"
"../carbon/index.html", "Carbon"
"../fluentui/index.html", "FluentUI"
|]
[<JSX.Component>]
let View () =
let theme = useTheme ()
let isOpen, setOpen = React.useState false
let handleDrawerOpen () = setOpen true
let handleDrawerClose () = setOpen false
let linkButton (url: string, str: string) =
JSX.html $"""
<Button
href={url}
sx={
Utils.toStyle [
style.color "white"
]
}
>
{str}
</Button>
"""
JSX.html
$"""
<ThemeProvider theme={darkTheme}>
<Box sx={Utils.toStyle [ style.display.flex ]}>
<CssBaseline />
<AppBar position="fixed" open={isOpen}>
<Toolbar>
<IconButton
onClick={handleDrawerOpen}
sx={Utils.toStyle [
style.custom ("mr", 2)
if isOpen then style.display.none
]}
>
<MenuIcon />
</IconButton>
<Typography
variant="h5"
sx={
Utils.toStyle [
style.custom ("mr", 2)
]
}
>
Oceanbox
</Typography>
<Box
sx={
Utils.toStyle [
style.flexGrow 1
]
}
>
{pages |> Array.map linkButton}
</Box>
</Toolbar>
</AppBar>
<Drawer
sx={
Utils.toStyle [
style.width (length.px drawerWidth)
style.flexShrink 0
style.custom (
"& .MuiDrawer-paper",
Utils.toStyle [
style.width drawerWidth
style.boxSizing.borderBox
]
)
]
}
variant="persistent"
anchor="left"
open={isOpen}
>
<DrawerHeader>
<IconButton
onClick={handleDrawerClose}
>
<ChevronRightIcon />
</IconButton>
</DrawerHeader>
<Divider />
<List>
<ListItem key="oceanography" disablePadding>
<ListItemButton>
<ListItemIcon>
<WaterIcon />
</ListItemIcon>
<ListItemText primary="Oceanography" />
</ListItemButton>
</ListItem>
<ListItem key="compute" disablePadding>
<ListItemButton>
<ListItemIcon>
<StorageIcon />
</ListItemIcon>
<ListItemText primary="Compute" />
</ListItemButton>
</ListItem>
<ListItem key="statistics" disablePadding>
<ListItemButton>
<ListItemIcon>
<QueryStatsIcon />
</ListItemIcon>
<ListItemText primary="Statistics" />
</ListItemButton>
</ListItem>
</List>
</Drawer>
<Main open={isOpen}>
<DrawerHeader />
<Typography >
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Rhoncus dolor purus non
enim praesent elementum facilisis leo vel. Risus at ultrices mi tempus
imperdiet. Semper risus in hendrerit gravida rutrum quisque non tellus.
Convallis convallis tellus id interdum velit laoreet id donec ultrices.
Odio morbi quis commodo odio aenean sed adipiscing. Amet nisl suscipit
adipiscing bibendum est ultricies integer quis. Cursus euismod quis viverra
nibh cras. Metus vulputate eu scelerisque felis imperdiet proin fermentum
leo. Mauris commodo quis imperdiet massa tincidunt. Cras tincidunt lobortis
feugiat vivamus at augue. At augue eget arcu dictum varius duis at
consectetur lorem. Velit sed ullamcorper morbi tincidunt. Lorem donec massa
sapien faucibus et molestie ac.
</Typography>
<Button variant="contained">Hello, Mui</Button>
<MyComponent>Styled div</MyComponent>
</Main>
</Box>
</ThemeProvider>
"""

View File

@@ -0,0 +1,26 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Playground - Mui</title>
<meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" href="../react.svg" />
<link href="../index.css" rel="stylesheet">
<link href="style.scss" rel="stylesheet">
</head>
<body>
<header>
<nav class="navigation">
<a href="/">Atlantis</a>
<a href="/catalog/index.html">Home</a>
<a href="index.html">Mui</a>
<a href="../carbon/index.html">Carbon</a>
<a href="../fluentui/index.html">FluentUI</a>
</nav>
</header>
<div id="root"></div>
<script type="module" src="../../build/catalog/mui/Index.jsx"></script>
</body>
</html>

View File

@@ -0,0 +1,8 @@
.navigation {
display: none;
}
.drawer-header {
display: flex;
justify-content: flex-end;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@@ -0,0 +1,32 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 850.39 566.93">
<defs>
<style>
.cls-1 {
fill: #003f70;
}
.cls-2 {
fill: #7296b7;
}
</style>
</defs>
<g>
<g>
<circle class="cls-1" cx="296.72" cy="235.29" r="33.2"/>
<circle class="cls-1" cx="338.77" cy="190.14" r="21.65"/>
<g>
<path class="cls-1" d="M505.8,293.18A66.28,66.28,0,0,0,439.6,227v-48.3a114.5,114.5,0,0,0,0,229v-48.3A66.27,66.27,0,0,0,505.8,293.18Zm-66.2,63a63,63,0,1,1,63-63A63.09,63.09,0,0,1,439.6,356.2Z"/>
<g>
<path class="cls-2" d="M439.6,185.56v4c57,0,103.38,46.48,103.38,103.61S496.6,396.78,439.6,396.78v4c59.21,0,107.38-48.27,107.38-107.61S498.81,185.56,439.6,185.56Z"/>
<path class="cls-2" d="M439.6,192.44v3.86c53.3,0,96.66,43.46,96.66,96.88s-43.36,96.87-96.66,96.87v3.87c55.42,0,100.51-45.19,100.51-100.74S495,192.44,439.6,192.44Z"/>
<path class="cls-2" d="M439.6,199.32V203c49.6,0,89.95,40.44,89.95,90.15s-40.35,90.14-89.95,90.14V387c51.64,0,93.65-42.11,93.65-93.86S491.24,199.32,439.6,199.32Z"/>
<path class="cls-2" d="M439.6,206.2v3.56a83.42,83.42,0,0,1,0,166.83v3.57a87,87,0,0,0,0-174Z"/>
<path class="cls-2" d="M439.6,213.07v3.42a76.69,76.69,0,0,1,0,153.37v3.42a80.11,80.11,0,0,0,0-160.21Z"/>
<path class="cls-2" d="M439.6,220v3.27a70,70,0,0,1,0,139.91v3.27a73.23,73.23,0,0,0,0-146.45Z"/>
<path class="cls-2" d="M439.6,226.83V230a63.23,63.23,0,0,1,0,126.46v3.12a66.35,66.35,0,0,0,0-132.7Z"/>
<path class="cls-2" d="M439.6,178.68h0v4.16h0c60.7,0,110.09,49.5,110.09,110.34S500.3,403.51,439.6,403.51h0v4.16h0c63,0,114.24-51.36,114.24-114.49S502.59,178.68,439.6,178.68Z"/>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-11.5 -10.23174 23 20.46348">
<title>React Logo</title>
<circle cx="0" cy="0" r="2.05" fill="#61dafb"/>
<g stroke="#61dafb" stroke-width="1" fill="none">
<ellipse rx="11" ry="4.2"/>
<ellipse rx="11" ry="4.2" transform="rotate(60)"/>
<ellipse rx="11" ry="4.2" transform="rotate(120)"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 366 B

View File

@@ -0,0 +1,7 @@
User-agent: Googlebot
Disallow: /nogooglebot/
User-agent: *
Allow: /
Sitemap: https://www.example.com/sitemap.xml

View File

@@ -0,0 +1,3 @@
#!/usr/bin/env bash
dotnet fable watch --verbose -e ".jsx" --run bunx --bun vite

View File

@@ -0,0 +1,7 @@
# Generated by Cargo
# will have compiled files and executables
/target/
# Generated by Tauri
# will have schema files for capabilities auto-completion
/gen/schemas

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,21 @@
[package]
name = "atlantis"
version = "0.1.0"
description = "A Tauri App"
authors = ["Moritz Jörg"]
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
name = "atlantis_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
[build-dependencies]
tauri-build = { version = "2", features = [] }
[dependencies]
tauri = { version = "2", features = [] }
tauri-plugin-shell = "2"
serde_json = "1"
serde = { version = "1", features = ["derive"] }

View File

@@ -0,0 +1,3 @@
fn main() {
tauri_build::build()
}

View File

@@ -0,0 +1,9 @@
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "Capability for the main window",
"windows": ["main"],
"permissions": [
"core:default"
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 974 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 903 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,13 @@
#[tauri::command]
fn greet(name: &str) -> String {
format!("Hello {name}, You have been greeted from Rust!")
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_shell::init())
.invoke_handler(tauri::generate_handler![greet])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}

View File

@@ -0,0 +1,6 @@
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
fn main() {
atlantis_lib::run()
}

View File

@@ -0,0 +1,37 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "atlantis",
"version": "0.1.0",
"identifier": "com.oceanbox.atlantis",
"build": {
"beforeDevCommand": "./run",
"devUrl": "http://localhost:8081",
"beforeBuildCommand": "bun run build",
"frontendDist": "../"
},
"app": {
"windows": [
{
"title": "Atlantis",
"width": 800,
"height": 600,
"resizable": true,
"fullscreen": false
}
],
"security": {
"csp": null
}
},
"bundle": {
"active": true,
"targets": "all",
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
]
}
}

View File

@@ -0,0 +1,76 @@
import mkcert from "vite-plugin-mkcert"
import react from "@vitejs/plugin-react"
import { defineConfig } from "vite"
const certDir = `${process.env.HOME}/.vite-plugin-mkcert`;
const host = process.env.TAURI_DEV_HOST;
const proxy = {
target: `http://127.0.0.1:8085/`,
changeOrigin: false,
secure: false,
ws: true
};
export default defineConfig({
appType: "mpa",
clearScreen: false,
plugins: [
react(),
mkcert({
hosts: [
"localhost",
"*.local.oceanbox.io"
],
savePath: `${certDir}/certs`,
mkcertPath: `${certDir}/mkcert`
}),
],
build: {
rollupOptions: {
input: {
index: "index.html",
carbon: "carbon/index.html",
mui: "mui/index.html",
fluentui: "fluentui/index.html",
},
},
},
server: {
port: 8081,
// host: "0.0.0.0",
strictPort: true,
https: false,
host: host || false,
hmr: host
? {
protocol: "ws",
host,
port: 8081,
}
: undefined,
// https: true,
// cors: true,
watch: {
// 3. tell Vite to ignore watching `src-tauri` and `fsharp`
ignored: [
"**/src-tauri/**",
"**/*.fs"
],
},
proxy: {
'/socket': proxy,
"/api": proxy,
'/isAuthenticated': proxy,
'/signin-oidc': proxy,
'/signin': proxy,
'/signout': proxy,
'/token': proxy,
'/claims': proxy,
'/impersonate': proxy,
'/unimpersonate': proxy,
'/hub': proxy,
'/barentswatch-token': proxy,
},
},
})

View File

@@ -21,19 +21,6 @@
</head>
<body>
<!-- <script> -->
<!-- if (window.location.pathname == "/map") { -->
<!-- var app = document.createElement("map-app"); -->
<!-- } else if (window.location.pathname == "/atlas") { -->
<!-- var app = document.createElement("atlas-app"); -->
<!-- } else if (window.location.pathname == "/login") { -->
<!-- var app = document.createElement("login-app"); -->
<!-- } else { -->
<!-- var app = document.createElement("init-app"); -->
<!-- }; -->
<!-- app.setAttribute("style", "height: 100%;"); -->
<!-- document.body.appendChild(app); -->
<!-- </script> -->
<init-app id="app" style="height: 100%;"></init-app>
<script type="module" src="./build/App.jsx"></script>
</body>

View File

@@ -18,78 +18,83 @@ var proxy = {
};
const plugins = [
react({jsxRuntime: "automatic"}),
mkcert({
hosts: [
"localhost",
"*.local.oceanbox.io"
],
savePath: `${certDir}/certs`,
mkcertPath: `${certDir}/mkcert`
}),
!process.env.CREATE_SENTRY
? null
: sentryVitePlugin({
org: "oceanbox",
project: "atlantis-client",
authToken: "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",
filesToDeleteAfterUpload: "*",
sourcemaps: {
ignore: ["../../node_modules/**", "./vite.config.js"]
},
telemetry: false,
}),
react({jsxRuntime: "automatic"}),
mkcert({
hosts: [
"localhost",
"*.local.oceanbox.io"
],
savePath: `${certDir}/certs`,
mkcertPath: `${certDir}/mkcert`
}),
!process.env.CREATE_SENTRY
? null
: sentryVitePlugin({
org: "oceanbox",
project: "atlantis-client",
authToken: "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",
filesToDeleteAfterUpload: "*",
sourcemaps: {
ignore: ["../../node_modules/**", "./vite.config.js"]
},
telemetry: false,
}),
];
export default defineConfig({
plugins: plugins,
resolve: {
alias: {
// We're only using a subset from plotly
// Add alias to enable typing regardless
'plotly.js': resolve(__dirname, './plotly-bundle'),
// The bundler file still needs access to the actual plotly module
'plotly-dist': resolve(__dirname, '../../node_modules/plotly.js')
appType: "mpa",
plugins: plugins,
resolve: {
alias: {
// We're only using a subset from plotly
// Add alias to enable typing regardless
'plotly.js': resolve(__dirname, './plotly-bundle'),
// The bundler file still needs access to the actual plotly module
'plotly-dist': resolve(__dirname, '../../node_modules/plotly.js')
},
},
define: {
global: {},
'process.env': {},
},
build: {
rollupOptions: {
input: {
main: resolve(__dirname, './src/Client/index.html'),
atlas: resolve(__dirname, './src/Client/atlas.html'),
mapster: resolve(__dirname, './src/Client/map.html'),
catalog: resolve(__dirname, "./src/Client/catalog/index.html"),
catalogMui: "catalog/mui/index.html",
catalogCarbon: "catalog/carbon/index.html",
catalogFluentUI: "catalog/fluentui/index.html",
},
},
define: {
global: {},
'process.env': {},
minify: "oxc",
},
// config options
server: {
port: clientPort,
host: '0.0.0.0',
https: true,
cors: true,
proxy: {
'/api': proxy,
'/isAuthenticated': proxy,
'/signin-oidc': proxy,
'/signin': proxy,
'/signout': proxy,
'/token': proxy,
'/claims': proxy,
'/impersonate': proxy,
'/unimpersonate': proxy,
'/hub': proxy,
'/barentswatch-token': proxy,
'/socket': proxy,
},
build: {
rollupOptions: {
input: {
main: resolve(__dirname, './src/Client/index.html'),
atlas: resolve(__dirname, './src/Client/atlas.html'),
mapster: resolve(__dirname, './src/Client/map.html'),
},
},
minify: "oxc",
},
// config options
server: {
port: clientPort,
host: '0.0.0.0',
https: true,
cors: true,
proxy: {
'/api': proxy,
'/isAuthenticated': proxy,
'/signin-oidc': proxy,
'/signin': proxy,
'/signout': proxy,
'/token': proxy,
'/claims': proxy,
'/impersonate': proxy,
'/unimpersonate': proxy,
'/hub': proxy,
'/barentswatch-token': proxy,
'/socket': proxy,
},
watch: {
ignored: [
"**/*.fs" // Don't watch F# files
],
}
watch: {
ignored: [
"**/*.fs" // Don't watch F# files
],
}
})
}
})