diff --git a/src/components/generator/DataInput.tsx b/src/components/generator/DataInput.tsx index fd1b3a9..9a978de 100644 --- a/src/components/generator/DataInput.tsx +++ b/src/components/generator/DataInput.tsx @@ -69,9 +69,9 @@ const DataInput: React.FC = ({ }; const addManualRow = () => { - const base = selectedType === 'map' - ? { label: '', lat: '', lng: '', value: '' } - : { label: '', value: '', secondaryValue: '' }; + const base: ManualInputRow = selectedType === 'map' + ? { id: crypto.randomUUID(), label: '', lat: '', lng: '', value: '' } + : { id: crypto.randomUUID(), label: '', value: '', secondaryValue: '' }; onManualRowsChange([...manualRows, base]); }; @@ -263,7 +263,7 @@ const DataInput: React.FC = ({ {manualRows.map((row, index) => ( - + = ({ function getDefaultManualRows(selectedType: string): ManualInputRow[] { if (selectedType === 'map') { - return [{ label: '', lat: '', lng: '', value: '' }]; + return [{ id: crypto.randomUUID(), label: '', lat: '', lng: '', value: '' }]; } - return [{ label: '', value: '', secondaryValue: '' }]; + return [{ id: crypto.randomUUID(), label: '', value: '', secondaryValue: '' }]; } export default DataInput; diff --git a/src/components/generator/previews/D3Preview.tsx b/src/components/generator/previews/D3Preview.tsx index 33b93ae..2805ed7 100644 --- a/src/components/generator/previews/D3Preview.tsx +++ b/src/components/generator/previews/D3Preview.tsx @@ -294,6 +294,12 @@ const D3Preview: React.FC = ({ const script = document.createElement('script'); script.src = 'https://d3js.org/d3.v7.min.js'; + script.onerror = () => { + if (containerRef.current) { + containerRef.current.innerHTML = '

Failed to load D3.js library

'; + } + }; + script.onload = () => { // Re-run the chart creation after D3 loads if (containerRef.current && typeof (window as any).d3 !== 'undefined') { @@ -539,7 +545,8 @@ const D3Preview: React.FC = ({ } return () => { - // Clear the container on cleanup + // Don't remove script element — D3 stays on window and subsequent + // renders take the fast path without re-adding the script tag. if (containerRef.current) { containerRef.current.innerHTML = ''; } diff --git a/src/components/generator/previews/EChartsPreview.tsx b/src/components/generator/previews/EChartsPreview.tsx index bbc26f3..834a1d2 100644 --- a/src/components/generator/previews/EChartsPreview.tsx +++ b/src/components/generator/previews/EChartsPreview.tsx @@ -77,6 +77,12 @@ const EChartsPreview: React.FC = ({ const script = document.createElement('script'); script.src = 'https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js'; + script.onerror = () => { + if (containerRef.current) { + containerRef.current.innerHTML = '

Failed to load ECharts library

'; + } + }; + script.onload = () => { if (typeof (window as any).echarts !== 'undefined') { const echarts = (window as any).echarts; diff --git a/src/components/generator/previews/HighchartsPreview.tsx b/src/components/generator/previews/HighchartsPreview.tsx index e1d163a..7dc64a9 100644 --- a/src/components/generator/previews/HighchartsPreview.tsx +++ b/src/components/generator/previews/HighchartsPreview.tsx @@ -77,6 +77,12 @@ const HighchartsPreview: React.FC = ({ const script = document.createElement('script'); script.src = 'https://code.highcharts.com/highcharts.js'; + script.onerror = () => { + if (containerRef.current) { + containerRef.current.innerHTML = '

Failed to load Highcharts library

'; + } + }; + script.onload = () => { if (typeof (window as any).Highcharts !== 'undefined') { const Highcharts = (window as any).Highcharts; diff --git a/src/components/generator/previews/LeafletPreview.tsx b/src/components/generator/previews/LeafletPreview.tsx index a531a6e..cf23170 100644 --- a/src/components/generator/previews/LeafletPreview.tsx +++ b/src/components/generator/previews/LeafletPreview.tsx @@ -139,6 +139,12 @@ const LeafletPreview: React.FC = ({ link.rel = 'stylesheet'; link.href = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css'; + script.onerror = () => { + if (containerRef.current) { + containerRef.current.innerHTML = '

Failed to load Leaflet library

'; + } + }; + script.onload = () => { if (typeof (window as any).L !== 'undefined') { const L = (window as any).L; diff --git a/src/components/generator/previews/OpenLayersPreview.tsx b/src/components/generator/previews/OpenLayersPreview.tsx index 10d97b7..d0f7514 100644 --- a/src/components/generator/previews/OpenLayersPreview.tsx +++ b/src/components/generator/previews/OpenLayersPreview.tsx @@ -209,6 +209,12 @@ const OpenLayersPreview: React.FC = ({ link.rel = 'stylesheet'; link.href = 'https://cdn.jsdelivr.net/npm/ol@7.4.0/ol.css'; + script.onerror = () => { + if (containerRef.current) { + containerRef.current.innerHTML = '

Failed to load OpenLayers library

'; + } + }; + script.onload = () => { // Re-run the map creation after OpenLayers loads if (containerRef.current && typeof (window as any).ol !== 'undefined') { @@ -286,7 +292,8 @@ const OpenLayersPreview: React.FC = ({ } return () => { - // Clear the container on cleanup + // Don't remove script/link elements — OpenLayers stays on window and + // subsequent renders take the fast path without re-adding the CSS link. if (containerRef.current) { containerRef.current.innerHTML = ''; } diff --git a/src/pages/Generator.tsx b/src/pages/Generator.tsx index b8e2cea..80bb6b8 100644 --- a/src/pages/Generator.tsx +++ b/src/pages/Generator.tsx @@ -46,10 +46,10 @@ const STATUS_TIMEOUT_MS = 2500; const getDefaultManualRows = (selectedType: string): ManualInputRow[] => { if (selectedType === 'map') { - return [{ label: '', lat: '', lng: '', value: '' }]; + return [{ id: crypto.randomUUID(), label: '', lat: '', lng: '', value: '' }]; } - return [{ label: '', value: '', secondaryValue: '' }]; + return [{ id: crypto.randomUUID(), label: '', value: '', secondaryValue: '' }]; }; const toNumber = (value: string | number | undefined): number | null => { @@ -109,6 +109,10 @@ const normalizeMapRows = (rows: TabularRow[]): GeoPoint[] => { return null; } + if (lat < -90 || lat > 90 || lng < -180 || lng > 180) { + return null; + } + return { lat, lng, diff --git a/src/types/data.ts b/src/types/data.ts index 95424d6..555e27a 100644 --- a/src/types/data.ts +++ b/src/types/data.ts @@ -33,6 +33,7 @@ export interface GeoPoint { } export interface ManualInputRow { + id: string; label: string; value: string; secondaryValue?: string;