fix reactivity on saved point change

This commit is contained in:
ThePetrovich 2025-07-02 21:36:53 +08:00
parent a5bfed73a1
commit 5a1a20df6c
2 changed files with 81 additions and 112 deletions

View file

@ -9,7 +9,7 @@
Input,
InputGroup,
InputGroupText,
Icon
Icon,
} from "@sveltestrap/sveltestrap";
import { getForecast } from "$lib/prediction";
@ -58,12 +58,11 @@
let {
handleClickSelectOnMap = () => console.log("Select on map clicked"),
handleClickPointListModal = () => console.log("Open Point List Modal")
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();
@ -71,64 +70,31 @@
let startTime = $state(now.toISOString().split("T")[1].split(".")[0]);
// Coordinate inputs
let inputLat = $state($FlightParametersStore.launch_latitude.toFixed(6));
let inputLng = $state($FlightParametersStore.launch_longitude.toFixed(6));
// Reactive effects
$effect(() => {
$FlightParametersStore;
});
$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() {
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;
}
}
}
let inputLat = $derived($FlightParametersStore.start_point === "Custom"
? $FlightParametersStore.launch_latitude.toFixed(6)
: $SavedPointsStore.find(point => point.name === $FlightParametersStore.start_point)?.lat.toFixed(6) || "0.000000");
let inputLng = $derived($FlightParametersStore.start_point === "Custom"
? $FlightParametersStore.launch_longitude.toFixed(6)
: $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)
: $SavedPointsStore.find(point => point.name === $FlightParametersStore.start_point)?.alt.toFixed(2) || "0.00");
function setToCustomOnChange() {
if (startPoint !== "Custom") {
startPoint = "Custom";
if ($FlightParametersStore.start_point !== "Custom") {
$FlightParametersStore.start_point = "Custom";
}
}
function applyCoordinatesFromInput() {
const lat = parseFloat(inputLat);
const lng = parseFloat(inputLng);
const alt = parseFloat(inputAlt);
if (!isNaN(lat) && !isNaN(lng)) {
$FlightParametersStore.launch_latitude = lat;
$FlightParametersStore.launch_longitude = lng;
$FlightParametersStore.launch_altitude = alt || 0; // Default to 0 if alt is NaN
} else {
console.error("Invalid coordinate input");
// TODO: Show validation error to user
@ -137,23 +103,27 @@
async function handleGetPrediction() {
$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);
getForecast($FlightParametersStore).then(
(data) => {
getForecast($FlightParametersStore)
.then((data) => {
console.log("Forecast request successful:", data);
addToast({
header: "Forecast Request",
body: "Forecast request successful!",
color: "success"
color: "success",
});
// Handle the response data as needed
}).catch((error) => {
})
.catch((error) => {
console.error("Error getting forecast:", error);
addToast({
header: "Forecast Error",
body: `Error getting forecast: ${error.message}`,
color: "danger"
color: "danger",
});
});
}
@ -165,7 +135,7 @@
// Exported functions for parent components
export function updateLaunchPosition(lat: number, lng: number) {
$FlightParametersStore.launch_latitude = lat;
$FlightParametersStore.launch_longitude = lng;
$FlightParametersStore.launch_longitude = lng;
inputLat = lat.toFixed(6);
inputLng = lng.toFixed(6);
setToCustomOnChange();
@ -191,9 +161,17 @@
isCollapsed = !isCollapsed;
}
onMount(async () => {
const savedPoints = await getSavedPoints();
SavedPointsStore.set(savedPoints);
onMount(() => {
getSavedPoints()
.then((points) => SavedPointsStore.set(points))
.catch((error) => {
addToast({
header: "Error Loading Points",
body: `Failed to load saved points: ${error.message}`,
color: "danger",
});
return [];
});
});
</script>
@ -218,7 +196,7 @@
</Button>
</button>
</CardHeader>
{#if !isCollapsed}
<CardBody>
<div class="d-flex gap-2">
@ -260,10 +238,19 @@
<FormGroup spacing="mb-2">
<Label for="startPoint" class="form-label">Точка старта:</Label>
<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="Сохраненные точки">
{#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}
</option>
{/each}
@ -272,11 +259,7 @@
<option value="Custom">Custom</option>
</optgroup>
</Input>
<Button
color="secondary"
title="Edit Saved Locations"
onclick={handleClickPointListModal}
>
<Button color="secondary" title="Edit Saved Locations" onclick={handleClickPointListModal}>
<span>Редакт.</span>
<Icon name="journal-bookmark-fill" />
</Button>
@ -286,39 +269,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"
onchange={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"
onchange={setToCustomOnChange}
<Input
id="longitude"
type="text"
bind:value={inputLng}
placeholder="Longitude"
onchange={setToCustomOnChange}
/>
<Button
color="success"
size="sm"
onclick={applyCoordinatesFromInput}
title="Apply Coordinates"
>
<Button color="success" size="sm" onclick={applyCoordinatesFromInput} title="Apply Coordinates">
</Button>
</InputGroup>
</FormGroup>
<FormGroup spacing="mb-2">
<Button
color="outline-secondary"
size="sm"
class="w-100"
onclick={handleClickSelectOnMap}
>
<Button color="outline-secondary" size="sm" class="w-100" onclick={handleClickSelectOnMap}>
Указать на карте
</Button>
</FormGroup>
@ -331,7 +304,7 @@
id="startHeight"
class="form-control-sm"
onchange={setToCustomOnChange}
bind:value={$FlightParametersStore.launch_altitude}
bind:value={inputAlt}
/>
</FormGroup>
<FormGroup class="flex-fill w-50" spacing="mb-2">
@ -370,9 +343,7 @@
<div class="d-grid gap-1">
<Button color="outline-secondary" size="sm">Показать график высоты</Button>
<Button size="sm" color="primary" onclick={handleGetPrediction}>
Выполнить прогнозирование
</Button>
<Button size="sm" color="primary" onclick={handleGetPrediction}>Выполнить прогнозирование</Button>
</div>
</CardBody>
{/if}

View file

@ -18,10 +18,9 @@
import { getSavedPoints, savePoint, updatePoint, deletePoint } from '$lib/api/points';
// Props
let { isOpen = $bindable(false), onClose = () => {} } = $props();
let { isOpen = $bindable(false), onClose = () => {}, onChange = () => {} } = $props();
// Runes
let points = $state<SavedPoint[]>([]);
let selectedPoint = $state<SavedPoint | null>(null);
let newPoint = $state<SavedPoint>({ id: 0, name: '', lat: 0, lon: 0, alt: 0 });
let isEditing = $state(false);
@ -32,18 +31,17 @@
let modalTitle = $derived(isEditing ? 'Редактирование точки' : 'Сохраненные точки');
// Table handler
let table = $derived(new TableHandler(points, { rowsPerPage: 10 }));
let table = $derived(new TableHandler($SavedPointsStore, { rowsPerPage: 10 }));
// Sync with store
$effect(() => {
points = $SavedPointsStore || [];
onChange();
});
// On mount, fetch points
onMount(async () => {
const pts = await getSavedPoints();
points = pts;
SavedPointsStore.set(points);
$SavedPointsStore = pts;
SavedPointsStore.set($SavedPointsStore);
});
// Modal controls
@ -64,8 +62,8 @@
function handleDeletePoint(point: SavedPoint) {
deletePoint(point.id).then(() => {
points = points.filter(p => p.id !== point.id);
SavedPointsStore.set(points);
$SavedPointsStore = $SavedPointsStore.filter(p => p.id !== point.id);
SavedPointsStore.set($SavedPointsStore);
addToast({
header: 'Точка удалена',
body: `Точка "${point.name}" успешно удалена.`,
@ -80,8 +78,8 @@
function handleSavePoint() {
if (isEditing && selectedPoint) {
updatePoint(newPoint).then(updatedPoint => {
points = points.map(p => (p.id === updatedPoint.id ? updatedPoint : p));
SavedPointsStore.set(points);
$SavedPointsStore = $SavedPointsStore.map(p => (p.id === updatedPoint.id ? updatedPoint : p));
SavedPointsStore.set($SavedPointsStore);
resetForm();
addToast({
header: 'Точка обновлена',
@ -93,8 +91,8 @@
});
} else {
savePoint(newPoint).then(savedPoint => {
points = [...points, savedPoint];
SavedPointsStore.set(points);
$SavedPointsStore = [...$SavedPointsStore, savedPoint];
SavedPointsStore.set($SavedPointsStore);
resetForm();
addToast({
header: 'Точка сохранена',
@ -136,7 +134,7 @@
<table class="table table-sm">
<thead>
<tr>
<th>Имя</th>
<th>Название точки</th>
<th>Широта</th>
<th>Долгота</th>
<th>Высота</th>
@ -147,9 +145,9 @@
{#each table.rows as row}
<tr>
<td>{row.name}</td>
<td>{row.lat}</td>
<td>{row.lon}</td>
<td>{row.alt}</td>
<td>{row.lat} °</td>
<td>{row.lon} °</td>
<td>{row.alt} м</td>
<td class="fit">
<Button color="primary" size="sm" onclick={() => handleEditPoint(row)}>
<Icon name="pencil" />
@ -189,7 +187,7 @@
</Alert>
<form onsubmit={(e) => { e.preventDefault(); handleSavePoint(); }}>
<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 />
</div>
<div class="d-flex gap-2">