wip: import site locations from fiskeridir
This commit is contained in:
@@ -121,4 +121,5 @@ 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"
|
||||
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-list-bulleted.js"
|
||||
@@ -312,6 +312,9 @@ let fiskeri (topic: InfoLayer) : Ol.Layer.Layer =
|
||||
source.serverType Source.ServerType.Geoserver
|
||||
source.params' !!{| layers = string topic |}
|
||||
]
|
||||
if topic = POs then
|
||||
let parameters = source.getParams ()
|
||||
console.log $"[Maps] POs WMS parameters: {parameters}"
|
||||
|
||||
Layer.imageLayer [ layer.source source; layer.opacity alpha ]
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ open Fable.OpenLayers
|
||||
open FsToolkit.ErrorHandling
|
||||
|
||||
open Proj4
|
||||
open System.Text.RegularExpressions
|
||||
|
||||
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")
|
||||
@@ -25,12 +26,18 @@ let inline posToCoord (pos: 'a * 'a) : Coordinate = unbox pos
|
||||
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 tryParseFloat (s: string) : float option = s |> Double.TryParse |> function | true, f -> Some f | _ -> None
|
||||
let inline tryParseInt (s: string) : int option = s |> Int32.TryParse |> function | true, i -> Some i | _ -> None
|
||||
|
||||
// Extracting first number in string together with its prefix: "Ab4cdef" -> Some ("Ab", 4)
|
||||
let inline trySplitPrefixAndInt (input: string) : (string * int) option =
|
||||
let m = Regex.Match(input, @"^([A-Za-z]+)(\d+)$")
|
||||
if m.Success then
|
||||
let prefix = m.Groups.[1].Value
|
||||
let number = int m.Groups.[2].Value
|
||||
Some (prefix, number)
|
||||
else
|
||||
None
|
||||
|
||||
let formatDigits n m =
|
||||
{| minimumFractionDigits = n; maximumFractionDigits = m |} |> JS.JSON.stringify
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
module Fiskeridir
|
||||
|
||||
open Browser
|
||||
open Fable.Core
|
||||
open Fable.OpenLayers
|
||||
open Thoth.Fetch
|
||||
open Thoth.Json
|
||||
@@ -14,7 +15,7 @@ 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 = {
|
||||
type LocalityBorder = {
|
||||
Id : int
|
||||
SiteVersionId : int
|
||||
SiteNr : int
|
||||
@@ -31,14 +32,14 @@ and Point = {
|
||||
}
|
||||
|
||||
let fetchLocalityBorders (map: OlMap) (localityId: int) =
|
||||
Fetch.tryGet<unit, FiskeridirLocalityBorder array>(
|
||||
Fetch.tryGet<unit, LocalityBorder array>(
|
||||
url = bordersUrl localityId,
|
||||
headers = [
|
||||
Fetch.Types.HttpRequestHeaders.Accept "application/json"
|
||||
Fetch.Types.HttpRequestHeaders.AcceptCharset "UTF-8"
|
||||
],
|
||||
decoder =
|
||||
Decode.Auto.generateDecoder<FiskeridirLocalityBorder array>(
|
||||
Decode.Auto.generateDecoder<LocalityBorder array>(
|
||||
caseStrategy = CaseStrategy.CamelCase
|
||||
)
|
||||
)
|
||||
@@ -98,4 +99,205 @@ let fetchLocalityBorders (map: OlMap) (localityId: int) =
|
||||
|
||||
Layers.addFeatures map MapLayer.Aquaculture [| feature |]
|
||||
|
||||
())
|
||||
|
||||
|
||||
type ProductionAreaData = {
|
||||
DefaultCode: string
|
||||
DefaultName: string
|
||||
} with
|
||||
static member empty = {
|
||||
DefaultCode = ""
|
||||
DefaultName = ""
|
||||
}
|
||||
member x.ToLabel() = $"PO{x.DefaultCode}: {x.DefaultName}"
|
||||
|
||||
type SiteData = {
|
||||
// SiteId: int
|
||||
// VersionId: int
|
||||
SiteNr: int
|
||||
Name: string
|
||||
// PlacementType: string
|
||||
// PlacementTypeValue: string
|
||||
// WaterType: string
|
||||
// WaterTypeValue: string
|
||||
// FirstClearanceTime: string
|
||||
// FirstClearanceType: string
|
||||
// FirstClearanceTypeValue: string
|
||||
Latitude: float
|
||||
Longitude: float
|
||||
Capacity: float
|
||||
// TempCapacity: float
|
||||
// CapacityUnitType: string
|
||||
Placement: Placement
|
||||
// SpeciesType: string
|
||||
// SpeciesTypeValue: string
|
||||
// SpeciesLimitations: SpeciesLimitations list
|
||||
// Connections: Connections list
|
||||
// ObsoleteConnections: Connections list
|
||||
// Version: Version
|
||||
// IsSlaughtery: bool
|
||||
// HasCommercialActivity: bool
|
||||
// HasColocation: bool
|
||||
// HasJointOperation: bool
|
||||
}
|
||||
and Placement = {
|
||||
// MunicipalityCode: string
|
||||
MunicipalityName: string
|
||||
// CountyCode: string
|
||||
// CountyName: string
|
||||
ProdAreaCode: string
|
||||
ProdAreaName: string
|
||||
// ProdAreaStatus: string
|
||||
}
|
||||
// and SpeciesLimitations = {
|
||||
// Code: string
|
||||
// LatinName: string
|
||||
// NbNoName: string
|
||||
// NnNoName: string
|
||||
// EnGbName: string
|
||||
// }
|
||||
// and private Connections = {
|
||||
// Key: string
|
||||
// LicenseId: int
|
||||
// LicenseNr: string
|
||||
// SiteId: int
|
||||
// SiteNr: string
|
||||
// SiteName: string
|
||||
// ValidFrom: string
|
||||
// ValidUntil: string
|
||||
// TemporaryUntil: string option
|
||||
// RegisteredTime: string
|
||||
// Active: bool
|
||||
// Status: string
|
||||
// StatusValue: string
|
||||
// }
|
||||
// and private Version = {
|
||||
// ValidFrom: string
|
||||
// ValidUntil: string
|
||||
// TemporaryUntil: string option
|
||||
// RegisteredTime: string
|
||||
// VersionCauseType: string
|
||||
// VersionCauseTypeValue: string
|
||||
// Status: string
|
||||
// StatusValue: string
|
||||
// VersionableStatus: string
|
||||
// VersionableStatusValue: string
|
||||
// }
|
||||
let fetchInfoPO (areaCode: int) : ProductionAreaData JS.Promise =
|
||||
console.log $"Fetching Production Area data: PO{areaCode}"
|
||||
|
||||
Fetch.tryGet<unit, ProductionAreaData>(
|
||||
url = $"https://api.fiskeridir.no/pub-aqua/api/v1/areas/production_area/{string areaCode}",
|
||||
headers = [
|
||||
Fetch.Types.HttpRequestHeaders.Accept "application/json"
|
||||
Fetch.Types.HttpRequestHeaders.AcceptCharset "UTF-8"
|
||||
],
|
||||
decoder =
|
||||
Decode.Auto.generateDecoder<ProductionAreaData>(
|
||||
caseStrategy = CaseStrategy.CamelCase
|
||||
)
|
||||
)
|
||||
|> Promise.map (fun res ->
|
||||
match res with
|
||||
| Error err ->
|
||||
console.error("Could not fetch site data from api.fiskeridir.no:", err)
|
||||
ProductionAreaData.empty
|
||||
| Ok po ->
|
||||
console.log $"Fetched PO{po.DefaultCode} : {po.DefaultName}"
|
||||
po)
|
||||
|
||||
let fetchSitesPO (range: int*int) (po: int) : SiteData [] JS.Promise =
|
||||
console.log $"Fetching Aquaculture Sites : PO{po} [{fst range} - {snd range}]"
|
||||
|
||||
Fetch.tryGet<unit, SiteData[]>(
|
||||
url = $"https://api.fiskeridir.no/pub-aqua/api/v1/sites?production-area-code={string po}&range={fst range}-{snd range}",
|
||||
headers = [
|
||||
Fetch.Types.HttpRequestHeaders.Accept "application/json"
|
||||
Fetch.Types.HttpRequestHeaders.AcceptCharset "UTF-8"
|
||||
],
|
||||
decoder =
|
||||
Decode.Auto.generateDecoder<SiteData[]>(
|
||||
caseStrategy = CaseStrategy.CamelCase
|
||||
)
|
||||
)
|
||||
|> Promise.map (fun res ->
|
||||
match res with
|
||||
| Error err ->
|
||||
console.error("Could not fetch site data from api.fiskeridir.no:", err)
|
||||
Array.empty
|
||||
| Ok sites ->
|
||||
console.log $"Fetched {sites.Length} sites in PO{po}"
|
||||
sites)
|
||||
|
||||
let fetchSiteData (map: OlMap) (localityId: int) =
|
||||
console.log $"Fetching Aquaculture Site : {localityId}"
|
||||
Fetch.tryGet<unit, SiteData>(
|
||||
url = $"https://api.fiskeridir.no/pub-aqua/api/v1/sites/{localityId}",
|
||||
headers = [
|
||||
Fetch.Types.HttpRequestHeaders.Accept "application/json"
|
||||
Fetch.Types.HttpRequestHeaders.AcceptCharset "UTF-8"
|
||||
],
|
||||
decoder =
|
||||
Decode.Auto.generateDecoder<SiteData>(
|
||||
caseStrategy = CaseStrategy.CamelCase
|
||||
)
|
||||
)
|
||||
|> Promise.map (fun res ->
|
||||
match res with
|
||||
| Error err -> console.error("Could not fetch site data from api.fiskeridir.no:", err)
|
||||
| Ok site ->
|
||||
console.log $"SITE : {site}"
|
||||
// let points =
|
||||
// borders
|
||||
// |> Array.collect (fun border ->
|
||||
// border.Points)
|
||||
//
|
||||
// if points.Length < 1 then
|
||||
// // TODO: Add some default geom for sites without bordes
|
||||
// console.info("This site does not have borders")
|
||||
// ()
|
||||
// else
|
||||
// let feature =
|
||||
// let coords : Coordinate array =
|
||||
// let coords =
|
||||
// points
|
||||
// |> Array.map (fun point ->
|
||||
// posToCoord (point.Longitude, point.Latitude)
|
||||
// |> coordToEpsg3857)
|
||||
// let first = Array.head coords
|
||||
// let last = coords[coords.Length - 1]
|
||||
//
|
||||
// if first |> Coord.equals last then
|
||||
// coords
|
||||
// else
|
||||
// let first = coords |> Array.head |> Array.singleton
|
||||
// let linearRing = first |> Array.append coords
|
||||
// linearRing
|
||||
//
|
||||
// let polygon =
|
||||
// Geometry.polygon [
|
||||
// geometry.coordinates [| coords |]
|
||||
// geometry.layout GeometryLayout.XY
|
||||
// ]
|
||||
// Feature.feature [
|
||||
// feature.geometryOrProperties polygon
|
||||
// ]
|
||||
//
|
||||
// feature.setId localityId
|
||||
// feature.set("type", "polygon")
|
||||
//
|
||||
// // NOTE: Fly to feature
|
||||
// let geom = feature.getGeometry()
|
||||
// let extent = geom.getExtent ()
|
||||
// let center = extent |> Extent.extent.getCenter
|
||||
// let view = map.getView()
|
||||
// let res = view.getResolutionForExtent extent
|
||||
// let zoom = view.getZoomForResolution res
|
||||
// let clamped = System.Double.Clamp(zoom, 7.0, 14.0)
|
||||
// console.debug("Flying to aquaculture area", zoom, clamped)
|
||||
// Maps.flyTo map clamped center
|
||||
//
|
||||
// Layers.addFeatures map MapLayer.Aquaculture [| feature |]
|
||||
|
||||
())
|
||||
213
src/Atlantis/src/Client/Mapster/LocalitiesDialog.fs
Normal file
213
src/Atlantis/src/Client/Mapster/LocalitiesDialog.fs
Normal file
@@ -0,0 +1,213 @@
|
||||
module LocalitiesDialog
|
||||
|
||||
open System
|
||||
open Browser
|
||||
open Fable.Core
|
||||
open Fable.Core.JsInterop
|
||||
open Lit
|
||||
|
||||
open Atlantis
|
||||
open Atlantis.Types
|
||||
open Archmaester.Dto
|
||||
open Maps
|
||||
open Model
|
||||
open Utils
|
||||
|
||||
type private TableItem = {
|
||||
id: int
|
||||
name: string
|
||||
// species: string
|
||||
capacity: float
|
||||
latitude: float
|
||||
longitude: float
|
||||
municipality: string
|
||||
prodAreaName: string
|
||||
prodAreaCode: int
|
||||
} with
|
||||
static member empty = {
|
||||
id = 0
|
||||
name = ""
|
||||
// species = ""
|
||||
capacity = 0.0
|
||||
latitude = 0.0
|
||||
longitude = 0.0
|
||||
municipality = ""
|
||||
prodAreaName = ""
|
||||
prodAreaCode = 0
|
||||
}
|
||||
|
||||
let private siteDataToTableItems (site: Fiskeridir.SiteData) : TableItem =
|
||||
{
|
||||
id = site.SiteNr
|
||||
name = site.Name
|
||||
latitude = site.Latitude
|
||||
longitude = site.Longitude
|
||||
capacity = site.Capacity
|
||||
municipality = site.Placement.MunicipalityName
|
||||
prodAreaName = site.Placement.ProdAreaName
|
||||
prodAreaCode = site.Placement.ProdAreaCode |> tryParseInt |> Option.defaultValue 0
|
||||
}
|
||||
|
||||
let private fetchProductionAreaInfo onFetch (poOpt: int option) =
|
||||
promise {
|
||||
match poOpt with
|
||||
| Some po ->
|
||||
let! data = Fiskeridir.fetchInfoPO po
|
||||
data |> onFetch
|
||||
| None ->
|
||||
Fiskeridir.ProductionAreaData.empty
|
||||
|> onFetch
|
||||
}
|
||||
|
||||
let private fetchAquacultureSites onFetch (poOpt: int option) =
|
||||
promise {
|
||||
match poOpt with
|
||||
| Some po ->
|
||||
let! sites =
|
||||
[| for i in 0..9 do
|
||||
// the api accepts only 100 sites at a time, so batching up the requests
|
||||
// don't know how many sites there are, so fetching up to 1000...
|
||||
let range = (i*100, i*100 + 99)
|
||||
Fiskeridir.fetchSitesPO range po
|
||||
|]
|
||||
|> Array.map (Promise.map (Array.map siteDataToTableItems))
|
||||
|> Promise.all
|
||||
|> Promise.map Array.concat
|
||||
|
||||
sites
|
||||
|> onFetch
|
||||
| None -> ()
|
||||
}
|
||||
|
||||
[<HookComponent>]
|
||||
let localitiesDialog
|
||||
(arg: {|
|
||||
onClose: unit -> unit
|
||||
prodArea: int option
|
||||
|}) =
|
||||
let items, setItems = Hook.useState<TableItem array> [||]
|
||||
let modelData, setModelData = Hook.useState Fiskeridir.ProductionAreaData.empty
|
||||
|
||||
// let updateItems = Array.map siteDataToTableItems >> setItems
|
||||
let appendItems = Array.append items >> setItems
|
||||
|
||||
Hook.useEffectOnce (Utils.handleKeyPress "Escape" arg.onClose)
|
||||
// Hook.useEffectOnChange (arg.localities, updateItems)
|
||||
|
||||
Hook.useEffectOnChange(items, fun _ ->
|
||||
let table = document.getElementById "localities-table"
|
||||
table?selected <-
|
||||
items
|
||||
|> Array.choose (fun item ->
|
||||
Some item.id)
|
||||
table?items <- items)
|
||||
|
||||
|
||||
// NOTE(simkir): To enable a virtualized table, we do not create the rows by iterating over drifters, we instead
|
||||
// programatically add the rows to the table. In this useEffect, we wait for the table to be created, and then we
|
||||
// add all the items.
|
||||
// sp-table api: https://opensource.adobe.com/spectrum-web-components/components/table/api/
|
||||
Hook.useEffectOnce (fun () ->
|
||||
arg.prodArea
|
||||
|> fetchProductionAreaInfo setModelData
|
||||
|> Promise.start
|
||||
|
||||
arg.prodArea
|
||||
|> fetchAquacultureSites setItems
|
||||
|> Promise.start
|
||||
|
||||
let table = document.getElementById "localities-table"
|
||||
table?selected <- items
|
||||
|
||||
// NOTE: These are members of the spectrum table that we get
|
||||
// TODO: Make a small class for type safety
|
||||
table?items <- items
|
||||
// NOTE: Function to get an id for each row
|
||||
table?itemValue <- (fun (item : TableItem) -> $"{item.id}")
|
||||
// NOTE: Function to render each row. Creating the cell elements that corresponds to the header. Calls
|
||||
// this function on each item that we have sent into it.
|
||||
table?renderItem <- (fun (item: TableItem) ->
|
||||
html $"""
|
||||
<sp-table-cell style="max-width: 100px"><div style="padding-top: 5px;">{item.id}</div></sp-table-cell>
|
||||
<sp-table-cell style="max-width: 200px"><div style="padding-top: 5px;">{item.name}</div></sp-table-cell>
|
||||
<sp-table-cell style="max-width: 130px"><div style="padding-top: 5px;">{item.latitude |> sprintf "%.6f"}</div></sp-table-cell>
|
||||
<sp-table-cell style="max-width: 130px"><div style="padding-top: 5px;">{item.longitude |> sprintf "%.6f"}</div></sp-table-cell>
|
||||
<sp-table-cell style="max-width: 130px"><div style="padding-top: 5px;">{item.capacity |> sprintf "%.0f"}</div></sp-table-cell>
|
||||
<sp-table-cell style="max-width: 200px"><div style="padding-top: 5px;">{item.municipality}</div></sp-table-cell>
|
||||
""")
|
||||
|
||||
// NOTE: The table fires a "sorted" event when you click on the table headers
|
||||
table.addEventListener("sorted", fun ev ->
|
||||
// NOTE: The event has a detail member with these two members
|
||||
let sortDir: string = ev?detail?sortDirection
|
||||
let sortKey: string = ev?detail?sortKey // This comes from the sort-key given to the headers below
|
||||
let sortFn = if sortDir = "asc" then Array.sortBy else Array.sortByDescending
|
||||
table?items
|
||||
|> sortFn (fun item ->
|
||||
// NOTE(simkir): WARNING! Hack to access an objects members :) Everything is an object in
|
||||
// JS, right!?
|
||||
JS.expr_js $"{item}[{sortKey}]")
|
||||
|> setItems))
|
||||
|
||||
|
||||
let setupSimButton =
|
||||
html $"""
|
||||
<sp-action-menu size="m">
|
||||
<sp-icon-social-network slot="icon"></sp-icon-social-network>
|
||||
<span slot="label">Network analysis</span>
|
||||
<sp-menu-item>
|
||||
Lice
|
||||
</sp-menu-item>
|
||||
<sp-menu-item>
|
||||
Water Contact
|
||||
</sp-menu-item>
|
||||
</sp-action-menu>
|
||||
"""
|
||||
|
||||
let table =
|
||||
html $"""
|
||||
<sp-table
|
||||
id="localities-table"
|
||||
size="s"
|
||||
selects="multiple"
|
||||
scroller="true"
|
||||
style="height: 92%%; "
|
||||
@change={Ev(fun _ -> console.log $"CHANGE TABLE : {items.Length}")}
|
||||
>
|
||||
<sp-table-head>
|
||||
<sp-table-head-cell sortable sort-key="id" style="max-width: 100px">
|
||||
SiteId
|
||||
</sp-table-head-cell>
|
||||
<sp-table-head-cell sortable sort-key="name" style="max-width: 200px">
|
||||
Name
|
||||
</sp-table-head-cell>
|
||||
<sp-table-head-cell sortable sort-key="latitude" style="max-width: 130px">
|
||||
Latitude
|
||||
</sp-table-head-cell>
|
||||
<sp-table-head-cell sortable sort-key="longitude" style="max-width: 130px">
|
||||
Longitude
|
||||
</sp-table-head-cell>
|
||||
<sp-table-head-cell sortable sort-key="capacity" style="max-width: 130px">
|
||||
Capacity
|
||||
</sp-table-head-cell>
|
||||
<sp-table-head-cell sortable sort-key="municipality" style="max-width: 200px">
|
||||
Municipality
|
||||
</sp-table-head-cell>
|
||||
</sp-table-head>
|
||||
</sp-table>
|
||||
"""
|
||||
|
||||
html $"""
|
||||
<div class="archive-dialog">
|
||||
<sp-underlay ?open={true} @click={Ev(ignore >> arg.onClose)}></sp-underlay>
|
||||
<sp-dialog size="l" no-divider dismissable @close={Ev(fun _ -> arg.onClose ())}>
|
||||
<h1 slot="heading">{modelData.ToLabel()}</h1>
|
||||
{table}
|
||||
<div style="padding-top: 10px">
|
||||
<sp-action-group horizontal>
|
||||
{setupSimButton}
|
||||
</sp-action-group>
|
||||
</div>
|
||||
</sp-dialog>
|
||||
</div>
|
||||
"""
|
||||
@@ -11,6 +11,7 @@ open FsToolkit.ErrorHandling
|
||||
open Lit
|
||||
open Lit.Elmish
|
||||
open Thoth.Json
|
||||
open System.Text.RegularExpressions
|
||||
|
||||
open Archmaester.Dto
|
||||
open Atlantis.Shared
|
||||
@@ -2063,6 +2064,7 @@ let MapAppElement () =
|
||||
|> Program.withSubscription inert
|
||||
|
||||
let (model: Model), (dispatch: Msg -> unit) = Hook.useElmish program
|
||||
let localitiesOpen, setLocalitiesOpen = Hook.useState false
|
||||
let archivesOpen, setArchivesOpen = Hook.useState false
|
||||
let inboxOpen, setInboxOpen = Hook.useState false
|
||||
|
||||
@@ -2209,6 +2211,21 @@ let MapAppElement () =
|
||||
selectArchive = SetSelectedDrifter >> dispatch
|
||||
|}
|
||||
|
||||
let tryGetPoNumber (name: string) : int option =
|
||||
name.Split('-')
|
||||
|> Array.head
|
||||
|> trySplitPrefixAndInt
|
||||
|> Option.bind (function
|
||||
| "PO", number -> Some number
|
||||
| _ -> None)
|
||||
|
||||
let localitiesDialogArgs = {|
|
||||
onClose = fun () -> setLocalitiesOpen false
|
||||
prodArea =
|
||||
model.archive.name
|
||||
|> tryGetPoNumber
|
||||
|}
|
||||
|
||||
let selectInboxItem (id, type': MessageType) =
|
||||
Hub.Action.Inbox (Hub.InboxMsg.MarkRead id) |> (HubMsg >> dispatch)
|
||||
match type' with
|
||||
@@ -2380,7 +2397,7 @@ let MapAppElement () =
|
||||
class="flex-row"
|
||||
style="height: inherit"
|
||||
>
|
||||
{Navigation.toolboxNav setArchivesOpen setInboxOpen model dispatch}
|
||||
{Navigation.toolboxNav setLocalitiesOpen setArchivesOpen setInboxOpen model dispatch}
|
||||
|
||||
<div class="box grow full-box">
|
||||
<sp-split-view
|
||||
@@ -2406,6 +2423,10 @@ let MapAppElement () =
|
||||
</div>
|
||||
</sp-split-view>
|
||||
{notificationToast model.notification}
|
||||
{if localitiesOpen then
|
||||
LocalitiesDialog.localitiesDialog localitiesDialogArgs
|
||||
else
|
||||
Lit.nothing}
|
||||
{if archivesOpen then
|
||||
ArchiveDialog.archiveDialog archiveDialogArgs
|
||||
else
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
<Compile Include="ProbingControls.fs" />
|
||||
<Compile Include="PlotBox.fs" />
|
||||
<Compile Include="ArchiveDialog.fs" />
|
||||
<Compile Include="LocalitiesDialog.fs" />
|
||||
<Compile Include="Inbox.fs" />
|
||||
<Compile Include="Navigation.fs" />
|
||||
<Compile Include="Mapster.fs" />
|
||||
|
||||
@@ -1225,7 +1225,7 @@ let sideNav (dispatch: Msg -> unit) (model: Model) =
|
||||
"""
|
||||
|
||||
[<HookComponent>]
|
||||
let toolboxNav setArchivesOpen setInboxOpen model dispatch =
|
||||
let toolboxNav setLocalitiesOpen setArchivesOpen setInboxOpen model dispatch =
|
||||
Hook.useHmr hmr
|
||||
|
||||
let statsDisabled = false
|
||||
@@ -1292,6 +1292,7 @@ let toolboxNav setArchivesOpen setInboxOpen model dispatch =
|
||||
else
|
||||
Lit.nothing
|
||||
|
||||
let openLocalities ev = setLocalitiesOpen true
|
||||
let openArchives ev = setArchivesOpen true
|
||||
let openInbox ev = setInboxOpen true
|
||||
|
||||
@@ -1350,6 +1351,18 @@ let toolboxNav setArchivesOpen setInboxOpen model dispatch =
|
||||
</div>
|
||||
|
||||
<div class="toolbox-bottom">
|
||||
<overlay-trigger id="trigger" placement="right" offset="6" triggered-by="hover">
|
||||
<sp-tooltip slot="hover-content">Akvakulturregisteret</sp-tooltip>
|
||||
<div slot="trigger" class="toolbox-control">
|
||||
<div
|
||||
class="toolbox-icon"
|
||||
@click={Ev(openLocalities)}
|
||||
>
|
||||
<i class="fas fa-fish"></i>
|
||||
</div>
|
||||
</div>
|
||||
</overlay-trigger>
|
||||
|
||||
<overlay-trigger id="trigger" placement="right" offset="6" triggered-by="hover">
|
||||
<sp-tooltip slot="hover-content">Inbox</sp-tooltip>
|
||||
<div slot="trigger" class="toolbox-control">
|
||||
|
||||
Reference in New Issue
Block a user