Start on archive management in Mui
Table views of model areas and archives
This commit is contained in:
@@ -5,6 +5,8 @@ open Fable.Remoting.Client
|
||||
open Atlantis
|
||||
open Sorcerer
|
||||
|
||||
let getArchiveUrl () = Browser.WebStorage.sessionStorage["archmaester_url"]
|
||||
|
||||
let authApi =
|
||||
Remoting.createApi ()
|
||||
|> Remoting.withCredentials true
|
||||
@@ -124,8 +126,6 @@ let driftersJobApi () =
|
||||
|> Remoting.withRouteBuilder Api.authorizedRouteBuilder
|
||||
|> Remoting.buildProxy<Api.Drifters>
|
||||
|
||||
let getArchiveUrl () = Browser.WebStorage.sessionStorage["archmaester_url"]
|
||||
|
||||
let archiveApi () =
|
||||
Remoting.createApi ()
|
||||
|> Remoting.withCredentials true
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
<Compile Include="carbon/Home.fs" />
|
||||
<Compile Include="carbon/Carbon.fs" />
|
||||
<Compile Include="carbon/Index.fs" />
|
||||
<Compile Include="mui/Archives.fs" />
|
||||
<Compile Include="mui/Mui.fs" />
|
||||
<Compile Include="mui/Index.fs" />
|
||||
<Compile Include="fluentui/ModelAreaTree.fs" />
|
||||
|
||||
@@ -9,6 +9,7 @@ open Remoting
|
||||
let private testAuthenticated () =
|
||||
promise {
|
||||
let! authOpt = authApi.IsAuthenticated () |> Async.StartAsPromise
|
||||
do! Utils.initAtlantisSessionUrls () |> Async.StartAsPromise
|
||||
|
||||
match authOpt with
|
||||
| Some auth ->
|
||||
|
||||
@@ -14,4 +14,4 @@ let inline spreadStyles object : IStyleAttribute array = jsNative
|
||||
let toLocaleString (date: System.DateTime) : string =
|
||||
let jsDate = unbox<JS.Date> date
|
||||
|
||||
jsDate.toLocaleString()
|
||||
jsDate?toLocaleString("en-GB")
|
||||
@@ -203,9 +203,8 @@ let View () =
|
||||
do console.error("[Catalog] Archmaester url is empty!")
|
||||
else
|
||||
fetchModelAreas ()
|
||||
|> Promise.iter (fun archives ->
|
||||
setModelAreas archives
|
||||
)
|
||||
|> Promise.iter setModelAreas
|
||||
|
||||
)
|
||||
|
||||
Html.div [
|
||||
@@ -270,7 +269,7 @@ let View () =
|
||||
Html.div [
|
||||
prop.style [
|
||||
style.flexGrow 1
|
||||
style.minWidth (length.px 256)
|
||||
style.maxWidth (length.px 512)
|
||||
]
|
||||
prop.children (
|
||||
ModelAreaTree.View handleArchiveClick modelAreas |> Utils.toReact
|
||||
@@ -279,14 +278,57 @@ let View () =
|
||||
|
||||
match selectedArchive with
|
||||
| Some archive ->
|
||||
let duration = archive.endTime - archive.startTime
|
||||
|
||||
JSX.html $"""
|
||||
<div className={styles?cardContainer}>
|
||||
<Card key={archive.archiveId} className={styles?card} selected={false}>
|
||||
<CardHeader
|
||||
image={{<BookmarkRegularIcon />}}
|
||||
header={{<FuiText weight="semibold">{archive.name}</FuiText>}}
|
||||
description={{<Caption1>Start time: {archive.startTime.ToShortDateString()}</Caption1>}}
|
||||
/>
|
||||
|
||||
<div>
|
||||
<div>
|
||||
<span>Type: {string archive.archiveType}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>Projection: {archive.projection}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>Default zoom: {archive.defaultZoom}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>Frequency: {archive.freq}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>Frames: {archive.frames}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>Created: {Utils.toLocaleString archive.created}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>Start time: {Utils.toLocaleString archive.startTime}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>End time: {Utils.toLocaleString archive.endTime}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>Duration: {duration.Days} days</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>Owner: {archive.owner}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>Expires: {archive.expires}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>Publised: {if archive.isPublished then "True" else "False"}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>Public: {if archive.isPublic then "True" else "False"}</span>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
"""
|
||||
|
||||
@@ -7,15 +7,30 @@ open Feliz
|
||||
|
||||
open Remoting
|
||||
|
||||
let private makeStyles: obj -> obj = import "makeStyles" "@fluentui/react-components"
|
||||
|
||||
let private Tree: obj = import "Tree" "@fluentui/react-components"
|
||||
let private TreeItem: obj = import "TreeItem" "@fluentui/react-components"
|
||||
let private TreeItemLayout: obj = import "TreeItemLayout" "@fluentui/react-components"
|
||||
let private Tooltip: obj = import "Tooltip" "@fluentui/react-components"
|
||||
let private Spinner: obj = import "Spinner" "@fluentui/react-components"
|
||||
|
||||
let private CheckmarkStarburstRegularIcon: obj = import "CheckmarkStarburstRegular" "@fluentui/react-icons"
|
||||
let private GlobeRegularIcon: obj = import "GlobeRegular" "@fluentui/react-icons"
|
||||
let private LockClosedRegularIcon: obj = import "LockClosedRegular" "@fluentui/react-icons"
|
||||
|
||||
let private useStyles: obj = makeStyles {|
|
||||
tree = {|
|
||||
overflowY = "scroll"
|
||||
maxHeight = "1000px"
|
||||
|}
|
||||
|}
|
||||
|
||||
|
||||
let private fetchArchives (modelAreaId: Archmaester.Dto.ModelAreaId) =
|
||||
promise {
|
||||
let api = getArchiveUrl () |> ArchivesApi
|
||||
let archiveType = Archmaester.Dto.ArchiveType.Fvcom (Archmaester.Dto.FvcomVariant.Any, Archmaester.Dto.FvcomFormat.Any)
|
||||
let archiveType = Archmaester.Dto.ArchiveType.Any
|
||||
|
||||
let! res = api.Archive.getModelAreaArchives(modelAreaId, archiveType) |> Async.StartAsPromise
|
||||
|
||||
@@ -67,14 +82,36 @@ let private ModelAreaLeaf key (area: Archmaester.Dto.ModelArea) (onArchiveClick:
|
||||
if isOpen then
|
||||
let leafs =
|
||||
archives
|
||||
|> Array.sortBy _.name
|
||||
|> Array.map (fun archive ->
|
||||
let beforeIcon =
|
||||
if archive.isPublished then
|
||||
if archive.isPublic then
|
||||
JSX.html """
|
||||
<Tooltip content="Public">
|
||||
<GlobeRegularIcon />
|
||||
</Tooltip>
|
||||
"""
|
||||
else
|
||||
JSX.html """
|
||||
<Tooltip content="Published">
|
||||
<CheckmarkStarburstRegularIcon />
|
||||
</Tooltip>
|
||||
"""
|
||||
else
|
||||
JSX.html """<LockClosedRegularIcon />"""
|
||||
|
||||
JSX.html $"""
|
||||
<TreeItem
|
||||
key={archive.archiveId}
|
||||
itemType="leaf"
|
||||
onClick={handleArchiveClick archive}
|
||||
>
|
||||
<TreeItemLayout>{archive.name}</TreeItemLayout>
|
||||
<TreeItemLayout
|
||||
iconBefore={beforeIcon}
|
||||
>
|
||||
{archive.name}
|
||||
</TreeItemLayout>
|
||||
</TreeItem>
|
||||
"""
|
||||
)
|
||||
@@ -104,13 +141,15 @@ let private ModelAreaLeaf key (area: Archmaester.Dto.ModelArea) (onArchiveClick:
|
||||
|
||||
[<JSX.Component>]
|
||||
let View (onArchiveClick: Archmaester.Dto.ArchiveProps -> unit) (modelAreas: Archmaester.Dto.ModelArea array) =
|
||||
let styles: obj = emitJsExpr () "useStyles()"
|
||||
|
||||
let items =
|
||||
modelAreas
|
||||
|> Array.sortBy _.name
|
||||
|> Array.map (fun area -> ModelAreaLeaf area.modelAreaId area onArchiveClick)
|
||||
|
||||
JSX.html $"""
|
||||
<Tree aria-label="Default">
|
||||
<Tree aria-label="Default" className={styles?tree}>
|
||||
{items}
|
||||
</Tree>
|
||||
"""
|
||||
@@ -1,4 +1,4 @@
|
||||
nav {
|
||||
.navigation {
|
||||
width: 100%;
|
||||
|
||||
background-color: white;
|
||||
|
||||
246
src/Atlantis/src/Client/catalog/mui/Archives.fs
Normal file
246
src/Atlantis/src/Client/catalog/mui/Archives.fs
Normal file
@@ -0,0 +1,246 @@
|
||||
module Oceanbox.Catalog.Mui.Archives
|
||||
|
||||
open Browser
|
||||
open Fable.Core
|
||||
open Fable.Core.JsInterop
|
||||
open Feliz
|
||||
|
||||
open Oceanbox.Catalog
|
||||
open Remoting
|
||||
|
||||
importDefault "@mui/material/Breadcrumbs"
|
||||
importDefault "@mui/material/Button"
|
||||
importDefault "@mui/material/Link"
|
||||
importDefault "@mui/material/Paper"
|
||||
importDefault "@mui/material/Table"
|
||||
importDefault "@mui/material/TableBody"
|
||||
importDefault "@mui/material/TableCell"
|
||||
importDefault "@mui/material/TableContainer"
|
||||
importDefault "@mui/material/TableHead"
|
||||
importDefault "@mui/material/TableRow"
|
||||
importDefault "@mui/material/Typography"
|
||||
|
||||
let private fetchHelloWorldArchive (api: ArchivesApi) =
|
||||
promise {
|
||||
let! helloWorld = api.ModelArea.getModelArea Archmaester.Dto.HelloWorld |> Async.StartAsPromise
|
||||
|
||||
return helloWorld
|
||||
}
|
||||
|
||||
let private fetchModelAreas () =
|
||||
promise {
|
||||
let api = getArchiveUrl () |> ArchivesApi
|
||||
let! helloWorldOpt = fetchHelloWorldArchive api
|
||||
|
||||
match helloWorldOpt with
|
||||
| Some helloWorld ->
|
||||
console.info("[Catalog] Fetched hello world: %o", helloWorld)
|
||||
let! subArchives = api.ModelArea.getSubModelAreas helloWorld.modelAreaId |> Async.StartAsPromise
|
||||
|
||||
return subArchives
|
||||
| None ->
|
||||
console.error "[Catalog] No hello world! No archives!"
|
||||
|
||||
return [||]
|
||||
}
|
||||
|
||||
let private fetchModelAreaArchives (modelArea: Archmaester.Dto.ModelArea) =
|
||||
promise {
|
||||
let api = getArchiveUrl () |> ArchivesApi
|
||||
|
||||
let! subArchivesRes =
|
||||
api.Archive.getModelAreaArchives(modelArea.modelAreaId, Archmaester.Dto.ArchiveType.Any)
|
||||
|> Async.StartAsPromise
|
||||
|
||||
return subArchivesRes
|
||||
}
|
||||
|
||||
[<JSX.Component>]
|
||||
let ModelAreaArchiveTable (modelArea: Archmaester.Dto.ModelArea) (onClick: Archmaester.Dto.ArchiveProps -> unit) =
|
||||
let archives, setArchives = React.useState<Archmaester.Dto.ArchiveProps array> [||]
|
||||
|
||||
React.useEffectOnce (fun () ->
|
||||
fetchModelAreaArchives modelArea
|
||||
|> Promise.iter (function
|
||||
| Ok archives -> setArchives archives
|
||||
| Error err -> console.error("[Catalog] Error fetching model area %s archives %s", modelArea.name, err)
|
||||
)
|
||||
)
|
||||
|
||||
let rows =
|
||||
archives
|
||||
|> Array.sortBy _.name
|
||||
|> Array.map (fun archive ->
|
||||
let expiresStr =
|
||||
archive.expires
|
||||
|> Option.map Utils.toLocaleString
|
||||
|> Option.defaultValue "No expiry"
|
||||
|
||||
JSX.html $"""
|
||||
<TableRow
|
||||
key={archive.archiveId}
|
||||
hover={true}
|
||||
onClick={fun () -> onClick archive}
|
||||
>
|
||||
<TableCell>{archive.name}</TableCell>
|
||||
<TableCell>{string archive.archiveType}</TableCell>
|
||||
<TableCell>{archive.frames}</TableCell>
|
||||
<TableCell>{archive.freq}</TableCell>
|
||||
<TableCell>{archive.projection}</TableCell>
|
||||
<TableCell>{Utils.toLocaleString archive.startTime}</TableCell>
|
||||
<TableCell>{Utils.toLocaleString archive.endTime}</TableCell>
|
||||
<TableCell>{Utils.toLocaleString archive.created}</TableCell>
|
||||
<TableCell>{expiresStr}</TableCell>
|
||||
</TableRow>
|
||||
"""
|
||||
)
|
||||
|
||||
JSX.html $"""
|
||||
<TableContainer component={{Paper}}>
|
||||
<Table size="small">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Name</TableCell>
|
||||
<TableCell>Type</TableCell>
|
||||
<TableCell>Frames</TableCell>
|
||||
<TableCell>Frequency</TableCell>
|
||||
<TableCell>Projection</TableCell>
|
||||
<TableCell>Start</TableCell>
|
||||
<TableCell>End</TableCell>
|
||||
<TableCell>Created</TableCell>
|
||||
<TableCell>Expires</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
|
||||
<TableBody>
|
||||
{rows}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
"""
|
||||
|
||||
[<JSX.Component>]
|
||||
let ModelAreaView (modelArea: Archmaester.Dto.ModelArea) (onBack: unit -> unit) =
|
||||
let selectedArchive, setSelectedArchive = React.useState<Archmaester.Dto.ArchiveProps option> None
|
||||
|
||||
let handleClickBack () =
|
||||
onBack ()
|
||||
let handleArchiveClick (archive: Archmaester.Dto.ArchiveProps) =
|
||||
setSelectedArchive (Some archive)
|
||||
|
||||
let archiveBreadcrumb =
|
||||
match selectedArchive with
|
||||
| Some archive ->
|
||||
JSX.html $"""
|
||||
<Link
|
||||
underline="hover"
|
||||
color="inherit"
|
||||
>
|
||||
{archive.name}
|
||||
</Link>
|
||||
"""
|
||||
| None ->
|
||||
JSX.nothing
|
||||
|
||||
JSX.html $"""
|
||||
<>
|
||||
<div className="archive-header">
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={fun () -> handleClickBack ()}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
|
||||
<Breadcrumbs>
|
||||
<Link
|
||||
underline="hover"
|
||||
color="inherit"
|
||||
onClick={fun () -> setSelectedArchive None}
|
||||
>
|
||||
{modelArea.name}
|
||||
</Link>
|
||||
{archiveBreadcrumb}
|
||||
</Breadcrumbs>
|
||||
</div>
|
||||
|
||||
{
|
||||
match selectedArchive with
|
||||
| Some archive -> JSX.nothing
|
||||
| None -> ModelAreaArchiveTable modelArea handleArchiveClick
|
||||
}
|
||||
</>
|
||||
"""
|
||||
|
||||
[<JSX.Component>]
|
||||
let View () =
|
||||
let selectedModelArea, setSelectedModelArea = React.useState<Archmaester.Dto.ModelArea option> None
|
||||
let modelAreas, setModelAreas = React.useState<Archmaester.Dto.ModelArea array> [||]
|
||||
|
||||
React.useEffectOnce (fun () ->
|
||||
if Utils.strNull (getArchiveUrl ()) then
|
||||
do console.error("[Catalog] Archmaester url is empty!")
|
||||
else
|
||||
// TODO: Cache these somehow?
|
||||
fetchModelAreas ()
|
||||
|> Promise.iter setModelAreas
|
||||
)
|
||||
|
||||
let rows =
|
||||
modelAreas
|
||||
|> Array.sortBy _.name
|
||||
|> Array.map (fun modelArea ->
|
||||
JSX.html $"""
|
||||
<TableRow
|
||||
key={modelArea.modelAreaId}
|
||||
hover={true}
|
||||
onClick={fun () -> setSelectedModelArea (Some modelArea)}
|
||||
>
|
||||
<TableCell>{modelArea.name}</TableCell>
|
||||
<TableCell>{modelArea.archives}</TableCell>
|
||||
<TableCell>{modelArea.models}</TableCell>
|
||||
<TableCell>{modelArea.description}</TableCell>
|
||||
</TableRow>
|
||||
"""
|
||||
)
|
||||
|
||||
let modelAreaTable =
|
||||
JSX.html $"""
|
||||
<TableContainer component={{Paper}}>
|
||||
<Table size="small">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Name</TableCell>
|
||||
<TableCell>Archives</TableCell>
|
||||
<TableCell>Models</TableCell>
|
||||
<TableCell>Description</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
|
||||
<TableBody>
|
||||
{rows}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
"""
|
||||
|
||||
JSX.html $"""
|
||||
<>
|
||||
<Typography
|
||||
variant="h2"
|
||||
sx={{{{
|
||||
paddingBottom: "16px",
|
||||
}}}}
|
||||
>
|
||||
Archives
|
||||
</Typography>
|
||||
|
||||
{
|
||||
match selectedModelArea with
|
||||
| Some modelArea ->
|
||||
ModelAreaView modelArea (fun () -> setSelectedModelArea None)
|
||||
| None ->
|
||||
modelAreaTable
|
||||
}
|
||||
</>
|
||||
"""
|
||||
@@ -17,7 +17,7 @@ root.render (
|
||||
$"""
|
||||
<react.StrictMode>
|
||||
<StyledEngineProvider injectFirst>
|
||||
{App.View () |> Utils.toReact}
|
||||
{App.View ()}
|
||||
</StyledEngineProvider>
|
||||
</react.StrictMode>
|
||||
"""
|
||||
|
||||
@@ -4,6 +4,8 @@ open Browser
|
||||
open Fable.Core
|
||||
open Fable.Core.JsInterop
|
||||
open Feliz
|
||||
open Feliz.Router
|
||||
open Feliz.UseMediaQuery
|
||||
|
||||
open Oceanbox.Catalog
|
||||
|
||||
@@ -17,7 +19,7 @@ importDefault "@mui/material/Box"
|
||||
importDefault "@mui/material/Button"
|
||||
importDefault "@mui/material/CssBaseline"
|
||||
importDefault "@mui/material/Divider"
|
||||
importDefault "@mui/material/Drawer"
|
||||
let private MuiDrawer : obj = importDefault "@mui/material/Drawer"
|
||||
importDefault "@mui/material/IconButton"
|
||||
importDefault "@mui/material/List"
|
||||
importDefault "@mui/material/ListItem"
|
||||
@@ -28,11 +30,14 @@ 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 ChevronLeftIcon: obj = importDefault "@mui/icons-material/ChevronLeft"
|
||||
let private HomeIcon: obj = importDefault "@mui/icons-material/Home"
|
||||
let private InventoryIcon: obj = importDefault "@mui/icons-material/Inventory"
|
||||
let private MapIcon: obj = importDefault "@mui/icons-material/Map"
|
||||
let private MenuIcon: obj = importDefault "@mui/icons-material/Menu"
|
||||
let private QueryStatsIcon: obj = importDefault "@mui/icons-material/QueryStats"
|
||||
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"
|
||||
@@ -52,6 +57,27 @@ let private drawerWidth = 260
|
||||
let private darkTheme =
|
||||
createTheme (createObj [ "palette" ==> createObj [ "mode" ==> "light" ] ])
|
||||
|
||||
let private openedMixin theme : obj = emitJsExpr theme """({
|
||||
width: drawerWidth,
|
||||
transition: $0.transitions.create('width', {
|
||||
easing: $0.transitions.easing.sharp,
|
||||
duration: $0.transitions.duration.enteringScreen,
|
||||
}),
|
||||
overflowX: 'hidden',
|
||||
})"""
|
||||
|
||||
let private closedMixin theme = emitJsExpr theme """({
|
||||
transition: $0.transitions.create('width', {
|
||||
easing: $0.transitions.easing.sharp,
|
||||
duration: $0.transitions.duration.leavingScreen,
|
||||
}),
|
||||
overflowX: 'hidden',
|
||||
width: `calc(${$0.spacing(7)} + 1px)`,
|
||||
[$0.breakpoints.up('sm')]: {
|
||||
width: `calc(${$0.spacing(8)} + 1px)`,
|
||||
},
|
||||
})"""
|
||||
|
||||
let private MyComponent =
|
||||
Style.styled
|
||||
("div", {| shouldForwardProp = fun prop -> prop <> "open" |})
|
||||
@@ -74,7 +100,6 @@ let private Main =
|
||||
"duration" ==> theme?transitions?duration?leavingScreen
|
||||
])
|
||||
)
|
||||
style.marginLeft (length.px -drawerWidth)
|
||||
style.custom (
|
||||
"variants", [|
|
||||
createObj [
|
||||
@@ -93,37 +118,33 @@ let private Main =
|
||||
)
|
||||
])
|
||||
|
||||
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 AppBar : obj = emitJsExpr drawerWidth """styled(MuiAppBar, {
|
||||
shouldForwardProp: (prop) => prop !== 'open',
|
||||
})(({ theme }) => ({
|
||||
[theme.breakpoints.up('md')]: {
|
||||
zIndex: theme.zIndex.drawer + 1,
|
||||
transition: theme.transitions.create(['width', 'margin'], {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.enteringScreen,
|
||||
}),
|
||||
},
|
||||
variants: [
|
||||
{
|
||||
props: ({ open }) => open,
|
||||
style: {
|
||||
[theme.breakpoints.up('md')]: {
|
||||
marginLeft: $0,
|
||||
width: `calc(100% - ${$0}px)`,
|
||||
transition: theme.transitions.create(['width', 'margin'], {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.enteringScreen,
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
],
|
||||
}))
|
||||
"""
|
||||
|
||||
let private DrawerHeader: unit =
|
||||
styled "div" (fun (props: obj) ->
|
||||
@@ -136,13 +157,39 @@ let private DrawerHeader: unit =
|
||||
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 = [|
|
||||
// NOTE: Taken from https://mui.com/material-ui/react-drawer/#mini-variant-drawer
|
||||
let private Drawer : obj = emitJsExpr () """styled(MuiDrawer, {
|
||||
shouldForwardProp: (prop) => prop !== 'open',
|
||||
})(({ theme }) => ({
|
||||
width: drawerWidth,
|
||||
flexShrink: 0,
|
||||
whiteSpace: 'nowrap',
|
||||
boxSizing: 'border-box',
|
||||
variants: [
|
||||
{
|
||||
props: ({ open }) => open,
|
||||
style: {
|
||||
...openedMixin(theme),
|
||||
'& .MuiDrawer-paper': openedMixin(theme),
|
||||
},
|
||||
},
|
||||
{
|
||||
props: ({ open }) => !open,
|
||||
style: {
|
||||
...closedMixin(theme),
|
||||
'& .MuiDrawer-paper': closedMixin(theme),
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
)
|
||||
"""
|
||||
|
||||
let private pages = [|
|
||||
"/", "Atlantis"
|
||||
"/catalog/index.html", "Home"
|
||||
"index.html", "Mui"
|
||||
@@ -150,18 +197,127 @@ let pages = [|
|
||||
"../fluentui/index.html", "FluentUI"
|
||||
|]
|
||||
|
||||
[<JSX.Component>]
|
||||
let private DummyMain () =
|
||||
JSX.html """
|
||||
<>
|
||||
<Typography variant="h2">Home</Typography>
|
||||
|
||||
<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>
|
||||
</>
|
||||
"""
|
||||
|
||||
[<JSX.Component>]
|
||||
let View () =
|
||||
let theme = useTheme ()
|
||||
|
||||
let responsive = React.useResponsive()
|
||||
|
||||
let currentUrl, updateUrl = React.useState(Router.currentUrl())
|
||||
|
||||
let isOpen, setOpen = React.useState false
|
||||
|
||||
let handleDrawerOpen () = setOpen true
|
||||
let handleDrawerClose () = setOpen false
|
||||
|
||||
let isLargeScreen =
|
||||
responsive = ScreenSize.WideScreen
|
||||
|| responsive = ScreenSize.Desktop
|
||||
|
||||
let url = Router.currentUrl ()
|
||||
|
||||
let drawer =
|
||||
JSX.html $"""
|
||||
<>
|
||||
<DrawerHeader>
|
||||
<IconButton
|
||||
onClick={handleDrawerClose}
|
||||
>
|
||||
<ChevronLeftIcon />
|
||||
</IconButton>
|
||||
</DrawerHeader>
|
||||
<Divider />
|
||||
<List>
|
||||
<ListItem key="home" disablePadding>
|
||||
<ListItemButton
|
||||
selected={url = []}
|
||||
onClick={fun () -> Router.navigate ""}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<HomeIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Home" sx={Utils.toStyle [ style.opacity (if isOpen then 1 else 0)]} />
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
<ListItem key="oceanography" disablePadding>
|
||||
<ListItemButton
|
||||
selected={List.tryHead url = Some "oceanography"}
|
||||
onClick={fun () -> Router.navigate "oceanography"}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<MapIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Oceanography" sx={Utils.toStyle [ style.opacity (if isOpen then 1 else 0)]} />
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
<ListItem key="compute" disablePadding>
|
||||
<ListItemButton
|
||||
selected={List.tryHead url = Some "compute"}
|
||||
onClick={fun () -> Router.navigate "compute"}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<StorageIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Compute" sx={Utils.toStyle [ style.opacity (if isOpen then 1 else 0)]} />
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
<ListItem key="statistics" disablePadding>
|
||||
<ListItemButton
|
||||
selected={List.tryHead url = Some "statistics"}
|
||||
onClick={fun () -> Router.navigate "statistics"}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<QueryStatsIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Statistics" sx={Utils.toStyle [ style.opacity (if isOpen then 1 else 0)]} />
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
<ListItem key="archives" disablePadding>
|
||||
<ListItemButton
|
||||
selected={List.tryHead url = Some "archives"}
|
||||
onClick={fun () -> Router.navigate "archives"}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<InventoryIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Archives" sx={Utils.toStyle [ style.opacity (if isOpen then 1 else 0)]} />
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
</List>
|
||||
</>
|
||||
"""
|
||||
|
||||
let linkButton (url: string, str: string) =
|
||||
JSX.html $"""
|
||||
<Button
|
||||
key={str}
|
||||
href={url}
|
||||
sx={
|
||||
Utils.toStyle [
|
||||
@@ -173,120 +329,97 @@ let View () =
|
||||
</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>
|
||||
let content =
|
||||
match currentUrl with
|
||||
| [] -> DummyMain() |> Utils.toReact
|
||||
| ["oceanography"] -> JSX.html """<Typography variant="h2">Oceanography</Typography>""" |> Utils.toReact
|
||||
| ["compute"] -> JSX.html """<Typography variant="h2">Compute</Typography>""" |> Utils.toReact
|
||||
| ["statistics"] -> JSX.html """<Typography variant="h2">Statistics</Typography>""" |> Utils.toReact
|
||||
| ["archives"] -> Archives.View () |> Utils.toReact
|
||||
| otherwise -> Html.h1 "404 Not found"
|
||||
|
||||
<Typography
|
||||
variant="h5"
|
||||
React.router [
|
||||
router.onUrlChanged updateUrl
|
||||
router.children [
|
||||
JSX.html $"""
|
||||
<ThemeProvider theme={darkTheme}>
|
||||
<Box sx={Utils.toStyle [ style.display.flex ]}>
|
||||
<CssBaseline />
|
||||
<AppBar position="fixed" open={isOpen}>
|
||||
<Toolbar>
|
||||
<IconButton
|
||||
color="inherit"
|
||||
edge="start"
|
||||
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
|
||||
if not isLargeScreen then
|
||||
style.display.none
|
||||
]
|
||||
}
|
||||
>
|
||||
{pages |> Array.map linkButton}
|
||||
</Box>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
|
||||
|
||||
<MuiDrawer
|
||||
open={isOpen}
|
||||
sx={
|
||||
Utils.toStyle [
|
||||
style.custom ("mr", 2)
|
||||
if isLargeScreen then
|
||||
style.display.none
|
||||
else
|
||||
style.display.block
|
||||
]
|
||||
}
|
||||
>
|
||||
Oceanbox
|
||||
</Typography>
|
||||
<Box
|
||||
{drawer}
|
||||
</MuiDrawer>
|
||||
<Drawer
|
||||
variant={if isLargeScreen then "permanent" else "temporary"}
|
||||
anchor="left"
|
||||
open={isOpen}
|
||||
sx={
|
||||
Utils.toStyle [
|
||||
style.flexGrow 1
|
||||
if isLargeScreen then
|
||||
style.display.block
|
||||
else
|
||||
style.display.none
|
||||
]
|
||||
}
|
||||
>
|
||||
{pages |> Array.map linkButton}
|
||||
</Box>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
{drawer}
|
||||
</Drawer>
|
||||
|
||||
<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>
|
||||
"""
|
||||
<Main open={isOpen}>
|
||||
<DrawerHeader />
|
||||
{content}
|
||||
</Main>
|
||||
</Box>
|
||||
</ThemeProvider>
|
||||
"""
|
||||
|> Utils.toReact
|
||||
]
|
||||
]
|
||||
@@ -5,4 +5,12 @@
|
||||
.drawer-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.archive-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
Reference in New Issue
Block a user