Minimal error handling
This commit is contained in:
parent
1a89d49e8a
commit
1e09b2d7ef
4 changed files with 184 additions and 121 deletions
|
|
@ -13,53 +13,105 @@
|
|||
} from "@sveltestrap/sveltestrap";
|
||||
|
||||
import { getForecast } from "$lib/prediction";
|
||||
import type { FlightParameters, ProfileName } from "$lib/types";
|
||||
import { PROFILE_MAP } from "$lib/types";
|
||||
import type { FlightParameters, ProfileName, ProfileIdentifier } from "$lib/types";
|
||||
import { PROFILE_MAP, PROFILE_NAMES } from "$lib/types";
|
||||
import { SavedPointsStore, FlightParametersStore, writeLocalStorage } from "$lib/stores";
|
||||
import { getSavedPoints } from "$lib/api/points";
|
||||
import type { SavedPoint } from "$lib/types";
|
||||
import { onMount } from "svelte";
|
||||
import { addToast } from "$lib/components/Toast.svelte";
|
||||
|
||||
let isCollapsed = false;
|
||||
let selectedProfile: ProfileName = "Normal";
|
||||
let startPoint = "Custom";
|
||||
// TODO: Move to $lib/utils/datetime.js
|
||||
// function getCurrentDateTime() {
|
||||
// const now = new Date();
|
||||
// return {
|
||||
// date: now.toISOString().split("T")[0],
|
||||
// time: now.toISOString().split("T")[1].split(".")[0]
|
||||
// };
|
||||
// }
|
||||
|
||||
let element: HTMLDivElement | null = null;
|
||||
// TODO: Move to $lib/utils/validation.js
|
||||
// function validateCoordinates(lat: string, lng: string): { isValid: boolean; lat?: number; lng?: number } {
|
||||
// const latNum = parseFloat(lat);
|
||||
// const lngNum = parseFloat(lng);
|
||||
// if (isNaN(latNum) || isNaN(lngNum)) {
|
||||
// return { isValid: false };
|
||||
// }
|
||||
// return { isValid: true, lat: latNum, lng: lngNum };
|
||||
// }
|
||||
|
||||
// TODO: Move to $lib/components/PredictionService.js
|
||||
// async function handlePredictionRequest(params: FlightParameters) {
|
||||
// try {
|
||||
// const response = await getForecast(params);
|
||||
// // Emit event or update store
|
||||
// return response;
|
||||
// } catch (error) {
|
||||
// console.error("Error fetching forecast:", error);
|
||||
// throw error;
|
||||
// }
|
||||
// }
|
||||
|
||||
interface Props {
|
||||
handleClickSelectOnMap?: () => void;
|
||||
handleClickPointListModal?: () => void;
|
||||
}
|
||||
|
||||
let {
|
||||
handleClickSelectOnMap = () => console.log("Select on map clicked"),
|
||||
handleClickPointListModal = () => console.log("Open Point List Modal")
|
||||
}: Props = $props();
|
||||
|
||||
// State
|
||||
let isCollapsed = $state(false);
|
||||
let startPoint = $state("Custom");
|
||||
|
||||
// Initialize date/time
|
||||
const now = new Date();
|
||||
let startDate = now.toISOString().split("T")[0]; // YYYY-MM-DD
|
||||
let startTime = now.toISOString().split("T")[1].split(".")[0]; // HH:MM:SS
|
||||
let startDate = $state(now.toISOString().split("T")[0]);
|
||||
let startTime = $state(now.toISOString().split("T")[1].split(".")[0]);
|
||||
|
||||
let inputLat = $FlightParametersStore.launch_latitude.toFixed(6).toString();
|
||||
let inputLng = $FlightParametersStore.launch_longitude.toFixed(6).toString();
|
||||
// Coordinate inputs
|
||||
let inputLat = $state($FlightParametersStore.launch_latitude.toFixed(6));
|
||||
let inputLng = $state($FlightParametersStore.launch_longitude.toFixed(6));
|
||||
|
||||
$: $FlightParametersStore = {
|
||||
...$FlightParametersStore,
|
||||
profile: PROFILE_MAP[selectedProfile],
|
||||
};
|
||||
// Reactive effects
|
||||
$effect(() => {
|
||||
$FlightParametersStore;
|
||||
});
|
||||
|
||||
$: $SavedPointsStore, setCoordinatesFromSavedPoint();
|
||||
$effect(() => {
|
||||
// Watch for saved points changes
|
||||
$SavedPointsStore;
|
||||
|
||||
// This effect also depends on startPoint, which is changed by setCoordinatesFromSavedPoint
|
||||
// via other state variables. To avoid an infinite loop, we only call this when startPoint changes.
|
||||
const sp = startPoint;
|
||||
setCoordinatesFromSavedPoint();
|
||||
});
|
||||
|
||||
// Functions
|
||||
function setCoordinatesFromSavedPoint() {
|
||||
console.log("Start point changed:", startPoint);
|
||||
|
||||
if (startPoint === "Custom") {
|
||||
$FlightParametersStore.launch_latitude = parseFloat(inputLat);
|
||||
$FlightParametersStore.launch_longitude = parseFloat(inputLng);
|
||||
} else {
|
||||
const lat = parseFloat(inputLat);
|
||||
const lng = parseFloat(inputLng);
|
||||
if (!isNaN(lat) && !isNaN(lng)) {
|
||||
$FlightParametersStore.launch_latitude = lat;
|
||||
$FlightParametersStore.launch_longitude = lng;
|
||||
}
|
||||
else {
|
||||
const selectedOption = document.querySelector(
|
||||
`#startPoint option[value="${startPoint}"]`
|
||||
) as HTMLOptionElement;
|
||||
|
||||
if (selectedOption) {
|
||||
const lat = parseFloat(selectedOption.getAttribute("data-lat") || "0");
|
||||
const lng = parseFloat(selectedOption.getAttribute("data-lng") || "0");
|
||||
const alt = parseFloat(selectedOption.getAttribute("data-alt") || "0");
|
||||
inputLat = lat.toFixed(6).toString();
|
||||
inputLng = lng.toFixed(6).toString();
|
||||
|
||||
inputLat = lat.toFixed(6);
|
||||
inputLng = lng.toFixed(6);
|
||||
$FlightParametersStore.launch_latitude = lat;
|
||||
$FlightParametersStore.launch_longitude = lng;
|
||||
$FlightParametersStore.launch_altitude = alt;
|
||||
console.log("Updated position from saved point:", lat, lng, alt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -67,102 +119,84 @@
|
|||
function setToCustomOnChange() {
|
||||
if (startPoint !== "Custom") {
|
||||
startPoint = "Custom";
|
||||
console.log("Switched to Custom point");
|
||||
}
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
// Load saved points from the server or local storage
|
||||
const savedPoints = await getSavedPoints();
|
||||
SavedPointsStore.set(savedPoints);
|
||||
});
|
||||
|
||||
const handleGetPrediction = async () => {
|
||||
console.log("Fetching prediction with parameters:", $FlightParametersStore);
|
||||
console.log(startDate, startTime);
|
||||
|
||||
$FlightParametersStore.launch_datetime = `${startDate}T${startTime}Z`;
|
||||
writeLocalStorage<FlightParameters>("flightParameters", $FlightParametersStore);
|
||||
|
||||
try {
|
||||
const response = await getForecast($FlightParametersStore);
|
||||
console.log(response);
|
||||
// TODO: Notify other components of the new prediction.
|
||||
// const dispatch = createEventDispatcher();
|
||||
// dispatch('newPrediction', response);
|
||||
} catch (error) {
|
||||
console.error("Error fetching forecast:", error);
|
||||
// TODO: Display a user-friendly error message in the UI.
|
||||
}
|
||||
};
|
||||
|
||||
export let handleClickSelectOnMap = () => {
|
||||
console.log("Select on map clicked");
|
||||
}
|
||||
|
||||
export let handleClickPointListModal = () => {
|
||||
console.log("Open Point List Modal");
|
||||
};
|
||||
|
||||
const applyCoordinatesFromInput = () => {
|
||||
function applyCoordinatesFromInput() {
|
||||
const lat = parseFloat(inputLat);
|
||||
const lng = parseFloat(inputLng);
|
||||
|
||||
if (!isNaN(lat) && !isNaN(lng)) {
|
||||
$FlightParametersStore.launch_latitude = lat;
|
||||
$FlightParametersStore.launch_longitude = lng;
|
||||
console.log(
|
||||
"Updated position:",
|
||||
$FlightParametersStore.launch_latitude,
|
||||
$FlightParametersStore.launch_longitude,
|
||||
);
|
||||
} else {
|
||||
console.error("Invalid coordinate input");
|
||||
// TODO: Show a validation error to the user.
|
||||
// TODO: Show validation error to user
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the launch coordinates.
|
||||
* @param {number} lat The new latitude.
|
||||
* @param {number} lng The new longitude.
|
||||
*/
|
||||
export const updateLaunchPosition = (lat: number, lng: number) => {
|
||||
async function handleGetPrediction() {
|
||||
$FlightParametersStore.launch_datetime = `${startDate}T${startTime}Z`;
|
||||
writeLocalStorage<FlightParameters>("flightParameters", $FlightParametersStore);
|
||||
|
||||
getForecast($FlightParametersStore).then(
|
||||
(data) => {
|
||||
console.log("Forecast request successful:", data);
|
||||
addToast({
|
||||
header: "Forecast Request",
|
||||
body: "Forecast request successful!",
|
||||
color: "success"
|
||||
});
|
||||
// Handle the response data as needed
|
||||
}).catch((error) => {
|
||||
console.error("Error getting forecast:", error);
|
||||
addToast({
|
||||
header: "Forecast Error",
|
||||
body: `Error getting forecast: ${error.message}`,
|
||||
color: "danger"
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function toggleCollapse() {
|
||||
isCollapsed = !isCollapsed;
|
||||
}
|
||||
|
||||
// Exported functions for parent components
|
||||
export function updateLaunchPosition(lat: number, lng: number) {
|
||||
$FlightParametersStore.launch_latitude = lat;
|
||||
$FlightParametersStore.launch_longitude = lng;
|
||||
console.log("Launch position updated:", lat, lng);
|
||||
inputLat = lat.toFixed(6).toString();
|
||||
inputLng = lng.toFixed(6).toString();
|
||||
inputLat = lat.toFixed(6);
|
||||
inputLng = lng.toFixed(6);
|
||||
setToCustomOnChange();
|
||||
};
|
||||
}
|
||||
|
||||
export const getElement = () => {
|
||||
return element;
|
||||
};
|
||||
export function getSelectedProfile() {
|
||||
return $FlightParametersStore.profile;
|
||||
}
|
||||
|
||||
export const getSelectedProfile = () => {
|
||||
return selectedProfile;
|
||||
};
|
||||
export function selectProfile(profile: ProfileName) {
|
||||
$FlightParametersStore.profile = profile;
|
||||
}
|
||||
|
||||
export const selectProfile = (profile: ProfileName) => {
|
||||
selectedProfile = profile;
|
||||
$FlightParametersStore.profile = PROFILE_MAP[selectedProfile];
|
||||
console.log("Selected profile:", selectedProfile);
|
||||
};
|
||||
|
||||
export const collapsePanel = () => {
|
||||
export function collapsePanel() {
|
||||
isCollapsed = true;
|
||||
};
|
||||
}
|
||||
|
||||
export const expandPanel = () => {
|
||||
export function expandPanel() {
|
||||
isCollapsed = false;
|
||||
};
|
||||
}
|
||||
|
||||
export const togglePanel = () => {
|
||||
export function togglePanel() {
|
||||
isCollapsed = !isCollapsed;
|
||||
};
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
const savedPoints = await getSavedPoints();
|
||||
SavedPointsStore.set(savedPoints);
|
||||
});
|
||||
</script>
|
||||
|
||||
<Card>
|
||||
<CardHeader
|
||||
class="bg-primary text-white d-flex justify-content-between align-items-center card-header p-1 px-3"
|
||||
|
|
@ -171,12 +205,11 @@
|
|||
<button
|
||||
type="button"
|
||||
class="d-flex w-100 justify-content-between align-items-center bg-transparent border-0 p-0"
|
||||
style="width:100%;"
|
||||
aria-label="Свернуть/развернуть параметры прогнозирования"
|
||||
on:click={() => (isCollapsed = !isCollapsed)}
|
||||
onclick={toggleCollapse}
|
||||
>
|
||||
<b class="card-title mb-0 text-white p-0">Параметры прогнозирования</b>
|
||||
<Button class="p-0" size="sm" color="primary" on:click={() => (isCollapsed = !isCollapsed)}>
|
||||
<Button class="p-0" size="sm" color="primary" onclick={toggleCollapse}>
|
||||
{#if isCollapsed}
|
||||
<Icon name="caret-left-fill" class="text-white" />
|
||||
{:else}
|
||||
|
|
@ -185,6 +218,7 @@
|
|||
</Button>
|
||||
</button>
|
||||
</CardHeader>
|
||||
|
||||
{#if !isCollapsed}
|
||||
<CardBody>
|
||||
<div class="d-flex gap-2">
|
||||
|
|
@ -201,10 +235,10 @@
|
|||
<FormGroup spacing="mb-2">
|
||||
<Label for="flightProfile" class="form-label">Профиль полета:</Label>
|
||||
<InputGroup size="sm">
|
||||
<Input type="select" id="flightProfile" bind:value={selectedProfile}>
|
||||
<Input type="select" id="flightProfile" bind:value={$FlightParametersStore.profile}>
|
||||
<optgroup label="Стандартные профили">
|
||||
{#each Object.keys(PROFILE_MAP) as profileName}
|
||||
<option value={profileName}>{profileName}</option>
|
||||
<option value={PROFILE_MAP[profileName as ProfileName]}>{profileName}</option>
|
||||
{/each}
|
||||
</optgroup>
|
||||
<optgroup label="Пользовательские профили">
|
||||
|
|
@ -215,7 +249,7 @@
|
|||
color="secondary"
|
||||
size="sm"
|
||||
title="Edit profile"
|
||||
disabled={selectedProfile !== "Custom"}
|
||||
disabled={$FlightParametersStore.profile !== "custom_profile"}
|
||||
>
|
||||
<span>Редакт.</span>
|
||||
<Icon name="gear-fill" />
|
||||
|
|
@ -226,7 +260,7 @@
|
|||
<FormGroup spacing="mb-2">
|
||||
<Label for="startPoint" class="form-label">Точка старта:</Label>
|
||||
<InputGroup size="sm">
|
||||
<Input type="select" id="startPoint" bind:value={startPoint} on:change={setCoordinatesFromSavedPoint}>
|
||||
<Input type="select" id="startPoint" bind:value={startPoint} onchange={setCoordinatesFromSavedPoint}>
|
||||
<optgroup label="Сохраненные точки">
|
||||
{#each $SavedPointsStore as point}
|
||||
<option value={point.name} data-lat={point.lat} data-lng={point.lon} data-alt={point.alt}>
|
||||
|
|
@ -241,7 +275,7 @@
|
|||
<Button
|
||||
color="secondary"
|
||||
title="Edit Saved Locations"
|
||||
on:click={handleClickPointListModal}
|
||||
onclick={handleClickPointListModal}
|
||||
>
|
||||
<span>Редакт.</span>
|
||||
<Icon name="journal-bookmark-fill" />
|
||||
|
|
@ -252,12 +286,29 @@
|
|||
<FormGroup spacing="mb-2">
|
||||
<Label for="latitude" class="form-label">Широта/Долгота:</Label>
|
||||
<InputGroup size="sm">
|
||||
<Input id="latitude" type="text" bind:value={inputLat} placeholder="Latitude" on:change={setToCustomOnChange} />
|
||||
<Input
|
||||
id="latitude"
|
||||
type="text"
|
||||
bind:value={inputLat}
|
||||
placeholder="Latitude"
|
||||
onchange={setToCustomOnChange}
|
||||
/>
|
||||
<InputGroupText>/</InputGroupText>
|
||||
<Input id="longitude" type="text" bind:value={inputLng} placeholder="Longitude" on:change={setToCustomOnChange} />
|
||||
<Button color="success" size="sm" on:click={applyCoordinatesFromInput} title="Apply Coordinates"
|
||||
>✓</Button
|
||||
<Input
|
||||
id="longitude"
|
||||
type="text"
|
||||
bind:value={inputLng}
|
||||
placeholder="Longitude"
|
||||
onchange={setToCustomOnChange}
|
||||
/>
|
||||
<Button
|
||||
color="success"
|
||||
size="sm"
|
||||
onclick={applyCoordinatesFromInput}
|
||||
title="Apply Coordinates"
|
||||
>
|
||||
✓
|
||||
</Button>
|
||||
</InputGroup>
|
||||
</FormGroup>
|
||||
|
||||
|
|
@ -266,8 +317,10 @@
|
|||
color="outline-secondary"
|
||||
size="sm"
|
||||
class="w-100"
|
||||
on:click={ handleClickSelectOnMap }>Указать на карте</Button
|
||||
onclick={handleClickSelectOnMap}
|
||||
>
|
||||
Указать на карте
|
||||
</Button>
|
||||
</FormGroup>
|
||||
|
||||
<div class="d-flex gap-2">
|
||||
|
|
@ -277,7 +330,7 @@
|
|||
type="number"
|
||||
id="startHeight"
|
||||
class="form-control-sm"
|
||||
on:change={setToCustomOnChange}
|
||||
onchange={setToCustomOnChange}
|
||||
bind:value={$FlightParametersStore.launch_altitude}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
|
@ -292,7 +345,7 @@
|
|||
</FormGroup>
|
||||
</div>
|
||||
|
||||
{#if selectedProfile != "Custom"}
|
||||
{#if $FlightParametersStore.profile !== "custom_profile"}
|
||||
<div class="mb-2 d-flex gap-2">
|
||||
<FormGroup class="flex-fill w-50" spacing="mb-2">
|
||||
<Label for="ascentRate" class="form-label">Скорость подъема (м/с):</Label>
|
||||
|
|
@ -317,7 +370,9 @@
|
|||
|
||||
<div class="d-grid gap-1">
|
||||
<Button color="outline-secondary" size="sm">Показать график высоты</Button>
|
||||
<Button size="sm" color="primary" on:click={handleGetPrediction}>Выполнить прогнозирование</Button>
|
||||
<Button size="sm" color="primary" onclick={handleGetPrediction}>
|
||||
Выполнить прогнозирование
|
||||
</Button>
|
||||
</div>
|
||||
</CardBody>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@
|
|||
<button type="button" class="btn-close" onclick={closeModal} aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div bind:this={table.element}>
|
||||
<div bind:this={table.element} class="table-responsive">
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
|
|
|
|||
|
|
@ -80,12 +80,10 @@ export const getForecast = async (
|
|||
PredictionStore.set(parsePrediction(data.result.prediction) as Prediction);
|
||||
writeLocalStorage("rawPrediction", data.result as RawPrediction);
|
||||
writeLocalStorage("prediction", parsePrediction(data.result.prediction) as Prediction);
|
||||
|
||||
alert("Forecast request successful!");
|
||||
// Handle the response data as needed
|
||||
} catch (error) {
|
||||
console.error("Error sending forecast request:", error);
|
||||
alert("Error getting forecast: " + error);
|
||||
return Promise.reject(new Error(`${error}`));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -7,8 +7,18 @@ export const PROFILE_MAP = {
|
|||
Custom: "custom_profile",
|
||||
};
|
||||
|
||||
// Map of profile names to their string identifiers
|
||||
export const PROFILE_NAMES = {
|
||||
standard_profile: "Normal",
|
||||
float_profile: "Float",
|
||||
reverse_profile: "Reverse",
|
||||
custom_profile: "Custom",
|
||||
};
|
||||
|
||||
export type ProfileName = keyof typeof PROFILE_MAP;
|
||||
|
||||
export type ProfileIdentifier = keyof typeof PROFILE_NAMES;
|
||||
|
||||
export interface FlightParameters {
|
||||
ascent_rate: number;
|
||||
burst_altitude: number;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue