Scenario system & point editor rework
This commit is contained in:
parent
7d01fce094
commit
19f969c18c
13 changed files with 1010 additions and 694 deletions
|
|
@ -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">Cценарий:</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} />
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue