fix reactivity on saved point change
This commit is contained in:
parent
a5bfed73a1
commit
5a1a20df6c
2 changed files with 81 additions and 112 deletions
|
|
@ -9,7 +9,7 @@
|
||||||
Input,
|
Input,
|
||||||
InputGroup,
|
InputGroup,
|
||||||
InputGroupText,
|
InputGroupText,
|
||||||
Icon
|
Icon,
|
||||||
} from "@sveltestrap/sveltestrap";
|
} from "@sveltestrap/sveltestrap";
|
||||||
|
|
||||||
import { getForecast } from "$lib/prediction";
|
import { getForecast } from "$lib/prediction";
|
||||||
|
|
@ -58,12 +58,11 @@
|
||||||
|
|
||||||
let {
|
let {
|
||||||
handleClickSelectOnMap = () => console.log("Select on map clicked"),
|
handleClickSelectOnMap = () => console.log("Select on map clicked"),
|
||||||
handleClickPointListModal = () => console.log("Open Point List Modal")
|
handleClickPointListModal = () => console.log("Open Point List Modal"),
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
|
||||||
// State
|
// State
|
||||||
let isCollapsed = $state(false);
|
let isCollapsed = $state(false);
|
||||||
let startPoint = $state("Custom");
|
|
||||||
|
|
||||||
// Initialize date/time
|
// Initialize date/time
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
|
@ -71,64 +70,31 @@
|
||||||
let startTime = $state(now.toISOString().split("T")[1].split(".")[0]);
|
let startTime = $state(now.toISOString().split("T")[1].split(".")[0]);
|
||||||
|
|
||||||
// Coordinate inputs
|
// Coordinate inputs
|
||||||
let inputLat = $state($FlightParametersStore.launch_latitude.toFixed(6));
|
let inputLat = $derived($FlightParametersStore.start_point === "Custom"
|
||||||
let inputLng = $state($FlightParametersStore.launch_longitude.toFixed(6));
|
? $FlightParametersStore.launch_latitude.toFixed(6)
|
||||||
|
: $SavedPointsStore.find(point => point.name === $FlightParametersStore.start_point)?.lat.toFixed(6) || "0.000000");
|
||||||
// Reactive effects
|
let inputLng = $derived($FlightParametersStore.start_point === "Custom"
|
||||||
$effect(() => {
|
? $FlightParametersStore.launch_longitude.toFixed(6)
|
||||||
$FlightParametersStore;
|
: $SavedPointsStore.find(point => point.name === $FlightParametersStore.start_point)?.lon.toFixed(6) || "0.000000");
|
||||||
});
|
let inputAlt = $derived($FlightParametersStore.start_point === "Custom"
|
||||||
|
? $FlightParametersStore.launch_altitude.toFixed(2)
|
||||||
$effect(() => {
|
: $SavedPointsStore.find(point => point.name === $FlightParametersStore.start_point)?.alt.toFixed(2) || "0.00");
|
||||||
// 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() {
|
|
||||||
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);
|
|
||||||
inputLng = lng.toFixed(6);
|
|
||||||
$FlightParametersStore.launch_latitude = lat;
|
|
||||||
$FlightParametersStore.launch_longitude = lng;
|
|
||||||
$FlightParametersStore.launch_altitude = alt;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setToCustomOnChange() {
|
function setToCustomOnChange() {
|
||||||
if (startPoint !== "Custom") {
|
if ($FlightParametersStore.start_point !== "Custom") {
|
||||||
startPoint = "Custom";
|
$FlightParametersStore.start_point = "Custom";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyCoordinatesFromInput() {
|
function applyCoordinatesFromInput() {
|
||||||
const lat = parseFloat(inputLat);
|
const lat = parseFloat(inputLat);
|
||||||
const lng = parseFloat(inputLng);
|
const lng = parseFloat(inputLng);
|
||||||
|
const alt = parseFloat(inputAlt);
|
||||||
|
|
||||||
if (!isNaN(lat) && !isNaN(lng)) {
|
if (!isNaN(lat) && !isNaN(lng)) {
|
||||||
$FlightParametersStore.launch_latitude = lat;
|
$FlightParametersStore.launch_latitude = lat;
|
||||||
$FlightParametersStore.launch_longitude = lng;
|
$FlightParametersStore.launch_longitude = lng;
|
||||||
|
$FlightParametersStore.launch_altitude = alt || 0; // Default to 0 if alt is NaN
|
||||||
} else {
|
} else {
|
||||||
console.error("Invalid coordinate input");
|
console.error("Invalid coordinate input");
|
||||||
// TODO: Show validation error to user
|
// TODO: Show validation error to user
|
||||||
|
|
@ -137,23 +103,27 @@
|
||||||
|
|
||||||
async function handleGetPrediction() {
|
async function handleGetPrediction() {
|
||||||
$FlightParametersStore.launch_datetime = `${startDate}T${startTime}Z`;
|
$FlightParametersStore.launch_datetime = `${startDate}T${startTime}Z`;
|
||||||
|
$FlightParametersStore.launch_latitude = parseFloat(inputLat);
|
||||||
|
$FlightParametersStore.launch_longitude = parseFloat(inputLng);
|
||||||
|
$FlightParametersStore.launch_altitude = parseFloat(inputAlt);
|
||||||
writeLocalStorage<FlightParameters>("flightParameters", $FlightParametersStore);
|
writeLocalStorage<FlightParameters>("flightParameters", $FlightParametersStore);
|
||||||
|
|
||||||
getForecast($FlightParametersStore).then(
|
getForecast($FlightParametersStore)
|
||||||
(data) => {
|
.then((data) => {
|
||||||
console.log("Forecast request successful:", data);
|
console.log("Forecast request successful:", data);
|
||||||
addToast({
|
addToast({
|
||||||
header: "Forecast Request",
|
header: "Forecast Request",
|
||||||
body: "Forecast request successful!",
|
body: "Forecast request successful!",
|
||||||
color: "success"
|
color: "success",
|
||||||
});
|
});
|
||||||
// Handle the response data as needed
|
// Handle the response data as needed
|
||||||
}).catch((error) => {
|
})
|
||||||
|
.catch((error) => {
|
||||||
console.error("Error getting forecast:", error);
|
console.error("Error getting forecast:", error);
|
||||||
addToast({
|
addToast({
|
||||||
header: "Forecast Error",
|
header: "Forecast Error",
|
||||||
body: `Error getting forecast: ${error.message}`,
|
body: `Error getting forecast: ${error.message}`,
|
||||||
color: "danger"
|
color: "danger",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -165,7 +135,7 @@
|
||||||
// Exported functions for parent components
|
// Exported functions for parent components
|
||||||
export function updateLaunchPosition(lat: number, lng: number) {
|
export function updateLaunchPosition(lat: number, lng: number) {
|
||||||
$FlightParametersStore.launch_latitude = lat;
|
$FlightParametersStore.launch_latitude = lat;
|
||||||
$FlightParametersStore.launch_longitude = lng;
|
$FlightParametersStore.launch_longitude = lng;
|
||||||
inputLat = lat.toFixed(6);
|
inputLat = lat.toFixed(6);
|
||||||
inputLng = lng.toFixed(6);
|
inputLng = lng.toFixed(6);
|
||||||
setToCustomOnChange();
|
setToCustomOnChange();
|
||||||
|
|
@ -191,9 +161,17 @@
|
||||||
isCollapsed = !isCollapsed;
|
isCollapsed = !isCollapsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(() => {
|
||||||
const savedPoints = await getSavedPoints();
|
getSavedPoints()
|
||||||
SavedPointsStore.set(savedPoints);
|
.then((points) => SavedPointsStore.set(points))
|
||||||
|
.catch((error) => {
|
||||||
|
addToast({
|
||||||
|
header: "Error Loading Points",
|
||||||
|
body: `Failed to load saved points: ${error.message}`,
|
||||||
|
color: "danger",
|
||||||
|
});
|
||||||
|
return [];
|
||||||
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -218,7 +196,7 @@
|
||||||
</Button>
|
</Button>
|
||||||
</button>
|
</button>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
||||||
{#if !isCollapsed}
|
{#if !isCollapsed}
|
||||||
<CardBody>
|
<CardBody>
|
||||||
<div class="d-flex gap-2">
|
<div class="d-flex gap-2">
|
||||||
|
|
@ -260,10 +238,19 @@
|
||||||
<FormGroup spacing="mb-2">
|
<FormGroup spacing="mb-2">
|
||||||
<Label for="startPoint" class="form-label">Точка старта:</Label>
|
<Label for="startPoint" class="form-label">Точка старта:</Label>
|
||||||
<InputGroup size="sm">
|
<InputGroup size="sm">
|
||||||
<Input type="select" id="startPoint" bind:value={startPoint} onchange={setCoordinatesFromSavedPoint}>
|
<Input
|
||||||
|
type="select"
|
||||||
|
id="startPoint"
|
||||||
|
bind:value={$FlightParametersStore.start_point}
|
||||||
|
>
|
||||||
<optgroup label="Сохраненные точки">
|
<optgroup label="Сохраненные точки">
|
||||||
{#each $SavedPointsStore as point}
|
{#each $SavedPointsStore as point}
|
||||||
<option value={point.name} data-lat={point.lat} data-lng={point.lon} data-alt={point.alt}>
|
<option
|
||||||
|
value={point.name}
|
||||||
|
data-lat={point.lat}
|
||||||
|
data-lng={point.lon}
|
||||||
|
data-alt={point.alt}
|
||||||
|
>
|
||||||
{point.name}
|
{point.name}
|
||||||
</option>
|
</option>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
@ -272,11 +259,7 @@
|
||||||
<option value="Custom">Custom</option>
|
<option value="Custom">Custom</option>
|
||||||
</optgroup>
|
</optgroup>
|
||||||
</Input>
|
</Input>
|
||||||
<Button
|
<Button color="secondary" title="Edit Saved Locations" onclick={handleClickPointListModal}>
|
||||||
color="secondary"
|
|
||||||
title="Edit Saved Locations"
|
|
||||||
onclick={handleClickPointListModal}
|
|
||||||
>
|
|
||||||
<span>Редакт.</span>
|
<span>Редакт.</span>
|
||||||
<Icon name="journal-bookmark-fill" />
|
<Icon name="journal-bookmark-fill" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -286,39 +269,29 @@
|
||||||
<FormGroup spacing="mb-2">
|
<FormGroup spacing="mb-2">
|
||||||
<Label for="latitude" class="form-label">Широта/Долгота:</Label>
|
<Label for="latitude" class="form-label">Широта/Долгота:</Label>
|
||||||
<InputGroup size="sm">
|
<InputGroup size="sm">
|
||||||
<Input
|
<Input
|
||||||
id="latitude"
|
id="latitude"
|
||||||
type="text"
|
type="text"
|
||||||
bind:value={inputLat}
|
bind:value={inputLat}
|
||||||
placeholder="Latitude"
|
placeholder="Latitude"
|
||||||
onchange={setToCustomOnChange}
|
onchange={setToCustomOnChange}
|
||||||
/>
|
/>
|
||||||
<InputGroupText>/</InputGroupText>
|
<InputGroupText>/</InputGroupText>
|
||||||
<Input
|
<Input
|
||||||
id="longitude"
|
id="longitude"
|
||||||
type="text"
|
type="text"
|
||||||
bind:value={inputLng}
|
bind:value={inputLng}
|
||||||
placeholder="Longitude"
|
placeholder="Longitude"
|
||||||
onchange={setToCustomOnChange}
|
onchange={setToCustomOnChange}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button color="success" size="sm" onclick={applyCoordinatesFromInput} title="Apply Coordinates">
|
||||||
color="success"
|
|
||||||
size="sm"
|
|
||||||
onclick={applyCoordinatesFromInput}
|
|
||||||
title="Apply Coordinates"
|
|
||||||
>
|
|
||||||
✓
|
✓
|
||||||
</Button>
|
</Button>
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
<FormGroup spacing="mb-2">
|
<FormGroup spacing="mb-2">
|
||||||
<Button
|
<Button color="outline-secondary" size="sm" class="w-100" onclick={handleClickSelectOnMap}>
|
||||||
color="outline-secondary"
|
|
||||||
size="sm"
|
|
||||||
class="w-100"
|
|
||||||
onclick={handleClickSelectOnMap}
|
|
||||||
>
|
|
||||||
Указать на карте
|
Указать на карте
|
||||||
</Button>
|
</Button>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
@ -331,7 +304,7 @@
|
||||||
id="startHeight"
|
id="startHeight"
|
||||||
class="form-control-sm"
|
class="form-control-sm"
|
||||||
onchange={setToCustomOnChange}
|
onchange={setToCustomOnChange}
|
||||||
bind:value={$FlightParametersStore.launch_altitude}
|
bind:value={inputAlt}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
<FormGroup class="flex-fill w-50" spacing="mb-2">
|
<FormGroup class="flex-fill w-50" spacing="mb-2">
|
||||||
|
|
@ -370,9 +343,7 @@
|
||||||
|
|
||||||
<div class="d-grid gap-1">
|
<div class="d-grid gap-1">
|
||||||
<Button color="outline-secondary" size="sm">Показать график высоты</Button>
|
<Button color="outline-secondary" size="sm">Показать график высоты</Button>
|
||||||
<Button size="sm" color="primary" onclick={handleGetPrediction}>
|
<Button size="sm" color="primary" onclick={handleGetPrediction}>Выполнить прогнозирование</Button>
|
||||||
Выполнить прогнозирование
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
||||||
|
|
@ -18,10 +18,9 @@
|
||||||
import { getSavedPoints, savePoint, updatePoint, deletePoint } from '$lib/api/points';
|
import { getSavedPoints, savePoint, updatePoint, deletePoint } from '$lib/api/points';
|
||||||
|
|
||||||
// Props
|
// Props
|
||||||
let { isOpen = $bindable(false), onClose = () => {} } = $props();
|
let { isOpen = $bindable(false), onClose = () => {}, onChange = () => {} } = $props();
|
||||||
|
|
||||||
// Runes
|
// Runes
|
||||||
let points = $state<SavedPoint[]>([]);
|
|
||||||
let selectedPoint = $state<SavedPoint | null>(null);
|
let selectedPoint = $state<SavedPoint | null>(null);
|
||||||
let newPoint = $state<SavedPoint>({ id: 0, name: '', lat: 0, lon: 0, alt: 0 });
|
let newPoint = $state<SavedPoint>({ id: 0, name: '', lat: 0, lon: 0, alt: 0 });
|
||||||
let isEditing = $state(false);
|
let isEditing = $state(false);
|
||||||
|
|
@ -32,18 +31,17 @@
|
||||||
let modalTitle = $derived(isEditing ? 'Редактирование точки' : 'Сохраненные точки');
|
let modalTitle = $derived(isEditing ? 'Редактирование точки' : 'Сохраненные точки');
|
||||||
|
|
||||||
// Table handler
|
// Table handler
|
||||||
let table = $derived(new TableHandler(points, { rowsPerPage: 10 }));
|
let table = $derived(new TableHandler($SavedPointsStore, { rowsPerPage: 10 }));
|
||||||
|
|
||||||
// Sync with store
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
points = $SavedPointsStore || [];
|
onChange();
|
||||||
});
|
});
|
||||||
|
|
||||||
// On mount, fetch points
|
// On mount, fetch points
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
const pts = await getSavedPoints();
|
const pts = await getSavedPoints();
|
||||||
points = pts;
|
$SavedPointsStore = pts;
|
||||||
SavedPointsStore.set(points);
|
SavedPointsStore.set($SavedPointsStore);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Modal controls
|
// Modal controls
|
||||||
|
|
@ -64,8 +62,8 @@
|
||||||
|
|
||||||
function handleDeletePoint(point: SavedPoint) {
|
function handleDeletePoint(point: SavedPoint) {
|
||||||
deletePoint(point.id).then(() => {
|
deletePoint(point.id).then(() => {
|
||||||
points = points.filter(p => p.id !== point.id);
|
$SavedPointsStore = $SavedPointsStore.filter(p => p.id !== point.id);
|
||||||
SavedPointsStore.set(points);
|
SavedPointsStore.set($SavedPointsStore);
|
||||||
addToast({
|
addToast({
|
||||||
header: 'Точка удалена',
|
header: 'Точка удалена',
|
||||||
body: `Точка "${point.name}" успешно удалена.`,
|
body: `Точка "${point.name}" успешно удалена.`,
|
||||||
|
|
@ -80,8 +78,8 @@
|
||||||
function handleSavePoint() {
|
function handleSavePoint() {
|
||||||
if (isEditing && selectedPoint) {
|
if (isEditing && selectedPoint) {
|
||||||
updatePoint(newPoint).then(updatedPoint => {
|
updatePoint(newPoint).then(updatedPoint => {
|
||||||
points = points.map(p => (p.id === updatedPoint.id ? updatedPoint : p));
|
$SavedPointsStore = $SavedPointsStore.map(p => (p.id === updatedPoint.id ? updatedPoint : p));
|
||||||
SavedPointsStore.set(points);
|
SavedPointsStore.set($SavedPointsStore);
|
||||||
resetForm();
|
resetForm();
|
||||||
addToast({
|
addToast({
|
||||||
header: 'Точка обновлена',
|
header: 'Точка обновлена',
|
||||||
|
|
@ -93,8 +91,8 @@
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
savePoint(newPoint).then(savedPoint => {
|
savePoint(newPoint).then(savedPoint => {
|
||||||
points = [...points, savedPoint];
|
$SavedPointsStore = [...$SavedPointsStore, savedPoint];
|
||||||
SavedPointsStore.set(points);
|
SavedPointsStore.set($SavedPointsStore);
|
||||||
resetForm();
|
resetForm();
|
||||||
addToast({
|
addToast({
|
||||||
header: 'Точка сохранена',
|
header: 'Точка сохранена',
|
||||||
|
|
@ -136,7 +134,7 @@
|
||||||
<table class="table table-sm">
|
<table class="table table-sm">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Имя</th>
|
<th>Название точки</th>
|
||||||
<th>Широта</th>
|
<th>Широта</th>
|
||||||
<th>Долгота</th>
|
<th>Долгота</th>
|
||||||
<th>Высота</th>
|
<th>Высота</th>
|
||||||
|
|
@ -147,9 +145,9 @@
|
||||||
{#each table.rows as row}
|
{#each table.rows as row}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{row.name}</td>
|
<td>{row.name}</td>
|
||||||
<td>{row.lat}</td>
|
<td>{row.lat} °</td>
|
||||||
<td>{row.lon}</td>
|
<td>{row.lon} °</td>
|
||||||
<td>{row.alt}</td>
|
<td>{row.alt} м</td>
|
||||||
<td class="fit">
|
<td class="fit">
|
||||||
<Button color="primary" size="sm" onclick={() => handleEditPoint(row)}>
|
<Button color="primary" size="sm" onclick={() => handleEditPoint(row)}>
|
||||||
<Icon name="pencil" />
|
<Icon name="pencil" />
|
||||||
|
|
@ -189,7 +187,7 @@
|
||||||
</Alert>
|
</Alert>
|
||||||
<form onsubmit={(e) => { e.preventDefault(); handleSavePoint(); }}>
|
<form onsubmit={(e) => { e.preventDefault(); handleSavePoint(); }}>
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
<Label for="name" class="small">Имя:</Label>
|
<Label for="name" class="small">Название точки:</Label>
|
||||||
<Input class="form-control-sm" type="text" id="name" bind:value={newPoint.name} required />
|
<Input class="form-control-sm" type="text" id="name" bind:value={newPoint.name} required />
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex gap-2">
|
<div class="d-flex gap-2">
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue