Refactor changed widgets to match themes.

This commit is contained in:
2026-10-20 16:19:45 -07:00
parent 0ec4f27640
commit 0c178baa01
5 changed files with 144 additions and 157 deletions
@@ -5,13 +5,17 @@ import Plot from 'react-plotly.js';
import VdapApi from '../../hooks/useVdapApi.js'; import VdapApi from '../../hooks/useVdapApi.js';
import useBuildUrl from "@/hooks/useBuildUrl.js"; import useBuildUrl from "@/hooks/useBuildUrl.js";
// 1. Import your central color dictionary
import { rawWidgetColors } from '../../theme.js';
export default function ExplosionEvents({ settings, widgetID }) { export default function ExplosionEvents({ settings, widgetID }) {
// Plotly expects 'linear' for normal axes // Plotly expects 'linear' for normal axes
const plotYAxisType = settings?.yAxisScale === 'log' ? 'log' : 'linear'; const plotYAxisType = settings?.yAxisScale === 'log' ? 'log' : 'linear';
// 🎨 Let's check color mode so we can make people happy with light and dark // 🎨 Let's check color mode so we can make people happy with light and dark
const { colorMode } = useColorMode(); const { colorMode } = useColorMode();
const isDark = colorMode === 'dark'; // Assign the active palette based on the current mode
const plotColors = colorMode === 'dark' ? rawWidgetColors.dark : rawWidgetColors.light;
// Generate url with input dates, default to 7 day range // Generate url with input dates, default to 7 day range
const url = useBuildUrl('/api/v2/explosion_events', settings?.startDate, settings?.endDate); const url = useBuildUrl('/api/v2/explosion_events', settings?.startDate, settings?.endDate);
@@ -53,7 +57,7 @@ export default function ExplosionEvents({ settings, widgetID }) {
mode: 'markers', mode: 'markers',
marker: { marker: {
size: 7, size: 7,
line: { width: .5, color: 'rgba(255, 255, 255, 0.7)' }, line: { width: .5, color: plotColors.text },
symbol: Symbolway[index % Symbolway.length], symbol: Symbolway[index % Symbolway.length],
}, },
// Replicating the clean D3/Observable tooltip // Replicating the clean D3/Observable tooltip
@@ -62,49 +66,37 @@ export default function ExplosionEvents({ settings, widgetID }) {
'Date (UTC): %{x|%b %d %Y %H:%M}<br>' + 'Date (UTC): %{x|%b %d %Y %H:%M}<br>' +
'Number of Explosions: %{y}<extra></extra>' 'Number of Explosions: %{y}<extra></extra>'
})); }));
}, [apiData]); }, [apiData, plotColors]);
// 2. Build the Layout (Styling and Grid) // 2. Build the Layout (Styling and Grid)
const layout = useMemo(() => { const layout = useMemo(() => {
return { return {
autosize: true, autosize: true,
paper_bgcolor: 'transparent', // Let Chakra UI background show through paper_bgcolor: 'transparent',
plot_bgcolor: 'transparent', plot_bgcolor: 'transparent',
font: { color: '#d1d5db' }, // gray.300 font: { color: plotColors.muted },
margin: { t: 40, r: 20, l: 20, b: 20 }, margin: { t: 40, r: 20, l: 20, b: 20 },
// title: {
// text: '7 Day Consolidated Flight Levels',
// font: { size: 16, color: 'white' },
// x: 0.05
// },
xaxis: { xaxis: {
title: { title: {
text: 'Date (UTC)', text: 'Date (UTC)',
font: { color: 'white', size: 12 } font: { color: plotColors.text, size: 12 }
}, },
type: 'date', type: 'date',
tickformat: '%b %d<br>%Y', // D3 requested %b %d \n %Y formatting tickformat: '%b %d<br>%Y',
gridcolor: '#4b5563', gridcolor: plotColors.grid,
zerolinecolor: '#4b5563', zerolinecolor: plotColors.grid,
automargin: true, automargin: true,
}, },
yaxis: { yaxis: {
title: { title: {
text: 'Number of Explosions', text: 'Number of Explosions',
font: { color: 'white', size: 12 } font: { color: plotColors.text, size: 12 }
}, },
type: plotYAxisType, type: plotYAxisType,
gridcolor: '#4b5563', gridcolor: plotColors.grid,
zerolinecolor: '#ffffff', zerolinecolor: plotColors.text,
automargin: true, automargin: true,
}, },
// legend: {
// orientation: 'h',
// y: -0.2,
// x: 0.5,
// xanchor: 'center'
// },
hovermode: 'closest', hovermode: 'closest',
shapes: [ shapes: [
{ {
@@ -115,37 +107,37 @@ export default function ExplosionEvents({ settings, widgetID }) {
y0: 0, y0: 0,
y1: 0, y1: 0,
yref: 'y', yref: 'y',
line: { color: 'white', width: 1 } line: { color: plotColors.text, width: 1 }
} }
], ],
}; };
}, [apiData, plotYAxisType]); }, [plotYAxisType, plotColors]);
if (isLoading) return ( if (isLoading) return (
<Flex w="100%" h="100%" bg="gray.800" borderRadius="md" align="center" justify="center"> <Flex w="100%" h="100%" bg="transparent" align="center" justify="center">
<Spinner size="xl" color="blue.400" thickness="4px" /> <Spinner size="xl" color="blue.400" thickness="4px" />
</Flex> </Flex>
); );
if (error) return ( if (error) return (
<Flex w="100%" h="100%" bg="gray.800" borderRadius="md" align="center" justify="center" p={4}> <Flex w="100%" h="100%" bg="transparent" align="center" justify="center" p={4}>
<Text color="red.400">{error}</Text> <Text color="red.400">{error}</Text>
</Flex> </Flex>
); );
if (plotTraces.length === 0) return ( if (plotTraces.length === 0) return (
<Flex height="100%" width="100%" align="center" justify="center" bg="gray.800" borderRadius="md"> <Flex height="100%" width="100%" align="center" justify="center" bg="transparent">
<Text color="gray.400">No data available for current filter.</Text> <Text color="var(--chakra-colors-widgetTextMuted)">No data available for current filter.</Text>
</Flex> </Flex>
); );
return ( return (
<Box height="100%" width="100%" bg="transparent" borderRadius="md" p={2} overflow="hidden"> <Box height="100%" width="100%" bg="transparent" overflow="hidden">
<Plot <Plot
key={plotYAxisType} key={`${plotYAxisType}-${colorMode}`}
data={plotTraces} data={plotTraces}
layout={layout} layout={layout}
useResizeHandler={true} // Essential for dashboard grid resizing! useResizeHandler={true}
style={{ width: '100%', height: '100%' }} style={{ width: '100%', height: '100%' }}
config={{ responsive: true, displayModeBar: true, displaylogo: false }} config={{ responsive: true, displayModeBar: true, displaylogo: false }}
/> />
+23 -21
View File
@@ -52,21 +52,22 @@ export default function IvansAlerts({ settings, widgetID }) {
} }
}, [settings, some_data, widgetID, updateSettings]); }, [settings, some_data, widgetID, updateSettings]);
// Cleaned hardcoded grays for transparent so glass shell shows through
if (isLoading) return ( if (isLoading) return (
<Flex w="100%" h="100%" align="center" justify="center" borderRadius="lg" bg="gray.800"> <Flex w="100%" h="100%" align="center" justify="center" borderRadius="lg" bg="transparent">
<Spinner size="xl" color="blue.400" thickness="4px"/> <Spinner size="xl" color="blue.400" thickness="4px"/>
</Flex> </Flex>
); );
if (error) return ( if (error) return (
<Flex w="100%" h="100%" align="center" justify="center" borderRadius="lg" bg="gray.800"> <Flex w="100%" h="100%" align="center" justify="center" borderRadius="lg" bg="transparent">
<Text color="red.400">{error}</Text> <Text color="red.400">{error}</Text>
</Flex> </Flex>
); );
if (some_data.length === 0) return ( if (some_data.length === 0) return (
<Flex w="100%" h="100%" align="center" justify="center" borderRadius="lg" bg="gray.800"> <Flex w="100%" h="100%" align="center" justify="center" borderRadius="lg" bg="transparent">
<Text textAlign="center" color="gray.400">No flight level data available for the selected dates.</Text> <Text textAlign="center" color="widgetTextMuted">No flight level data available for the selected dates.</Text>
</Flex> </Flex>
); );
@@ -78,7 +79,7 @@ export default function IvansAlerts({ settings, widgetID }) {
p={2} gap={2} p={2} gap={2}
overflow="auto" overflow="auto"
borderRadius="xl" borderRadius="xl"
bg="gray.800" bg="transparent" // Let the global widgetRecipe glass shell show through
> >
{some_data.map((item, index) => { {some_data.map((item, index) => {
if (thisWidget?.size === "large") return ( if (thisWidget?.size === "large") return (
@@ -96,31 +97,31 @@ export default function IvansAlerts({ settings, widgetID }) {
gap={2} gap={2}
borderTopRadius="lg" borderTopRadius="lg"
borderBottomRadius={collapsible.open ? "0" : "lg"} borderBottomRadius={collapsible.open ? "0" : "lg"}
bg="gray.600" bg="widgetItemBg"
> >
<Flex direction="column" w="100%" minW={0} textAlign="left" color="gray.100"> <Flex direction="column" w="100%" minW={0} textAlign="left" color="text">
<Text truncate>{item.volcano_name}</Text> <Text truncate>{item.volcano_name}</Text>
<Text truncate fontSize="xs" color="gray.300/90">{item.send_time}</Text> <Text truncate fontSize="xs" color="widgetTextMuted">{item.send_time}</Text>
</Flex> </Flex>
<Flex w="100%" minW={0} justify="right"> <Flex w="100%" minW={0} justify="right">
<Text truncate pl={2} pr={2} borderRadius="lg" bg="gray.800/50" color="gray.100" fontWeight="semibold">{item.report_num}</Text> <Text truncate pl={2} pr={2} borderRadius="lg" bg="widgetBadgeBg" color="text" fontWeight="semibold">{item.report_num}</Text>
</Flex> </Flex>
</Flex> </Flex>
</Collapsible.Trigger> </Collapsible.Trigger>
<Collapsible.Content w="100%" h="auto"> <Collapsible.Content w="100%" h="auto">
{/* This box needs to be at h="100%" for the pdf toolbar to appear */} {/* This box needs to be at h="100%" for the pdf toolbar to appear */}
<Box w="100%" h="100%" p={1} borderBottomRadius="lg" bg="gray.700/45"> <Box w="100%" h="100%" p={1} borderBottomRadius="lg" bg="widgetItemExpandedBg">
<Flex direction="column" gap={2} bg="gray.800/75" borderRadius="lg" p={2} align="end"> <Flex direction="column" gap={2} bg="widgetItemBg" borderRadius="lg" p={2} align="end">
<Text w="100%" h="100%" fontSize="sm" color="gray.100" textAlign="left" whiteSpace="pre-line">{item?.summary}</Text> <Text w="100%" h="100%" fontSize="sm" color="text" textAlign="left" whiteSpace="pre-line">{item?.summary}</Text>
<Text w="fit" fontSize="sm" color="gray.200" pl={1} pr={1} bg="gray.700/50" borderRadius="md">Preparer: {item?.preparer}</Text> <Text w="fit" fontSize="sm" color="text" pl={1} pr={1} bg="widgetBadgeBg" borderRadius="md">Preparer: {item?.preparer}</Text>
</Flex> </Flex>
{item?.files?.map((file, index) => { {item?.files?.map((file, index) => {
return ( return (
<Box w="100%" h="100%" key={index} p={1}> <Box w="100%" h="100%" key={index} p={1}>
<br /> <br />
<Flex mb={2} justify="space-between"> <Flex mb={2} justify="space-between">
<Text pl={1} pr={1} bg="gray.600/50" borderRadius="md" fontSize="xs" color="gray.300">{file?.name}</Text> <Text pl={1} pr={1} bg="widgetBadgeBg" borderRadius="md" fontSize="xs" color="text">{file?.name}</Text>
<Box borderRadius="lg" bg="gray.700" color="gray.300" pl={1} pr={1} fontSize={13} asChild> <Box borderRadius="lg" bg="widgetBadgeBg" color="text" pl={1} pr={1} fontSize={13} asChild>
<a href={`${file?.url}#view=FitV&toolbar=1`} target="_blank" rel="noopener noreferrer"> <a href={`${file?.url}#view=FitV&toolbar=1`} target="_blank" rel="noopener noreferrer">
Open Open
</a> </a>
@@ -131,8 +132,8 @@ export default function IvansAlerts({ settings, widgetID }) {
type="application/pdf" type="application/pdf"
style={{ width: '100%', height: '100%', display: 'block' }} style={{ width: '100%', height: '100%', display: 'block' }}
> >
<Flex w="100%" h="100%" p={4} color="gray.400"> <Flex w="100%" h="100%" p={4} color="widgetTextMuted">
<Text mb={2} color="fg.muted"> <Text mb={2}>
Your browser cannot display this PDF directly. Your browser cannot display this PDF directly.
</Text> </Text>
<a href={file?.url} target="_blank"> <a href={file?.url} target="_blank">
@@ -151,19 +152,20 @@ export default function IvansAlerts({ settings, widgetID }) {
) )
else if (thisWidget?.size === "small") return ( else if (thisWidget?.size === "small") return (
<Flex direction="row" <Flex direction="row"
key={index} // Added missing map key
w="100%" w="100%"
justify="space-between" align="center" justify="space-between" align="center"
p={1} pl={3} pr={3} p={1} pl={3} pr={3}
gap={2} gap={2}
borderRadius="lg" borderRadius="lg"
bg="gray.600" bg="widgetItemBg"
> >
<Flex direction="column" w="100%" minW={0} textAlign="left" color="gray.100"> <Flex direction="column" w="100%" minW={0} textAlign="left" color="text">
<Text truncate>{item.volcano_name}</Text> <Text truncate>{item.volcano_name}</Text>
<Text truncate fontSize="xs" color="gray.300/90">{item.send_time}</Text> <Text truncate fontSize="xs" color="widgetTextMuted">{item.send_time}</Text>
</Flex> </Flex>
<Flex w="100%" minW={0} justify="right"> <Flex w="100%" minW={0} justify="right">
<Text truncate pl={2} pr={2} borderRadius="lg" bg="gray.800/50" color="gray.100" fontWeight="semibold">{item.report_num}</Text> <Text truncate pl={2} pr={2} borderRadius="lg" bg="widgetBadgeBg" color="text" fontWeight="semibold">{item.report_num}</Text>
</Flex> </Flex>
</Flex> </Flex>
) )
+30 -33
View File
@@ -1,10 +1,18 @@
import VdapApi from '../../hooks/useVdapApi.js';
import { useMemo } from 'react'; import { useMemo } from 'react';
import Plot from 'react-plotly.js';
import { Box, Flex, Spinner, Text } from "@chakra-ui/react"; import { Box, Flex, Spinner, Text } from "@chakra-ui/react";
import { useColorMode } from "@/components/ui/color-mode.jsx";
import Plot from 'react-plotly.js';
import VdapApi from '../../hooks/useVdapApi.js';
import useBuildUrl from "@/hooks/useBuildUrl.js"; import useBuildUrl from "@/hooks/useBuildUrl.js";
// 1. Import your central color dictionary
import { rawWidgetColors } from '../../theme.js';
export default function PlumeEvents({ settings, widgetID}) { export default function PlumeEvents({ settings, widgetID}) {
const { colorMode } = useColorMode();
// 2. Assign the active palette based on the current mode
const plotColors = colorMode === 'dark' ? rawWidgetColors.dark : rawWidgetColors.light;
// Build url, defaults to 7 days past // Build url, defaults to 7 days past
const url = useBuildUrl('/api/v2/plume_events', settings?.startDate, settings?.endDate); const url = useBuildUrl('/api/v2/plume_events', settings?.startDate, settings?.endDate);
@@ -44,7 +52,7 @@ export default function PlumeEvents({ settings, widgetID}) {
mode: 'markers', mode: 'markers',
marker: { marker: {
size: 7, size: 7,
line: { width: .5, color: 'rgba(255, 255, 255, 0.7)' }, line: { width: .5, color: plotColors.text },
symbol: Symbolway[index % Symbolway.length], symbol: Symbolway[index % Symbolway.length],
}, },
// Replicating the clean D3/Observable tooltip // Replicating the clean D3/Observable tooltip
@@ -53,88 +61,77 @@ export default function PlumeEvents({ settings, widgetID}) {
'Date (UTC): %{x|%b %d %Y %H:%M}<br>' + 'Date (UTC): %{x|%b %d %Y %H:%M}<br>' +
'Plume Height (m): %{y}<extra></extra>' 'Plume Height (m): %{y}<extra></extra>'
})); }));
}, [apiData]); }, [apiData, plotColors]);
const layout = useMemo(() => { const layout = useMemo(() => {
return { return {
autosize: true, autosize: true,
paper_bgcolor: 'transparent', // Let Chakra UI background show through paper_bgcolor: 'transparent', // Let Chakra UI background show through
plot_bgcolor: 'transparent', plot_bgcolor: 'transparent',
font: { color: '#d1d5db' }, // gray.300 font: { color: plotColors.muted },
margin: { t: 40, r: 20, l: 20, b: 20 }, margin: { t: 40, r: 20, l: 20, b: 20 },
// title: {
// text: '7 Day Consolidated Flight Levels',
// font: { size: 16, color: 'white' },
// x: 0.05
// },
xaxis: { xaxis: {
title: { title: {
text: 'Date (UTC)', text: 'Date (UTC)',
font: { color: 'white', size: 12 } font: { color: plotColors.text, size: 12 }
}, },
type: 'date', type: 'date',
tickformat: '%b %d<br>%Y', // D3 requested %b %d \n %Y formatting tickformat: '%b %d<br>%Y',
gridcolor: '#4b5563', gridcolor: plotColors.grid,
zerolinecolor: '#4b5563', zerolinecolor: plotColors.grid,
automargin: true, automargin: true,
}, },
yaxis: { yaxis: {
title: { title: {
text: 'Plume Height', text: 'Plume Height',
font: { color: 'white', size: 12 } font: { color: plotColors.text, size: 12 }
}, },
type: 'linear', type: 'linear',
gridcolor: '#4b5563', gridcolor: plotColors.grid,
zerolinecolor: '#ffffff', zerolinecolor: plotColors.text,
automargin: true, automargin: true,
}, },
// legend: {
// orientation: 'h',
// y: -0.2,
// x: 0.5,
// xanchor: 'center'
// },
hovermode: 'closest', hovermode: 'closest',
shapes: [ shapes: [
{ {
type: 'line', type: 'line',
x0: 0, x0: 0,
x1: 1, x1: 1,
xref: 'paper', // Spans entire chart width xref: 'paper',
y0: 0, y0: 0,
y1: 0, y1: 0,
yref: 'y', yref: 'y',
line: { color: 'white', width: 1 } line: { color: plotColors.text, width: 1 }
} }
], ],
}; };
}, []); }, [plotColors]);
if (isLoading) return ( if (isLoading) return (
<Flex w="100%" h="100%" bg="gray.800" borderRadius="md" align="center" justify="center"> <Flex w="100%" h="100%" bg="transparent" align="center" justify="center">
<Spinner size="xl" color="blue.400" thickness="4px" /> <Spinner size="xl" color="blue.400" thickness="4px" />
</Flex> </Flex>
); );
if (error) return ( if (error) return (
<Flex w="100%" h="100%" bg="gray.800" borderRadius="md" align="center" justify="center" p={4}> <Flex w="100%" h="100%" bg="transparent" align="center" justify="center" p={4}>
<Text color="red.400">{error}</Text> <Text color="red.400">{error}</Text>
</Flex> </Flex>
); );
if (plotTraces.length === 0) return ( if (plotTraces.length === 0) return (
<Flex height="100%" width="100%" align="center" justify="center" bg="gray.800" borderRadius="md"> <Flex height="100%" width="100%" align="center" justify="center" bg="transparent">
<Text color="gray.400">No data available for current filter.</Text> <Text color="widgetTextMuted">No data available for current filter.</Text>
</Flex> </Flex>
); );
return ( return (
<Box height="100%" width="100%" bg="transparent" borderRadius="md" p={2} overflow="hidden"> <Box height="100%" width="100%" bg="transparent" overflow="hidden">
<Plot <Plot
key={colorMode}
data={plotTraces} data={plotTraces}
layout={layout} layout={layout}
useResizeHandler={true} // Essential for dashboard grid resizing! useResizeHandler={true}
style={{ width: '100%', height: '100%' }} style={{ width: '100%', height: '100%' }}
config={{ responsive: true, displayModeBar: true, displaylogo: false }} config={{ responsive: true, displayModeBar: true, displaylogo: false }}
/> />
+23 -24
View File
@@ -52,25 +52,25 @@ export default function RemoteSensing({ settings, widgetID }) {
}, [settings, data, widgetID, updateSettings]); }, [settings, data, widgetID, updateSettings]);
if (isLoading) return ( if (isLoading) return (
<Flex w="100%" h="100%" align="center" justify="center" borderRadius="md" bg="gray.800"> <Flex w="100%" h="100%" align="center" justify="center" bg="transparent">
<Spinner size="xl" color="blue.400" thickness="4px"/> <Spinner size="xl" color="blue.400" thickness="4px"/>
</Flex> </Flex>
); );
if (error) return ( if (error) return (
<Flex w="100%" h="100%" align="center" justify="center" p={4} borderRadius="md" bg="gray.800"> <Flex w="100%" h="100%" align="center" justify="center" p={4} bg="transparent">
<Text color="red.400">{error}</Text> <Text color="red.400">{error}</Text>
</Flex> </Flex>
); );
if (data.length === 0) return ( if (data.length === 0) return (
<Flex height="100%" width="100%" align="center" justify="center" bg="gray.800" borderRadius="md"> <Flex height="100%" width="100%" align="center" justify="center" bg="transparent">
<Text color="gray.400">No Remote Sensing data available this week.</Text> <Text color="widgetTextMuted">No Remote Sensing data available this week.</Text>
</Flex> </Flex>
); );
return ( return (
<Flex direction="column" w="100%" h="100%" pos="relative" justify="top" align="center" p={2} gap={2} overflow="auto" borderRadius="xl" bg="gray.800"> <Flex direction="column" w="100%" h="100%" pos="relative" justify="top" align="center" p={2} gap={2} overflow="auto" bg="transparent">
{data.map((item, index) => { {data.map((item, index) => {
if (thisWidget?.size === "large" || thisWidget?.size === "medium") return ( if (thisWidget?.size === "large" || thisWidget?.size === "medium") return (
<Collapsible.Root key={index} w="100%"> <Collapsible.Root key={index} w="100%">
@@ -80,14 +80,14 @@ export default function RemoteSensing({ settings, widgetID }) {
<> <>
<Collapsible.Trigger w="100%" overflow="hidden"> <Collapsible.Trigger w="100%" overflow="hidden">
<Flex direction="row" w="100%" h="100%" justify="space-between" align="center" gap={2} p={1} pl={3} pr={3} <Flex direction="row" w="100%" h="100%" justify="space-between" align="center" gap={2} p={1} pl={3} pr={3}
borderTopRadius="lg" borderBottomRadius={collapsible.open ? "0" : "lg"} bg="gray.600" borderTopRadius="lg" borderBottomRadius={collapsible.open ? "0" : "lg"} bg="widgetItemBg"
> >
<Flex direction="column" w="100%" minW={0} textAlign="left" color="gray.100"> <Flex direction="column" w="100%" minW={0} textAlign="left" color="text">
<Text truncate>{item?.volcano_name}</Text> <Text truncate>{item?.volcano_name}</Text>
<Text truncate fontSize="xs" color="gray.300/90">{item?.country}</Text> <Text truncate fontSize="xs" color="widgetTextMuted">{item?.country}</Text>
</Flex> </Flex>
<Flex w="100%" minW={0} justify="right"> <Flex w="100%" minW={0} justify="right">
<Text truncate pl={2} pr={2} borderRadius="lg" bg="gray.800/50" color="gray.100" fontSize="sm" fontWeight="semibold"> <Text truncate pl={2} pr={2} borderRadius="lg" bg="widgetBadgeBg" color="text" fontSize="sm" fontWeight="semibold">
{item?.report_time} {item?.report_time}
</Text> </Text>
</Flex> </Flex>
@@ -95,10 +95,10 @@ export default function RemoteSensing({ settings, widgetID }) {
</Collapsible.Trigger> </Collapsible.Trigger>
<Collapsible.Content w="100%" h="auto"> <Collapsible.Content w="100%" h="auto">
{/* This box needs to be at h="100%" for the pdf toolbar to appear */} {/* This box needs to be at h="100%" for the pdf toolbar to appear */}
<Box w="100%" h="100%" p={1} borderBottomRadius="lg" bg="gray.700/45"> <Box w="100%" h="100%" p={1} borderBottomRadius="lg" bg="widgetItemExpandedBg">
<Flex direction="column" gap={2} bg="gray.800/75" borderRadius="lg" p={2} align="end"> <Flex direction="column" gap={2} bg="widgetItemBg" borderRadius="lg" p={2} align="end">
<Text w="100%" h="100%" fontSize="sm" color="gray.100" textAlign="left" whiteSpace="pre-line">{item?.summary}</Text> <Text w="100%" h="100%" fontSize="sm" color="text" textAlign="left" whiteSpace="pre-line">{item?.summary}</Text>
<Text w="fit" fontSize="sm" color="gray.200" pl={1} pr={1} bg="gray.700/50" borderRadius="md">Source: {item?.sender}</Text> <Text w="fit" fontSize="sm" color="text" pl={1} pr={1} bg="widgetBadgeBg" borderRadius="md">Source: {item?.sender}</Text>
</Flex> </Flex>
{item?.files?.map((file, index) => { {item?.files?.map((file, index) => {
@@ -107,15 +107,15 @@ export default function RemoteSensing({ settings, widgetID }) {
<Box w="100%" h="100%" key={index} p={1}> <Box w="100%" h="100%" key={index} p={1}>
<br /> <br />
<Flex dir="row" justify="space-between" align="center" mb={2}> <Flex dir="row" justify="space-between" align="center" mb={2}>
<Text pl={1} pr={1} bg="gray.600/50" borderRadius="md" fontSize="xs" color="gray.300">{file?.name}</Text> <Text pl={1} pr={1} bg="widgetBadgeBg" borderRadius="md" fontSize="xs" color="text">{file?.name}</Text>
<Text pl={1} pr={1} bg="gray.600/50" borderRadius="md" fontSize="xs" color="gray.300/90">PDF</Text> <Text pl={1} pr={1} bg="widgetBadgeBg" borderRadius="md" fontSize="xs" color="widgetTextMuted">PDF</Text>
</Flex> </Flex>
<object <object
data={`${file?.url}#view=FitH&toolbar=1`} data={`${file?.url}#view=FitH&toolbar=1`}
type="application/pdf" type="application/pdf"
style={{width: '100%', height: '100%', display: 'block'}} style={{width: '100%', height: '100%', display: 'block'}}
> >
<Flex w="100%" h="100%" p={4} color="gray.400"> <Flex w="100%" h="100%" p={4} color="widgetTextMuted">
<Text mb={2} color="fg.muted"> <Text mb={2} color="fg.muted">
Your browser cannot display this PDF directly. Your browser cannot display this PDF directly.
</Text> </Text>
@@ -132,8 +132,8 @@ export default function RemoteSensing({ settings, widgetID }) {
<Box key={index} w="100%" h="100%" p={1}> <Box key={index} w="100%" h="100%" p={1}>
<br /> <br />
<Flex dir="row" justify="space-between" align="center" mb={2}> <Flex dir="row" justify="space-between" align="center" mb={2}>
<Text pl={1} pr={1} bg="gray.600/50" borderRadius="md" fontSize="xs" color="gray.300">{file?.name}</Text> <Text pl={1} pr={1} bg="widgetBadgeBg" borderRadius="md" fontSize="xs" color="text">{file?.name}</Text>
<Text pl={1} pr={1} bg="gray.600/50" borderRadius="md" fontSize="xs" color="gray.300/90">PNG</Text> <Text pl={1} pr={1} bg="widgetBadgeBg" borderRadius="md" fontSize="xs" color="widgetTextMuted">PNG</Text>
</Flex> </Flex>
<a href={file?.url} target="_blank" rel="noopener noreferrer"> <a href={file?.url} target="_blank" rel="noopener noreferrer">
<img src={file?.url} alt={file?.name} /> <img src={file?.url} alt={file?.name} />
@@ -146,7 +146,7 @@ export default function RemoteSensing({ settings, widgetID }) {
<> <>
<br /> <br />
<Flex key={index} dir="row" w="100%" h="100%" p={1} align="center" justify="space-between"> <Flex key={index} dir="row" w="100%" h="100%" p={1} align="center" justify="space-between">
<Text pl={1} pr={1} bg="gray.600/50" borderRadius="md" fontSize="xs" color="gray.300">{file?.name}</Text> <Text pl={1} pr={1} bg="widgetBadgeBg" borderRadius="md" fontSize="xs" color="text">{file?.name}</Text>
<Button asChild variant="surface" size="xxs" fontSize="xs" pl={2} pr={2}> <Button asChild variant="surface" size="xxs" fontSize="xs" pl={2} pr={2}>
<a href={file?.url} target="_blank" rel="noopener noreferrer">Download</a> <a href={file?.url} target="_blank" rel="noopener noreferrer">Download</a>
</Button> </Button>
@@ -162,17 +162,17 @@ export default function RemoteSensing({ settings, widgetID }) {
</Collapsible.Root> </Collapsible.Root>
) )
else if (thisWidget?.size === "small") return ( else if (thisWidget?.size === "small") return (
<Flex key={index} direction="row" w="100%" justify="space-between" align="center" gap={2} p={1} pl={3} pr={3} borderRadius="lg" bg="gray.600"> <Flex key={index} direction="row" w="100%" justify="space-between" align="center" gap={2} p={1} pl={3} pr={3} borderRadius="lg" bg="widgetItemBg">
<Flex direction="column" w="100%" minW={0} textAlign="left" color="gray.100"> <Flex direction="column" w="100%" minW={0} textAlign="left" color="text">
<Text truncate>{item?.volcano_name}</Text> <Text truncate>{item?.volcano_name}</Text>
<Text truncate fontSize="xs" color="gray.300/90">{item?.country}</Text> <Text truncate fontSize="xs" color="widgetTextMuted">{item?.country}</Text>
</Flex> </Flex>
<Flex w="100%" minW={0} justify="right"> <Flex w="100%" minW={0} justify="right">
<Text <Text
truncate truncate
pl={2} pr={2} pl={2} pr={2}
borderRadius="lg" borderRadius="lg"
bg="gray.800/50" color="gray.100" bg="widgetBadgeBg" color="text"
fontSize="sm" fontWeight="semibold" fontSize="sm" fontWeight="semibold"
> >
{item?.report_time} {item?.report_time}
@@ -185,7 +185,6 @@ export default function RemoteSensing({ settings, widgetID }) {
); );
} }
function ascTimeVolcanoSort(a, b) { function ascTimeVolcanoSort(a, b) {
if (a.volcano_name < b.volcano_name) return -1 if (a.volcano_name < b.volcano_name) return -1
else if (a.volcano_name > b.volcano_name) return 1 else if (a.volcano_name > b.volcano_name) return 1
+25 -28
View File
@@ -4,7 +4,15 @@ import Plot from 'react-plotly.js';
import { Box, Flex, Spinner, Text } from "@chakra-ui/react"; import { Box, Flex, Spinner, Text } from "@chakra-ui/react";
import useBuildUrl from "@/hooks/useBuildUrl.js"; import useBuildUrl from "@/hooks/useBuildUrl.js";
// 1. Import Chakra color mode and your central color dictionary
import { useColorMode } from "@/components/ui/color-mode.jsx";
import { rawWidgetColors } from '../../theme.js';
export default function SeismicEvents({ settings, widgetID}) { export default function SeismicEvents({ settings, widgetID}) {
const { colorMode } = useColorMode();
// 2. Assign the active palette based on the current mode
const plotColors = colorMode === 'dark' ? rawWidgetColors.dark : rawWidgetColors.light;
// build url, defaults to 7 days past // build url, defaults to 7 days past
const url = useBuildUrl('/api/v2/seismic_events', settings?.startDate, settings?.endDate); const url = useBuildUrl('/api/v2/seismic_events', settings?.startDate, settings?.endDate);
@@ -44,7 +52,7 @@ export default function SeismicEvents({ settings, widgetID}) {
mode: 'markers', mode: 'markers',
marker: { marker: {
size: 7, size: 7,
line: { width: .5, color: 'rgba(255, 255, 255, 0.7)' }, line: { width: .5, color: plotColors.text },
symbol: Symbolway[index % Symbolway.length], symbol: Symbolway[index % Symbolway.length],
}, },
// Replicating the clean D3/Observable tooltip // Replicating the clean D3/Observable tooltip
@@ -53,48 +61,36 @@ export default function SeismicEvents({ settings, widgetID}) {
'Date (UTC): %{x|%b %d %Y %H:%M}<br>' + 'Date (UTC): %{x|%b %d %Y %H:%M}<br>' +
'Number of Seismic Events: %{y}<extra></extra>' 'Number of Seismic Events: %{y}<extra></extra>'
})); }));
}, [apiData]); }, [apiData, plotColors]);
const layout = useMemo(() => { const layout = useMemo(() => {
return { return {
autosize: true, autosize: true,
paper_bgcolor: 'transparent', // Let Chakra UI background show through paper_bgcolor: 'transparent', // Let Chakra UI background show through
plot_bgcolor: 'transparent', plot_bgcolor: 'transparent',
font: { color: '#d1d5db' }, // gray.300 font: { color: plotColors.muted },
margin: { t: 40, r: 20, l: 20, b: 20 }, margin: { t: 40, r: 20, l: 20, b: 20 },
// title: {
// text: '',
// font: { size: 16, color: 'white' },
// x: 0.05
// },
xaxis: { xaxis: {
title: { title: {
text: 'Date (UTC)', text: 'Date (UTC)',
font: { color: 'white', size: 12 } font: { color: plotColors.text, size: 12 }
}, },
type: 'date', type: 'date',
tickformat: '%b %d<br>%Y', // D3 requested %b %d \n %Y formatting tickformat: '%b %d<br>%Y', // D3 requested %b %d \n %Y formatting
gridcolor: '#4b5563', gridcolor: plotColors.grid,
zerolinecolor: '#4b5563', zerolinecolor: plotColors.grid,
automargin: true, automargin: true,
}, },
yaxis: { yaxis: {
title: { title: {
text: 'Seismic Count', text: 'Seismic Count',
font: { color: 'white', size: 12 } font: { color: plotColors.text, size: 12 }
}, },
type: 'linear', type: 'linear',
gridcolor: '#4b5563', gridcolor: plotColors.grid,
zerolinecolor: '#ffffff', zerolinecolor: plotColors.text,
automargin: true, automargin: true,
}, },
// legend: {
// orientation: 'h',
// y: -0.2,
// x: 0.5,
// xanchor: 'center'
// },
hovermode: 'closest', hovermode: 'closest',
shapes: [ shapes: [
{ {
@@ -105,33 +101,34 @@ export default function SeismicEvents({ settings, widgetID}) {
y0: 0, y0: 0,
y1: 0, y1: 0,
yref: 'y', yref: 'y',
line: { color: 'white', width: 1 } line: { color: plotColors.text, width: 1 }
} }
], ],
}; };
}, []); }, [plotColors]);
if (isLoading) return ( if (isLoading) return (
<Flex w="100%" h="100%" bg="gray.800" borderRadius="md" align="center" justify="center"> <Flex w="100%" h="100%" bg="transparent" align="center" justify="center">
<Spinner size="xl" color="blue.400" thickness="4px" /> <Spinner size="xl" color="blue.400" thickness="4px" />
</Flex> </Flex>
); );
if (error) return ( if (error) return (
<Flex w="100%" h="100%" bg="gray.800" borderRadius="md" align="center" justify="center" p={4}> <Flex w="100%" h="100%" bg="transparent" align="center" justify="center" p={4}>
<Text color="red.400">{error}</Text> <Text color="red.400">{error}</Text>
</Flex> </Flex>
); );
if (plotTraces.length === 0) return ( if (plotTraces.length === 0) return (
<Flex height="100%" width="100%" align="center" justify="center" bg="gray.800" borderRadius="md"> <Flex height="100%" width="100%" align="center" justify="center" bg="transparent">
<Text color="gray.400">No data available for current filter.</Text> <Text color="var(--chakra-colors-widgetTextMuted)">No data available for current filter.</Text>
</Flex> </Flex>
); );
return ( return (
<Box height="100%" width="100%" bg="transparent" borderRadius="md" p={2} overflow="hidden"> <Box height="100%" width="100%" bg="transparent" overflow="hidden">
<Plot <Plot
key={colorMode}
data={plotTraces} data={plotTraces}
layout={layout} layout={layout}
useResizeHandler={true} // Essential for dashboard grid resizing! useResizeHandler={true} // Essential for dashboard grid resizing!