Scenario system & point editor rework

This commit is contained in:
ThePetrovich 2025-07-05 23:04:29 +08:00
parent 7d01fce094
commit 19f969c18c
13 changed files with 1010 additions and 694 deletions

View file

@ -12,11 +12,131 @@
Icon,
} from "@sveltestrap/sveltestrap";
import { PROFILE_MAP } from "$lib/types";
import { FlightParametersStore, writeLocalStorage, ScenarioStore, SavedScenarioTemplatesStore } from "$lib/stores";
import { PREDICTION_MODE_MAP, PPREDICTION_MODE_NAMES } from "$lib/types";
import type { SavedScenario } from "$lib/types";
import { getSavedScenarios, updateScenario, saveScenario } from "$lib/api/scenarios";
import { FlightParametersStore, writeLocalStorage, ScenarioStore, SavedScenarioStore } from "$lib/stores";
import SelectSearchable from "$lib/components/SelectSearchable.svelte";
import { onMount } from "svelte";
import { addToast } from "./Toast.svelte";
import ScenarioEditor from "./ScenarioEditor.svelte";
let isCollapsed = false;
let isCollapsed = $state(false);
let scenarioUnsaved = $derived(checkScenarioUnsaved());
let selectedScenarioId = $state(-1);
let scenarioEditorRef: ScenarioEditor | null = null;
onMount(() => {
getSavedScenarios()
.then((scenarios) => SavedScenarioStore.set(scenarios))
.catch((error) => {
addToast({
header: "Error Loading Points",
body: `Failed to load saved points: ${error.message}`,
color: "danger",
});
return [];
});
selectedScenarioId = $ScenarioStore.id;
});
function checkScenarioUnsaved() {
const savedScenario = $SavedScenarioStore.find((scenario) => scenario.id === $ScenarioStore.id);
if (!savedScenario) {
return false; // No saved scenario found
}
const flightParameters = $FlightParametersStore;
const savedData = savedScenario.template_data;
const savedFlightParameters = savedData.flight_parameters;
// Compare flight parameters excluding launch_datetime
return JSON.stringify(flightParameters) !== JSON.stringify(savedFlightParameters);
}
function handleSaveCurrentScenario() {
console.log("handleSaveCurrentScenario called");
const scenario = $SavedScenarioStore.find((s) => s.id === selectedScenarioId);
if (selectedScenarioId !== -1 && scenario) {
$ScenarioStore.id = selectedScenarioId;
updateScenario($ScenarioStore)
.then((updatedScenario) => {
$SavedScenarioStore = $SavedScenarioStore.map((s) =>
s.id === updatedScenario.id ? updatedScenario : s,
);
SavedScenarioStore.set($SavedScenarioStore);
$ScenarioStore = updatedScenario;
selectedScenarioId = updatedScenario.id;
addToast({
header: "Сценарий обновлен",
body: `Сценарий "${updatedScenario.name}" успешно обновлен.`,
color: "success",
});
scenarioUnsaved = false;
})
.catch((error) => {
addToast({
header: "Ошибка обновления сценария",
body: `Ошибка при обновлении сценария: ${error.message}`,
color: "danger",
});
console.error("Ошибка при обновлении сценария:", error);
});
} else {
scenarioEditorRef?.openModalAndCreate(
null,
{
id: 0,
name: "",
template_data: {
flight_parameters: $FlightParametersStore,
description: "test",
model: "test",
dataset: "test",
prediction_mode: $ScenarioStore.template_data.prediction_mode
},
},
true,
handleModalSave,
);
}
}
function handleApplySelectedScenario(showToast = true) {
const selectedScenario = $SavedScenarioStore.find((scenario) => scenario.id === selectedScenarioId);
if (selectedScenario) {
$ScenarioStore = selectedScenario;
$FlightParametersStore = selectedScenario.template_data.flight_parameters;
scenarioUnsaved = false;
writeLocalStorage("scenario", $ScenarioStore);
if (showToast) {
addToast({
header: "Сценарий применен",
body: `Сценарий "${selectedScenario.name}" успешно применен.`,
color: "success",
});
}
} else {
if (showToast)
addToast({
header: "Сценарий не найден",
body: "Выбранный сценарий не существует.",
color: "warning",
});
console.warn("Selected scenario not found:", selectedScenarioId);
}
}
function handleModalSave(savedScenario: SavedScenario) {
if (savedScenario) {
$ScenarioStore = savedScenario;
selectedScenarioId = savedScenario.id;
scenarioUnsaved = false;
}
}
export const collapsePanel = () => {
isCollapsed = true;
@ -41,10 +161,10 @@
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={() => (isCollapsed = !isCollapsed)}
>
<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={() => (isCollapsed = !isCollapsed)}>
{#if isCollapsed}
<Icon name="caret-left-fill" class="text-white" />
{:else}
@ -58,50 +178,77 @@
<FormGroup spacing="mb-2">
<Label for="scenarioName" class="form-label">енарий:</Label>
<InputGroup size="sm">
<SelectSearchable
id="startPoint"
options={$SavedScenarioTemplatesStore.map(scenario => ({
value: scenario.id,
label: scenario.name,
}))}
placeholder="Выберите сценарий..."
searchPlaceholder="Поиск сценариев..."
/>
<Button color="success" title="Применить сценарий">
<div class="position-relative flex-grow-1">
<SelectSearchable
style="min-height: calc(1.5em + .5rem + 2px); padding: .25rem .5rem; font-size: .875rem;"
id="cp-start-point"
options={$SavedScenarioStore.map((scenario) => ({
value: scenario.id,
label:
scenario.name +
`${scenario.id == $ScenarioStore.id && scenarioUnsaved ? " (изменено)" : ""}`,
}))}
bind:selected={selectedScenarioId}
placeholder="Новый сценарий..."
searchPlaceholder="Поиск сценариев..."
on:change={() => {
if (!scenarioUnsaved) {
handleApplySelectedScenario(false);
}
}}
/>
<Button
size="sm"
color="white"
class="position-absolute top-50 end-0 translate-middle-y rounded-circle d-flex align-items-center justify-content-center"
style="width: 16px; height: 16px; border: none; background: var(--bs-secondary); color: var(--bs-white); z-index: 10; margin-right: 2rem;"
on:click={() => {
selectedScenarioId = -1;
}}
disabled={selectedScenarioId === -1}
>
<Icon name="x" style="font-size: 16px;" />
</Button>
</div>
<Button color="success" title="Применить сценарий" onclick={() => {handleApplySelectedScenario(true)}}>
<span></span>
</Button>
</InputGroup>
</FormGroup>
<div class="d-flex gap-2 mb-2">
<Button class="flex-fill" color="secondary" size="sm">
Сохранить
<Icon name="save" />
<Button color="secondary flex-fill" size="sm" title="Открыть список сценариев">
Все сценарии
<Icon name="journal-bookmark-fill" />
</Button>
<Button class="flex-fill" color="secondary" size="sm">
Загрузить
<Icon name="folder2-open" />
<Button
color="primary flex-fill"
size="sm"
title="Сохранить текущие условия как сценарий"
onclick={handleSaveCurrentScenario}
disabled={!scenarioUnsaved && selectedScenarioId !== -1}
>
{selectedScenarioId !== -1 ? "Обновить сценарий" : "Сохранить сценарий"}
<Icon name="floppy2-fill" />
</Button>
</div>
<Button
color="primary"
size="sm"
class="mb-0 w-100"
>
Редактировать сохраненные сценарии
<Icon name="journal-bookmark-fill" />
</Button>
<hr />
<FormGroup spacing="mb-2">
<Label for="scenarioMode" class="form-label">Режим сценария:</Label>
<InputGroup size="sm">
<Input type="select" id="scenarioMode">
<option>Обычный</option>
<option>Почасовой</option>
<option>Ансамблевый</option>
<Input type="select" id="scenarioMode" bind:value={$ScenarioStore.template_data.prediction_mode}
on:change={() => {
scenarioUnsaved = true;
}}>
{#each Object.entries(PREDICTION_MODE_MAP) as [key, value]}
<option {value}
>{PPREDICTION_MODE_NAMES[key as keyof typeof PPREDICTION_MODE_NAMES]}
{key}
</option>
{/each}
</Input>
</InputGroup>
</FormGroup>
@ -141,7 +288,7 @@
<Button
color="primary"
title="Edit Saved Locations"
on:click={() => console.log("Not implemented yet")}
onclick={() => console.log("Not implemented yet")}
>
<span>Экспорт</span>
<Icon name="file-earmark-arrow-down" />
@ -151,3 +298,4 @@
</CardBody>
{/if}
</Card>
<ScenarioEditor bind:this={scenarioEditorRef} onSave={handleModalSave} />