-
-
-
diff --git a/src/components/wms/OverlayLayer.vue b/src/components/wms/OverlayLayer.vue
new file mode 100644
index 000000000..6a11397f4
--- /dev/null
+++ b/src/components/wms/OverlayLayer.vue
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
diff --git a/src/components/wms/panel/BaseMapPanel.vue b/src/components/wms/panel/BaseMapPanel.vue
new file mode 100644
index 000000000..f6661c6fc
--- /dev/null
+++ b/src/components/wms/panel/BaseMapPanel.vue
@@ -0,0 +1,26 @@
+
+
+
+
+
+ mdi-check
+
+
+
+
+
+
+
diff --git a/src/components/wms/panel/ColourPanel.vue b/src/components/wms/panel/ColourPanel.vue
new file mode 100644
index 000000000..17425396d
--- /dev/null
+++ b/src/components/wms/panel/ColourPanel.vue
@@ -0,0 +1,141 @@
+
+
+
+
+
+
+ {{ item.range?.min ?? '' }}
+
+ {{ item.range?.max ?? '' }}
+
+
+
+
+
+
+ mdi-check
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/wms/panel/InformationPanel.vue b/src/components/wms/panel/InformationPanel.vue
new file mode 100644
index 000000000..b65f045ad
--- /dev/null
+++ b/src/components/wms/panel/InformationPanel.vue
@@ -0,0 +1,140 @@
+
+
+
+ {{ showLayer ? 'mdi-layers' : 'mdi-layers-off' }}
+
+
+
+
+ {{ layerTitle }}
+
+
+
+
+
+
+
+
+
+
+
+
+ Animate
+
+
+
+
+
+
+
+
+ mdi-animation-play
+
+
+
+
+
+
+
diff --git a/src/components/wms/panel/OverlayPanel.vue b/src/components/wms/panel/OverlayPanel.vue
new file mode 100644
index 000000000..608bf3ce9
--- /dev/null
+++ b/src/components/wms/panel/OverlayPanel.vue
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/lib/dashboard/types.ts b/src/lib/dashboard/types.ts
new file mode 100644
index 000000000..d6dcd8583
--- /dev/null
+++ b/src/lib/dashboard/types.ts
@@ -0,0 +1,26 @@
+import { ComponentType } from '@/lib/topology/component'
+
+export interface DashboardsResponse {
+ dashboards: Dashboard[]
+}
+
+export interface Dashboard {
+ id: string
+ cssTemplate: string
+ groups: DashboardGroup[]
+}
+
+export interface DashboardGroup {
+ elements: DashboardElement[]
+}
+
+export interface DashboardElement {
+ gridTemplateArea: string
+ items: DashboardItem[]
+}
+
+export interface DashboardItem {
+ topologyNodeId: string
+ component: ComponentType
+ componentSettingsId: string
+}
diff --git a/src/lib/topology/component.ts b/src/lib/topology/component.ts
new file mode 100644
index 000000000..e9164bd82
--- /dev/null
+++ b/src/lib/topology/component.ts
@@ -0,0 +1,61 @@
+export const ComponentType = {
+ map: 'map',
+ charts: 'charts',
+ 'data-download': 'data-download',
+ reports: 'reports',
+ 'schematic-status-display': 'schematic-status-display',
+ 'system-monitor': 'system-monitor',
+ 'web-display': 'web-display',
+ dashboard: 'dashboard',
+ tasks: 'tasks',
+} as const
+
+export type ComponentType = (typeof ComponentType)[keyof typeof ComponentType]
+
+export const componentTypeToIconMap = {
+ map: 'mdi-map',
+ charts: 'mdi-chart-multiple',
+ 'data-download': 'mdi-download',
+ reports: 'mdi-file-document',
+ 'schematic-status-display': 'mdi-view-dashboard',
+ 'system-monitor': 'mdi-monitor',
+ 'web-display': 'mdi-web',
+ dashboard: 'mdi-view-dashboard',
+ tasks: 'mdi-cog',
+} satisfies Record
+
+export const componentTypeToTitleMap = {
+ map: 'Map',
+ charts: 'Charts',
+ 'data-download': 'Download',
+ reports: 'Reports',
+ 'schematic-status-display': 'Schematic',
+ 'system-monitor': 'System Monitor',
+ 'web-display': 'Web Display',
+ dashboard: 'Dashboard',
+ tasks: 'Tasks',
+} satisfies Record
+
+export const componentTypeToRouteNameMap = {
+ map: 'TopologySpatialDisplay',
+ charts: 'TopologyTimeSeries',
+ 'data-download': 'TopologyDataDownload',
+ reports: 'TopologyReports',
+ 'schematic-status-display': 'TopologySchematicStatusDisplay',
+ 'system-monitor': 'TopologySystemMonitor',
+ 'web-display': 'TopologyWebDisplay',
+ dashboard: 'TopologyDashboard',
+ tasks: 'TopologyTasks',
+} satisfies Record
+
+export const componentTypeToIdMap = {
+ map: 'spatial',
+ charts: 'timeseries',
+ 'data-download': 'download',
+ reports: 'reports',
+ 'schematic-status-display': 'ssd',
+ 'system-monitor': 'systemmonitor',
+ 'web-display': 'webdisplay',
+ dashboard: 'dashboard',
+ tasks: crypto.randomUUID(),
+} satisfies Record
diff --git a/src/lib/topology/componentSettings.ts b/src/lib/topology/componentSettings.ts
new file mode 100644
index 000000000..a80adc9a2
--- /dev/null
+++ b/src/lib/topology/componentSettings.ts
@@ -0,0 +1,68 @@
+import type { FillPaintProps, LinePaintProps } from 'maplibre-gl'
+import type { ComponentType } from './component'
+
+type PaintMapping = {
+ fill: FillPaintProps
+ line: LinePaintProps
+}
+
+export interface ComponentSettingsResponse {
+ componentSettings: ComponentSettings[]
+ declarations?: Declarations
+}
+
+interface ComponentSettingsMapping {
+ map: MapSettings
+ charts: ChartSettings
+ 'data-download': undefined
+ reports: undefined
+ 'schematic-status-display': undefined
+ 'system-monitor': undefined
+ 'web-display': undefined
+ dashboard: undefined
+ tasks: undefined
+}
+
+type SettingsPerComponent = {
+ [key in ComponentType]?: ComponentSettingsMapping[key]
+}
+
+export interface ComponentSettings extends SettingsPerComponent {
+ id: string
+}
+
+export interface ChartSettings {
+ chartEnabled?: boolean
+ elevationChartEnabled?: boolean
+ tableEnabled?: boolean
+ metaDataEnabled?: boolean
+ downloadEnabled?: boolean
+}
+
+export interface MapSettings extends ChartSettings {
+ chartPanelEnabled?: boolean
+ locationSearchEnabled?: boolean
+}
+
+export interface Declarations {
+ baseMaps?: BaseMap[]
+ overlays?: Overlays
+}
+
+export interface BaseMap {
+ id: string
+ name: string
+ style: string
+}
+
+export interface Overlays {
+ locations: OverlayLocation[]
+}
+
+export interface OverlayLocation {
+ id: string
+ name: string
+ locationSet: string
+ type: keyof PaintMapping
+ paint: PaintMapping[OverlayLocation['type']]
+}
diff --git a/src/lib/topology/dashboard.ts b/src/lib/topology/dashboard.ts
index 325521cb1..8ea23ed0d 100644
--- a/src/lib/topology/dashboard.ts
+++ b/src/lib/topology/dashboard.ts
@@ -1,14 +1,7 @@
import type { TopologyNode } from '@deltares/fews-pi-requests'
import { defineAsyncComponent, type Component } from 'vue'
-import {
- nodeHasCharts,
- nodeHasDataDownload,
- nodeHasMap,
- nodeHasReports,
- nodeHasSchematicStatusDisplay,
- nodeHasSystemMonitor,
-} from './nodes'
import { ComponentProps } from '@/lib/utils/types'
+import { ComponentType } from '@/lib/topology/component'
const SpatialDisplay = defineAsyncComponent(
() => import('@/components/spatialdisplay/SpatialDisplay.vue'),
@@ -29,83 +22,72 @@ const SystemMonitorDisplayView = defineAsyncComponent(
() => import('@/views/SystemMonitorDisplayView.vue'),
)
-export interface ComponentWithProps {
- component: T
- props: ComponentProps
-}
+const Empty = defineAsyncComponent(() => import('@/views/Empty.vue'))
-export function getComponentWithPropsForNode(node: TopologyNode | undefined) {
- if (!node) {
- return {
- component: undefined,
- props: undefined,
- }
- }
+export const componentTypeToComponentMap = {
+ map: SpatialDisplay,
+ charts: TimeSeriesDisplay,
+ 'data-download': DataDownloadDisplayView,
+ reports: ReportsDisplayView,
+ 'schematic-status-display': SchematicStatusDisplay,
+ 'system-monitor': SystemMonitorDisplayView,
+ tasks: Empty,
+ 'web-display': Empty,
+ dashboard: Empty,
+} satisfies Record
- if (nodeHasMap(node)) {
- const result: ComponentWithProps = {
- component: SpatialDisplay,
- props: {
- layerName: node.gridDisplaySelection?.plotId,
- filterIds: node.filterIds,
- },
- }
- return result
- }
+export type PropsForComponentType = ComponentProps<
+ (typeof componentTypeToComponentMap)[T]
+>
+
+export function getComponentPropsForNode(
+ componentType: ComponentType,
+ node?: TopologyNode,
+): PropsForComponentType | undefined {
+ if (!node) return
- if (nodeHasCharts(node)) {
- const result: ComponentWithProps = {
- component: TimeSeriesDisplay,
- props: {
- nodeId: node.id,
- },
+ if (componentType === 'map') {
+ const result: PropsForComponentType<'map'> = {
+ layerName: node.gridDisplaySelection?.plotId,
+ filterIds: node.filterIds,
}
return result
}
- if (nodeHasDataDownload(node)) {
- const result: ComponentWithProps = {
- component: DataDownloadDisplayView,
- props: {
- nodeId: node.id,
- topologyNode: node,
- },
+ if (componentType === 'charts') {
+ const result: PropsForComponentType<'charts'> = {
+ nodeId: node.id,
}
return result
}
- if (nodeHasReports(node)) {
- const result: ComponentWithProps = {
- component: ReportsDisplayView,
- props: {
- topologyNode: node,
- },
+ if (componentType === 'data-download') {
+ const result: PropsForComponentType<'data-download'> = {
+ nodeId: node.id,
+ topologyNode: node,
}
return result
}
- if (nodeHasSchematicStatusDisplay(node)) {
- const result: ComponentWithProps = {
- component: SchematicStatusDisplay,
- props: {
- panelId: node.scadaPanelId,
- groupId: node.id,
- objectId: '',
- showDateTimeSlider: false,
- },
+ if (componentType === 'reports') {
+ const result: PropsForComponentType<'reports'> = {
+ topologyNode: node,
}
return result
}
- if (nodeHasSystemMonitor(node)) {
- const result: ComponentWithProps = {
- component: SystemMonitorDisplayView,
- props: {},
+
+ if (componentType === 'schematic-status-display') {
+ const result: PropsForComponentType<'schematic-status-display'> = {
+ panelId: node.scadaPanelId,
+ groupId: node.id,
+ objectId: '',
+ showDateTimeSlider: false,
}
return result
}
- return {
- component: undefined,
- props: undefined,
+ if (componentType === 'system-monitor') {
+ const result: PropsForComponentType<'system-monitor'> = {}
+ return result
}
}
diff --git a/src/lib/topology/displayTabs.ts b/src/lib/topology/displayTabs.ts
index aa6f3abf4..5d5d59091 100644
--- a/src/lib/topology/displayTabs.ts
+++ b/src/lib/topology/displayTabs.ts
@@ -10,18 +10,16 @@ import {
nodeHasSystemMonitor,
nodeHasWebDisplay,
} from './nodes'
+import {
+ ComponentType,
+ componentTypeToIconMap,
+ componentTypeToIdMap,
+ componentTypeToRouteNameMap,
+ componentTypeToTitleMap,
+} from './component'
export interface DisplayTab {
- type:
- | 'charts'
- | 'map'
- | 'reports'
- | 'data-download'
- | 'schematic-status-display'
- | 'system-monitor'
- | 'web-display'
- | 'dashboard'
- | 'tasks'
+ type: ComponentType
id: string
title: string
href?: string
@@ -31,80 +29,16 @@ export interface DisplayTab {
active: boolean
}
-const displayTabs: DisplayTab[] = [
- {
- type: 'map',
- id: 'spatial',
- title: 'Map',
- to: { name: 'TopologySpatialDisplay' },
- icon: 'mdi-map',
- active: false,
- },
- {
- type: 'charts',
- id: 'timeseries',
- title: 'Charts',
- to: { name: 'TopologyTimeSeries' },
- icon: 'mdi-chart-multiple',
- active: false,
- },
- {
- type: 'data-download',
- id: 'download',
- title: 'Download',
- to: { name: 'TopologyDataDownload' },
- icon: 'mdi-download',
- active: false,
- },
- {
- type: 'web-display',
- id: 'web-display',
- title: 'Web Display',
- to: { name: 'TopologyWebDisplay' },
- icon: 'mdi-web',
- active: false,
- },
- {
- type: 'reports',
- id: 'reports',
- title: 'Reports',
- to: { name: 'TopologyReports' },
- icon: 'mdi-file-document',
+const displayTabs: DisplayTab[] = Object.values(ComponentType).map((type) => {
+ return {
+ type,
+ id: componentTypeToIdMap[type],
+ title: componentTypeToTitleMap[type],
+ to: { name: componentTypeToRouteNameMap[type] },
+ icon: componentTypeToIconMap[type],
active: false,
- },
- {
- type: 'schematic-status-display',
- id: 'ssd',
- title: 'Schematic',
- to: { name: 'TopologySchematicStatusDisplay' },
- icon: 'mdi-view-dashboard',
- active: false,
- },
- {
- type: 'system-monitor',
- id: 'ssd',
- title: 'System Monitor',
- to: { name: 'TopologySystemMonitor' },
- icon: 'mdi-view-dashboard',
- active: false,
- },
- {
- type: 'dashboard',
- id: 'dashboard',
- title: 'Dashboard',
- to: { name: 'TopologyDashboard' },
- icon: 'mdi-view-dashboard',
- active: false,
- },
- {
- type: 'tasks',
- id: crypto.randomUUID(),
- title: 'Tasks',
- to: { name: 'TopologyTasksDisplay' },
- icon: 'mdi-cog',
- active: false,
- },
-]
+ }
+})
export function displayTabsForNode(
node: TopologyNode,
@@ -154,7 +88,7 @@ export function displayTabsForNode(
break
case 'tasks':
// HACK
- tab.active = true
+ tab.active = false
tab.to.params = { ...params }
break
}
diff --git a/src/lib/topology/index.ts b/src/lib/topology/index.ts
index cfb39e52d..332278491 100644
--- a/src/lib/topology/index.ts
+++ b/src/lib/topology/index.ts
@@ -1,5 +1,5 @@
export * from './locations'
-import { configManager } from '../../services/application-config/index.ts'
+import { configManager } from '@/services/application-config/index.ts'
import {
PiWebserviceProvider,
type TopologyNode,
@@ -7,27 +7,43 @@ import {
import { createTransformRequestFn } from '@/lib/requests/transformRequest'
/**
- * Recursively updates a topology map where each node is stored by its id.
+ * Creates hash maps for topology nodes.
+ *
+ * This function generates two hash maps:
+ * 1. idToNodeMap: Maps node IDs to their corresponding TopologyNode objects.
+ * 2. childIdToParentNodeMap: Maps child node IDs to their parent node.
*
* @param {TopologyNode[] | undefined} nodes - An array of TopologyNode objects or undefined.
- * @param {Map} topologyMap - A Map used to store the topology nodes.
+ * @returns {{ idToNodeMap: Map, childIdToParentNodeMap: Map }} - An object containing the two hash maps.
*/
-export function createTopologyMap(nodes: TopologyNode[] | undefined) {
- const topologyMap = new Map()
-
- function recursiveCreateTopologyMap(
- nodes: TopologyNode[] | undefined,
- topologyMap: Map,
- ) {
- if (nodes === undefined) return undefined
+export function createTopologyHashMaps(nodes: TopologyNode[] | undefined): {
+ idToNodeMap: Map
+ childIdToParentNodeMap: Map
+} {
+ const idToNodeMap = new Map()
+ const childIdToParentNodeMap = new Map()
+
+ function recursivelyFillMaps(nodes: TopologyNode[]) {
for (const node of nodes) {
- topologyMap.set(node.id, node)
- recursiveCreateTopologyMap(node.topologyNodes, topologyMap)
+ idToNodeMap.set(node.id, node)
+
+ if (node.topologyNodes) {
+ node.topologyNodes.forEach((childNode) => {
+ childIdToParentNodeMap.set(childNode.id, node)
+ })
+ recursivelyFillMaps(node.topologyNodes)
+ }
}
}
- recursiveCreateTopologyMap(nodes, topologyMap)
- return topologyMap
+ if (nodes) {
+ recursivelyFillMaps(nodes)
+ }
+
+ return {
+ idToNodeMap,
+ childIdToParentNodeMap,
+ }
}
/**
diff --git a/src/lib/topology/locations.ts b/src/lib/topology/locations.ts
index f3e75a39d..58110e084 100644
--- a/src/lib/topology/locations.ts
+++ b/src/lib/topology/locations.ts
@@ -71,9 +71,36 @@ async function fetchLocationsAsGeoJsonForSingleFilterId(
includeIconNames: true,
showThresholds: true,
}
- // TODO: Remove cast to any when fews-pi-requests supports GeoJSON response in LocationResponse
- // type.
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- const response = (await provider.getLocations(filter)) as any
- return response as FeatureCollection
+ const response = await provider.getLocations(filter)
+ if (!isFeatureCollection(response)) {
+ throw new Error('Expected GeoJSON FeatureCollection')
+ }
+ return response
+}
+
+function isFeatureCollection(
+ geojson: FeatureCollection | unknown,
+): geojson is FeatureCollection {
+ return (
+ (geojson as FeatureCollection).type ===
+ 'FeatureCollection'
+ )
+}
+
+export async function fetchLocationSetAsGeoJson(
+ baseUrl: string,
+ locationSetId: string,
+): Promise> {
+ const provider = new PiWebserviceProvider(baseUrl, {
+ transformRequestFn: createTransformRequestFn(),
+ })
+ const filter = {
+ documentFormat: DocumentFormat.GEO_JSON,
+ locationSetId: locationSetId,
+ }
+ const response = await provider.getLocations(filter)
+ if (!isFeatureCollection(response)) {
+ throw new Error('Expected GeoJSON FeatureCollection')
+ }
+ return response
}
diff --git a/src/lib/topology/nodes.ts b/src/lib/topology/nodes.ts
index de6f92a42..1de0ddd0b 100644
--- a/src/lib/topology/nodes.ts
+++ b/src/lib/topology/nodes.ts
@@ -165,5 +165,8 @@ export function nodeHasWebDisplay(node: TopologyNode) {
}
export function nodeHasDashboard(node: TopologyNode) {
- return node.id.startsWith('dashboard') && node.topologyNodes !== undefined
+ return (
+ node.id.toLowerCase().includes('dashboard') &&
+ node.topologyNodes !== undefined
+ )
}
diff --git a/src/services/useBaseLayers/index.ts b/src/services/useBaseLayers/index.ts
deleted file mode 100644
index 424c6e4e5..000000000
--- a/src/services/useBaseLayers/index.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-import type { MaybeRefOrGetter, ShallowRef } from 'vue'
-import { shallowRef, toValue, watchEffect } from 'vue'
-import Basemaps from '@/assets/base-layers.json'
-import { MglDefaults } from '@indoorequal/vue-maplibre-gl'
-import { useDark } from '@vueuse/core'
-import { StyleSpecification } from 'maplibre-gl'
-
-export interface UseBaseLayersReturn {
- baseLayerStyle: ShallowRef
- baseLayers: ShallowRef<(string | object)[]>
-}
-
-export function useBaseLayers(
- layerId: MaybeRefOrGetter,
-): UseBaseLayersReturn {
- const baseLayerStyle = shallowRef(
- MglDefaults.style ?? '',
- )
- const baseLayers = shallowRef<(string | object)[]>(Basemaps.baseLayers)
-
- const isDark = useDark()
-
- watchEffect(() => {
- const _layerId = toValue(layerId)
- const layerDefinition = Basemaps.baseLayers.find(
- (layer: any) => layer.id === _layerId,
- )
- if (layerDefinition?.automatic) {
- if (isDark.value === true) {
- const themedLayer = Basemaps.baseLayers.find(
- (layer: any) => layer.id === layerDefinition.automatic.dark,
- )
- if (themedLayer?.style) baseLayerStyle.value = themedLayer?.style
- } else {
- const themedLayer = Basemaps.baseLayers.find(
- (layer: any) => layer.id === layerDefinition.automatic.light,
- )
- if (themedLayer?.style) baseLayerStyle.value = themedLayer?.style
- }
- } else if (layerDefinition?.style) {
- baseLayerStyle.value = layerDefinition?.style
- }
- })
-
- return {
- baseLayerStyle,
- baseLayers,
- }
-}
diff --git a/src/services/useColourScales/index.ts b/src/services/useColourScales/index.ts
new file mode 100644
index 000000000..f8c2e65dd
--- /dev/null
+++ b/src/services/useColourScales/index.ts
@@ -0,0 +1,56 @@
+import type { ColourScale } from '@/stores/colourScales'
+import type { MaybeRefOrGetter, ShallowRef } from 'vue'
+import { computed, toValue } from 'vue'
+
+export interface UseColourScalesReturn {
+ currentScaleId: ShallowRef
+ currentScale: ShallowRef
+ currentScales: ShallowRef
+ currentScaleIsInitialRange: ShallowRef
+ resetCurrentScaleRange: () => void
+}
+
+export function useColourScales(
+ currentIndex: MaybeRefOrGetter,
+ currentIds: MaybeRefOrGetter,
+ scales: MaybeRefOrGetter>,
+): UseColourScalesReturn {
+ const currentScaleId = computed(() => {
+ const _currentIndex = toValue(currentIndex)
+ const _currentIds = toValue(currentIds)
+ return _currentIds[_currentIndex]
+ })
+
+ const currentScale = computed(() => {
+ const _scales = toValue(scales)
+ if (!currentScaleId.value) return
+ return _scales[currentScaleId.value]
+ })
+
+ const currentScales = computed(() => {
+ const _scales = toValue(scales)
+ const _currentIds = toValue(currentIds)
+ return _currentIds.map((id) => _scales[id])
+ })
+
+ const currentScaleIsInitialRange = computed(() => {
+ if (!currentScale.value) return false
+ return (
+ currentScale.value.range.min === currentScale.value.initialRange.min &&
+ currentScale.value.range.max === currentScale.value.initialRange.max
+ )
+ })
+
+ function resetCurrentScaleRange() {
+ if (!currentScale.value) return
+ currentScale.value.range = currentScale.value.initialRange
+ }
+
+ return {
+ currentScaleId,
+ currentScale,
+ currentScales,
+ currentScaleIsInitialRange,
+ resetCurrentScaleRange,
+ }
+}
diff --git a/src/services/useWms/index.ts b/src/services/useWms/index.ts
index 989c4f6ef..336f1b707 100644
--- a/src/services/useWms/index.ts
+++ b/src/services/useWms/index.ts
@@ -95,8 +95,8 @@ export function useWmsLegend(
layerName: MaybeRefOrGetter,
useDisplayUnits: MaybeRefOrGetter,
colorScaleRange: MaybeRefOrGetter,
- style: MaybeRefOrGetter