From 19f969c18c6bcdaaf1e331f52d4db8f77d6e1747 Mon Sep 17 00:00:00 2001 From: ThePetrovich Date: Sat, 5 Jul 2025 23:04:29 +0800 Subject: [PATCH] Scenario system & point editor rework --- src/lib/api/scenarios.ts | 19 + src/lib/api/templates.ts | 19 - src/lib/components/ControlPanel.svelte | 481 +++++++++++---------- src/lib/components/PointEditor.svelte | 285 ++++++++++-- src/lib/components/PointListModal.svelte | 347 --------------- src/lib/components/ScenarioEditor.svelte | 262 +++++++++++ src/lib/components/ScenarioPanel.svelte | 218 ++++++++-- src/lib/components/SelectSearchable.svelte | 6 +- src/lib/prediction.ts | 2 + src/lib/stores.ts | 11 +- src/lib/types.ts | 34 +- src/routes/predict/+page.svelte | 4 +- src/routes/user/templates/+page.svelte | 16 +- 13 files changed, 1010 insertions(+), 694 deletions(-) create mode 100644 src/lib/api/scenarios.ts delete mode 100644 src/lib/api/templates.ts delete mode 100644 src/lib/components/PointListModal.svelte create mode 100644 src/lib/components/ScenarioEditor.svelte diff --git a/src/lib/api/scenarios.ts b/src/lib/api/scenarios.ts new file mode 100644 index 0000000..d1ae89a --- /dev/null +++ b/src/lib/api/scenarios.ts @@ -0,0 +1,19 @@ +/* API functions for SavedScenario */ +import type { SavedScenario } from "$lib/types"; +import { getAPI, postAPI, putAPI, deleteAPI } from "./base"; + +export function getSavedScenarios(): Promise { + return getAPI("/saved-templates/"); +} + +export function saveScenario(template: SavedScenario): Promise { + return postAPI("/saved-templates/", template); +} + +export function updateScenario(template: SavedScenario): Promise { + return putAPI(`/saved-templates/${template.id}/`, template); +} + +export function deleteScenario(id: number): Promise { + return deleteAPI(`/saved-templates/${id}/`); +} \ No newline at end of file diff --git a/src/lib/api/templates.ts b/src/lib/api/templates.ts deleted file mode 100644 index 01d51b6..0000000 --- a/src/lib/api/templates.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* API functions for SavedScenarioTemplate */ -import type { SavedScenarioTemplate } from "$lib/types"; -import { getAPI, postAPI, putAPI, deleteAPI } from "./base"; - -export function getSavedScenarioTemplates(): Promise { - return getAPI("/saved-templates/"); -} - -export function saveScenarioTemplate(template: SavedScenarioTemplate): Promise { - return postAPI("/saved-templates/", template); -} - -export function updateScenarioTemplate(template: SavedScenarioTemplate): Promise { - return putAPI(`/saved-templates/${template.id}/`, template); -} - -export function deleteScenarioTemplate(id: number): Promise { - return deleteAPI(`/saved-templates/${id}/`); -} \ No newline at end of file diff --git a/src/lib/components/ControlPanel.svelte b/src/lib/components/ControlPanel.svelte index 734bdc2..9309f29 100644 --- a/src/lib/components/ControlPanel.svelte +++ b/src/lib/components/ControlPanel.svelte @@ -1,199 +1,216 @@ @@ -240,11 +242,11 @@ - +
({ + id="cp-start-point" + bind:selected={selectedPointId} + options={$SavedPointsStore.map((point) => ({ value: point.id, - label: point.name, + label: + point.name + + `${point.id == $FlightParametersStore.start_point && isPointDirty ? " (изменено)" : ""}`, }))} - placeholder="Выберите точку старта" - searchPlaceholder="Поиск точки..." + placeholder="Новая точка..." + searchPlaceholder="Поиск по точкам..." + on:change={() => { + if (!isPointDirty) { + $FlightParametersStore.start_point = selectedPointId; + } + }} />
- +
@@ -331,7 +326,7 @@ - + { + isPointDirty = true; + }} /> / { + isPointDirty = true; + }} /> -
- + { + isPointDirty = true; + }} bind:value={inputAlt} /> - + @@ -398,31 +399,59 @@ {#if $FlightParametersStore.profile !== "custom_profile"}
- + - +
+ {:else} + + + + ({ + // stub, replace with actual profiles + value: PROFILE_MAP[profileName as ProfileName], + label: profileName, + }))} + placeholder="Выберите профиль..." + searchPlaceholder="Поиск профилей..." + on:change={() => { + $FlightParametersStore.profile = $FlightParametersStore.profile; + }} + /> + + + {/if}
- - +
{/if} - \ No newline at end of file + diff --git a/src/lib/components/PointEditor.svelte b/src/lib/components/PointEditor.svelte index a5d17a4..f957491 100644 --- a/src/lib/components/PointEditor.svelte +++ b/src/lib/components/PointEditor.svelte @@ -11,6 +11,7 @@ Pagination, PaginationItem, PaginationLink, + InputGroup, } from "@sveltestrap/sveltestrap"; import { onMount } from "svelte"; import { addToast } from "$lib/components/Toast.svelte"; @@ -20,39 +21,116 @@ import { getSavedPoints, savePoint, updatePoint, deletePoint } from "$lib/api/points"; // Props - let { isOpen = $bindable(false), onClose = () => {}, onChange = () => {}, point} = $props(); + let { + isOpen = $bindable(false), + onClose = () => {}, + onSave = (p: SavedPoint) => {}, + onSelectPoint = (p: SavedPoint) => {}, + showTable = false, + point = null, + editor = false, + closeOnSave = false, + closeOnDelete = false, + } = $props(); // Runes - let selectedPoint = $derived(point); + let selectedPoint = $derived(point); + let newPoint = $state({ id: 0, name: "", lat: 0, lon: 0, alt: 0 }); + + let isEditing = $state(editor); let isAlertVisible = $state(false); let isConfirmationVisible = $state(false); let alertText = $state(""); + let closeOnSave_ = $state(closeOnSave); + + // Table handler + let table = $derived(new TableHandler($SavedPointsStore, { rowsPerPage: 10 })); + let search = $derived(table.createSearch(["name"])); $effect(() => { - onChange(); + if (showTable) { + getSavedPoints().then((pts) => { + $SavedPointsStore = pts; + SavedPointsStore.set($SavedPointsStore); + }); + } + if (editor && point) { + selectedPoint = point; + newPoint = { ...point }; + isEditing = true; + } + }); + + // On mount, fetch points + onMount(async () => { + if (showTable) { + const pts = await getSavedPoints(); + $SavedPointsStore = pts; + SavedPointsStore.set($SavedPointsStore); + } }); // Modal controls - export function openModal() { + export function openModal(table_: boolean = false) { + showTable = table_; isOpen = true; } + export function openModalAndCreate( + point: SavedPoint | null = null, + coordinates: SavedPoint = { id: 0, name: "", lat: 0, lon: 0, alt: 0 }, + close: boolean = false, + table_: boolean = false, + onSaveCallback: (point: SavedPoint) => void = () => {}, + ) { + if (point) { + selectedPoint = point; + newPoint = { ...point }; + isEditing = true; + } else { + selectedPoint = null; + newPoint = coordinates || { id: 0, name: "", lat: 0, lon: 0, alt: 0 }; + isEditing = false; + } + showTable = table_; + isOpen = true; + closeOnSave_ = close; + onSave = onSaveCallback; + } + function closeModal() { isOpen = false; + if (closeOnSave_ != closeOnSave) { + closeOnSave = closeOnSave_; + } onClose(); } - function handleDeletePoint(point: SavedPoint) { + function handleEditPoint(point: SavedPoint) { + selectedPoint = point; + newPoint = { ...point }; + isEditing = true; + } + + function confirmDeletePoint(point: SavedPoint) { + selectedPoint = point; + isConfirmationVisible = true; + } + + function handleDeletePoint(point: SavedPoint | null) { + if (!point) return; deletePoint(point.id) .then(() => { $SavedPointsStore = $SavedPointsStore.filter((p) => p.id !== point.id); SavedPointsStore.set($SavedPointsStore); - resetForm(); addToast({ header: "Точка удалена", body: `Точка "${point.name}" успешно удалена.`, color: "success", }); + if (closeOnDelete) { + closeModal(); + } }) .catch((error) => { showAlert(`Ошибка при удалении точки: ${error.message}`); @@ -60,21 +138,47 @@ }); } - function handleSavePoint() { - updatePoint(selectedPoint) - .then((updatedPoint) => { - $SavedPointsStore = $SavedPointsStore.map((p) => (p.id === updatedPoint.id ? updatedPoint : p)); - SavedPointsStore.set($SavedPointsStore); - resetForm(); - addToast({ - header: "Точка обновлена", - body: `Точка "${updatedPoint.name}" успешно обновлена.`, - color: "success", + export function handleSavePoint() { + if (isEditing && selectedPoint) { + updatePoint(newPoint) + .then((updatedPoint) => { + $SavedPointsStore = $SavedPointsStore.map((p) => (p.id === updatedPoint.id ? updatedPoint : p)); + SavedPointsStore.set($SavedPointsStore); + resetForm(); + addToast({ + header: "Точка обновлена", + body: `Точка "${updatedPoint.name}" успешно обновлена.`, + color: "success", + }); + if (closeOnSave_) { + closeModal(); + } + onSave(updatedPoint); + }) + .catch((error) => { + showAlert(`Ошибка при обновлении точки: ${error.message}`); }); - }) - .catch((error) => { - showAlert(`Ошибка при обновлении точки: ${error.message}`); - }); + } else { + savePoint(newPoint) + .then((savedPoint) => { + $SavedPointsStore = [...$SavedPointsStore, savedPoint]; + SavedPointsStore.set($SavedPointsStore); + resetForm(); + addToast({ + header: "Точка сохранена", + body: `Точка "${savedPoint.name}" успешно сохранена.`, + color: "success", + }); + if (closeOnSave_) { + closeModal(); + } + onSave(savedPoint); + }) + .catch((error) => { + showAlert(`Ошибка при сохранении точки: ${error.message}`); + console.error("Ошибка при сохранении точки:", error); + }); + } } export function showAlert(message: string) { @@ -88,19 +192,111 @@ } export function resetForm() { + selectedPoint = null; + newPoint = { id: 0, name: "", lat: 0, lon: 0, alt: 0 }; + isEditing = false; hideAlert(); - closeModal(); } - + +
- -
- -
- - - - + { + scenarioUnsaved = true; + }}> + {#each Object.entries(PREDICTION_MODE_MAP) as [key, value]} + + {/each} @@ -141,7 +288,7 @@