diff --git a/README.md b/README.md index 372bad1..2f78e67 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ You can preview the production build with `npm run preview`. ## Deploying -New deploys are down with every push to the main branch. You can deploy one manually by running: +New deploys are done with every push to the main branch. You can deploy one manually by running: ```bash npm run deploy diff --git a/src/lib/DataConverter.svelte b/src/lib/DataConverter.svelte new file mode 100644 index 0000000..fe8add4 --- /dev/null +++ b/src/lib/DataConverter.svelte @@ -0,0 +1,167 @@ + + +
+
+

BMLT Data Converter

+
+ event.key === 'Enter' && handleExport()} + placeholder="BMLT URL query..." + /> + + + {#if $errorMessage} +

{$errorMessage}

+ {/if} + + {#if csvDownloadUrl} + Download CSV
+ {/if} + {#if kmlDownloadUrl} + Download KML + {/if} +
Converts BMLT data from JSON to CSV or KML
+
+ +
+
+ + diff --git a/src/lib/DataUtils.ts b/src/lib/DataUtils.ts new file mode 100644 index 0000000..dc9152a --- /dev/null +++ b/src/lib/DataUtils.ts @@ -0,0 +1,155 @@ +import fetchJsonp from 'fetch-jsonp'; + +export async function fetchData(query: string): Promise { + try { + if (!query.includes('/client_interface/json')) { + throw new Error('Query does not contain a valid json endpoint.'); + } + const updatedQuery = query.replace('/client_interface/json/', '/client_interface/jsonp/'); + const response = await fetchJsonp(updatedQuery, { + jsonpCallback: 'callback', + timeout: 10000 // 10 seconds timeout + }); + const data = await response.json(); + if (!Array.isArray(data) || data.length === 0) { + throw new Error('No data found'); + } + return data; + } catch (error) { + if (error instanceof Error) { + throw new Error(error.message || 'Error loading data'); + } else { + throw new Error('Error loading data'); + } + } +} + +export function exportCSV(data: any[]): string { + if (!Array.isArray(data) || data.length === 0) { + throw new Error('No data found'); + } + const header = Object.keys(data[0]).join(','); + const csvRows = data.map((row) => { + const values = Object.values(row).map((value) => { + if (typeof value === 'string') { + let stringValue = value.replace(/"/g, '""'); // Escape double quotes + if (stringValue.includes(',') || stringValue.includes('\n')) { + stringValue = `"${value}"`; // Quote fields containing commas or newlines + } + return stringValue; + } + return String(value); + }); + return values.join(','); + }); + csvRows.unshift(header); // Add header row at the beginning + + const csvString = csvRows.join('\n'); + const blob = new Blob([csvString], { type: 'text/csv' }); + return URL.createObjectURL(blob); +} + +export function exportKML(data: any[]): string { + let kmlContent = ` + + `; + + // Add place marks + data.forEach((meeting) => { + const name = meeting['meeting_name'].trim() || 'NA Meeting'; + const lng = parseFloat(meeting['longitude']); + const lat = parseFloat(meeting['latitude']); + if (!lng || !lat) return ''; + const description = prepareSimpleLine(meeting); + const address = prepareSimpleLine(meeting, false); + + kmlContent += ` + + ${name} + ${address ? `
${address}
` : ''} + ${description ? `${description}` : ''} + + ${lng},${lat} + +
+`; + }); + + kmlContent += ` +
+
`; + + const blob = new Blob([kmlContent], { type: 'application/vnd.google-earth.kml+xml' }); + return URL.createObjectURL(blob); +} + +function prepareSimpleLine(meeting: { [x: string]: any }, withDate = true): string { + const getLocationInfo = () => { + const locationInfo: any[] = []; + const addInfo = (property: string) => { + if (property in meeting) { + const value = meeting[property].trim(); + if (value) { + locationInfo.push(value); + } + } + }; + + addInfo('location_text'); + addInfo('location_street'); + addInfo('location_city_subsection'); + addInfo('location_municipality'); + addInfo('location_neighborhood'); + addInfo('location_province'); + addInfo('location_postal_code_1'); + addInfo('location_nation'); + addInfo('location_info'); + + return locationInfo.join(', '); + }; + + const getDateString = () => { + const weekday_strings = [ + 'All', + 'Sunday', + 'Monday', + 'Tuesday', + 'Wednesday', + 'Thursday', + 'Friday', + 'Saturday' + ]; + const weekday = parseInt(meeting['weekday_tinyint'].trim()); + const weekdayString = weekday_strings[weekday]; + + const startTime = `2000-01-01 ${meeting['start_time']}`; + const time = new Date(startTime); + + if (weekdayString && withDate) { + let dateString = weekdayString; + + if (!isNaN(time.getTime())) { + dateString += `, ${time.toLocaleTimeString('en-US', { + hour: 'numeric', + minute: 'numeric', + hour12: true + })}`; + } + + return dateString; + } + + return ''; + }; + + const locationInfo = getLocationInfo(); + const dateString = getDateString(); + + if (withDate && dateString && locationInfo) { + return `${dateString}, ${locationInfo}`; + } else if (dateString) { + return dateString; + } else { + return locationInfo; + } +} diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 18d4714..e1fd677 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,416 +1,17 @@ - -
-
-

BMLT Data Converter

-
- - - - Download CSV - Download KML -
-
Converts BMLT data from JSON to CSV or KML
-
- -
-
- - + + + + + + BMLT Data Converter + + + + diff --git a/static/apple-icon-180.png b/static/apple-icon-180.png new file mode 100644 index 0000000..0c62fbb Binary files /dev/null and b/static/apple-icon-180.png differ diff --git a/static/favicon.png b/static/favicon.png index 825b9e6..38f4ef4 100644 Binary files a/static/favicon.png and b/static/favicon.png differ diff --git a/static/logo_512.png b/static/logo_512.png new file mode 100644 index 0000000..5273450 Binary files /dev/null and b/static/logo_512.png differ diff --git a/static/manifest-icon-192.maskable.png b/static/manifest-icon-192.maskable.png new file mode 100644 index 0000000..f59d729 Binary files /dev/null and b/static/manifest-icon-192.maskable.png differ diff --git a/static/manifest-icon-512.maskable.png b/static/manifest-icon-512.maskable.png new file mode 100644 index 0000000..fbf9875 Binary files /dev/null and b/static/manifest-icon-512.maskable.png differ diff --git a/static/manifest.json b/static/manifest.json new file mode 100644 index 0000000..48e1554 --- /dev/null +++ b/static/manifest.json @@ -0,0 +1,43 @@ +{ + "background_color": "#8c9b9d", + "display": "standalone", + "icons": [ + { + "src": "manifest-icon-192.maskable.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "any" + }, + { + "src": "manifest-icon-192.maskable.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "manifest-icon-512.maskable.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "any" + }, + { + "src": "manifest-icon-512.maskable.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ], + "orientation": "portrait", + "theme_color": "#8c9b9d", + "scope": "/", + "start_url": "/", + "name": "BMLT Data Converter", + "short_name": "DataConverter", + "description": "Convert BMLT Data", + "related_applications": [ + { + "platform": "webapp", + "url": "https://converter.bmlt.app/manifest.json" + } + ] +}