Skip to content

Commit

Permalink
Significant refactoring of export function
Browse files Browse the repository at this point in the history
might or might not make my life easier. Might or might not fix one nasty bug with subtropical storms.
  • Loading branch information
StrawberryMaster committed Jan 13, 2025
1 parent 1bc6f34 commit c185383
Showing 1 changed file with 176 additions and 99 deletions.
275 changes: 176 additions & 99 deletions static/js/export.js
Original file line number Diff line number Diff line change
@@ -1,130 +1,207 @@
// This script is used to export and import the data from the tracker -- WIP
const SPEED_CONVERSION = {
mph: 1.151,
kph: 1.852
};

const SUCCESS_MESSAGES = {
export: 'Data successfully exported',
download: 'Storm data downloaded',
import: 'Data successfully imported'
};

// for speed conversions
function convertSpeed(speed, fromUnit, toUnit = 'knots') {
if (fromUnit === toUnit) return speed;
return Math.round(fromUnit === 'knots' ?
speed * SPEED_CONVERSION[toUnit] :
speed / SPEED_CONVERSION[fromUnit]);
}

// to safely get the selected value
function getSelectedValue(element, attribute = 'data-selected') {
return element?.getAttribute(attribute)?.replace('°', '') || '';
}

// ta-da! the main export function
function exportData() {
const exportPoints = [];

document.querySelectorAll("#inputs .point").forEach(point => {
const name = point.querySelector(".name").value;

const latitudeInput = point.querySelector("input.latitude");
const latitudeSelect = point.querySelector("select.latitude");
const latitude = latitudeInput.value + latitudeSelect.getAttribute("data-selected").replace("°", "");

const longitudeInput = point.querySelector("input.longitude");
const longitudeSelect = point.querySelector("select.longitude");
const longitude = longitudeInput.value + longitudeSelect.getAttribute("data-selected").replace("°", "");

let speed = Number(point.querySelector("input.speed").value);
const speedSelect = point.querySelector("select.speed");
const unit = speedSelect.getAttribute("data-selected");
if (unit === "mph") {
speed /= 1.151;
speed = Math.round(speed);
} else if (unit === "kph") {
speed /= 1.852;
speed = Math.round(speed);
}
try {
const points = Array.from(document.querySelectorAll("#inputs .point"))
.map(point => {
const latSelect = point.querySelector("select.latitude");
const lonSelect = point.querySelector("select.longitude");
const speedSelect = point.querySelector("select.speed");

const speed = Number(point.querySelector("input.speed").value);
const unit = getSelectedValue(speedSelect);

const stageElement = point.querySelector(".stage");
return {
name: point.querySelector(".name").value?.trim(),
latitude: point.querySelector("input.latitude").value +
getSelectedValue(latSelect),
longitude: point.querySelector("input.longitude").value +
getSelectedValue(lonSelect),
speed: convertSpeed(speed, unit),
stage: stageElement.value || stageElement.getAttribute("data-selected")
};
});

console.log(SUCCESS_MESSAGES.export, points);
return points;
} catch (error) {
console.error('Zamn! Error exporting data:', error);
throw new Error('Failed to export data. Please check the input values.');
}
}

const stage = point.querySelector(".stage").getAttribute("data-selected");
// download the track data
async function downloadTrackData() {
try {
const stormName = document.querySelector(".name").value?.trim() || 'storm_data';
const data = exportData();

exportPoints.push({
name,
latitude,
longitude,
speed,
stage
});
});
// validate the data before creating a blob
if (!Array.isArray(data) || !data.length) {
throw new Error('No valid data to export');
}

console.log(`Whew. Data successfully exported: ${exportPoints}`);
return exportPoints;
}
const blob = new Blob([JSON.stringify(data, null, 2)], {
type: "application/json"
});

// Downloads the storm data
function downloadTrackData() {
const stormName = document.querySelector(".name").value;
const data = exportData();
const blob = new Blob([JSON.stringify(data)], { type: "application/json" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = `${stormName}.json`;
a.click();
console.log(`Storm data downloaded!`);
const url = URL.createObjectURL(blob);
try {
const a = document.createElement("a");
a.href = url;
a.download = `${stormName}.json`;
a.click();
console.log(SUCCESS_MESSAGES.download);
} finally {
URL.revokeObjectURL(url);
}
} catch (error) {
console.error('Zamn! Error downloading data:', error);
alert('Failed to download data. Please try again.');
}
}

document.querySelector("#export-data").addEventListener("click", downloadTrackData);

// Imports the storm data
function importData() {
// basic import functionality
async function importData() {
const input = document.createElement("input");
input.type = "file";
input.accept = "application/json";
input.addEventListener("change", () => {
const file = input.files[0];

input.addEventListener("change", async () => {
try {
const file = input.files?.[0];
if (!file) return;

const data = await readFileAsJSON(file);
if (validateImportData(data)) {
await importPoints(data);
console.log(SUCCESS_MESSAGES.import, data);
}
} catch (error) {
console.error('Zamn! Error importing data:', error);
alert('Failed to import data. Please check the file format.');
}
});

input.click();
}

// read our file as JSON
function readFileAsJSON(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
const data = JSON.parse(reader.result);
console.log(`Imported data successfully: ${data}`);
importPoints(data);
try {
resolve(JSON.parse(reader.result));
} catch (error) {
reject(new Error('Invalid JSON format'));
}
};
reader.onerror = () => reject(new Error('Failed to read file'));
reader.readAsText(file);
});
input.click();
console.log(`Importing data...`);
}

document.querySelector("#import-data").addEventListener("click", importData);
// so we can validate the imported data
function validateImportData(data) {
if (!Array.isArray(data) || !data.length) {
throw new Error('Invalid data format');
}

// We now import the data
function importPoints(data) {
const requiredFields = ['name', 'latitude', 'longitude', 'speed', 'stage'];
data.forEach((point, index) => {
requiredFields.forEach(field => {
if (!(field in point)) {
throw new Error(`Missing ${field} in point ${index + 1}`);
}
});
});

return true;
}

// import the points into the form
async function importPoints(data) {
const inputs = document.querySelector("#inputs");
let new_inputs = document.querySelector(".point");
let newInputs = document.querySelector(".point");

function populatePoint(pointData, pointElement) {
// storm name
pointElement.querySelector(".name").value = pointData.name;

const latitude = pointData.latitude;
pointElement.querySelector("input.latitude").value = latitude.slice(0, -1);
pointElement.querySelector("select.latitude").setAttribute("data-selected", latitude.slice(-1));
if (latitude.slice(-1) === "S") {
pointElement.querySelector("select.latitude").selectedIndex = 1;
}

const longitude = pointData.longitude;
pointElement.querySelector("input.longitude").value = longitude.slice(0, -1);
pointElement.querySelector("select.longitude").setAttribute("data-selected", longitude.slice(-1));
if (longitude.slice(-1) === "W") {
pointElement.querySelector("select.longitude").selectedIndex = 1;
}

let speed = pointData.speed;
const unit = pointElement.querySelector("select.speed").getAttribute("data-selected");
if (unit === "mph") {
speed *= 1.151;
} else if (unit === "kph") {
speed *= 1.852;
}
pointElement.querySelector("input.speed").value = speed;
pointElement.querySelector("select.speed").setAttribute("data-selected", unit);

const stage = pointData.stage;
// latitude and longitude
const latInput = pointElement.querySelector("input.latitude");
const latSelect = pointElement.querySelector("select.latitude");
const lat = pointData.latitude;
latInput.value = lat.replace(/[NS]$/, '');
latSelect.selectedIndex = lat.endsWith('S') ? 1 : 0;
latSelect.setAttribute("data-selected", lat.slice(-1));

const lonInput = pointElement.querySelector("input.longitude");
const lonSelect = pointElement.querySelector("select.longitude");
const lon = pointData.longitude;
lonInput.value = lon.replace(/[EW]$/, '');
lonSelect.selectedIndex = lon.endsWith('W') ? 1 : 0;
lonSelect.setAttribute("data-selected", lon.slice(-1));

// storm speed
const speedInput = pointElement.querySelector("input.speed");
const speedSelect = pointElement.querySelector("select.speed");
const unit = getSelectedValue(speedSelect);
speedInput.value = convertSpeed(pointData.speed, 'knots', unit);

// storm category
const stageSelect = pointElement.querySelector(".stage");
Array.from(stageSelect.options).forEach(option => {
if (option.value === stage) {
option.selected = true;
}
});
stageSelect.setAttribute("data-selected", stage);
stageSelect.value = pointData.stage;
stageSelect.setAttribute("data-selected", pointData.stage);

handle_removal(pointElement.querySelector(".remove"));
if (typeof handle_removal === 'function') {
handle_removal(pointElement.querySelector(".remove"));
}
}

populatePoint(data[0], new_inputs);
// first point
populatePoint(data[0], newInputs);

// remaining points
const fragment = document.createDocumentFragment();
for (let i = 1; i < data.length; i++) {
const cloned_inputs = new_inputs.cloneNode(true);
populatePoint(data[i], cloned_inputs);
inputs.appendChild(cloned_inputs);
cloned_inputs.scrollIntoView();
const clonedInputs = newInputs.cloneNode(true);
populatePoint(data[i], clonedInputs);
fragment.appendChild(clonedInputs);
}
inputs.appendChild(fragment);

// scroll to the last point
const lastPoint = inputs.lastElementChild;
if (lastPoint) {
lastPoint.scrollIntoView({ behavior: 'smooth' });
}
}

document.querySelector("#export-data")?.addEventListener("click", downloadTrackData);
document.querySelector("#import-data")?.addEventListener("click", importData);

0 comments on commit c185383

Please sign in to comment.