control panel ux
This commit is contained in:
parent
a1d80eb984
commit
7d01fce094
8 changed files with 219 additions and 60 deletions
|
|
@ -12,14 +12,15 @@
|
||||||
Icon,
|
Icon,
|
||||||
} from "@sveltestrap/sveltestrap";
|
} from "@sveltestrap/sveltestrap";
|
||||||
|
|
||||||
|
import PointListModal from "$lib/components/PointListModal.svelte";
|
||||||
import SelectSearchable from "$lib/components/SelectSearchable.svelte";
|
import SelectSearchable from "$lib/components/SelectSearchable.svelte";
|
||||||
import { getForecast } from "$lib/prediction";
|
import { getForecast } from "$lib/prediction";
|
||||||
import type { FlightParameters, ProfileName, ProfileIdentifier } from "$lib/types";
|
import type { FlightParameters, ProfileName, ProfileIdentifier, SavedPoint } from "$lib/types";
|
||||||
import { PROFILE_MAP, PROFILE_NAMES } from "$lib/types";
|
import { PROFILE_MAP, PROFILE_NAMES } from "$lib/types";
|
||||||
import { SavedPointsStore, FlightParametersStore, writeLocalStorage } from "$lib/stores";
|
import { SavedPointsStore, FlightParametersStore, writeLocalStorage } from "$lib/stores";
|
||||||
import { getSavedPoints } from "$lib/api/points";
|
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import { addToast } from "$lib/components/Toast.svelte";
|
import { addToast } from "$lib/components/Toast.svelte";
|
||||||
|
import { getSavedPoints, savePoint, updatePoint, deletePoint } from "$lib/api/points";
|
||||||
|
|
||||||
// TODO: Move to $lib/utils/datetime.js
|
// TODO: Move to $lib/utils/datetime.js
|
||||||
// function getCurrentDateTime() {
|
// function getCurrentDateTime() {
|
||||||
|
|
@ -54,21 +55,22 @@
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
handleClickSelectOnMap?: () => void;
|
handleClickSelectOnMap?: () => void;
|
||||||
handleClickPointListModal?: () => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let {
|
let {
|
||||||
handleClickSelectOnMap = () => console.log("Select on map clicked"),
|
handleClickSelectOnMap = () => console.log("Select on map clicked"),
|
||||||
handleClickPointListModal = () => console.log("Open Point List Modal"),
|
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
|
||||||
// State
|
// State
|
||||||
let isCollapsed = $state(false);
|
let isCollapsed = $state(false);
|
||||||
|
|
||||||
|
let pointListModal: PointListModal | null = null;
|
||||||
|
|
||||||
// Initialize date/time
|
// Initialize date/time
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
let startDate = $state(now.toISOString().split("T")[0]);
|
let startDate = $state(now.toISOString().split("T")[0]);
|
||||||
let startTime = $state(now.toISOString().split("T")[1].split(".")[0]);
|
let startTime = $state(now.toISOString().split("T")[1].split(".")[0]);
|
||||||
|
let selectedPoint = $state($FlightParametersStore.start_point); // Default to custom point
|
||||||
|
|
||||||
// Coordinate inputs
|
// Coordinate inputs
|
||||||
let inputLat = $derived($FlightParametersStore.start_point === -1
|
let inputLat = $derived($FlightParametersStore.start_point === -1
|
||||||
|
|
@ -87,21 +89,73 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyCoordinatesFromInput() {
|
function applySeletedPoint() {
|
||||||
const lat = parseFloat(inputLat);
|
if (selectedPoint && selectedPoint !== -1) {
|
||||||
const lng = parseFloat(inputLng);
|
$FlightParametersStore.start_point = selectedPoint;
|
||||||
const alt = parseFloat(inputAlt);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!isNaN(lat) && !isNaN(lng)) {
|
function saveCurrentPoint() {
|
||||||
$FlightParametersStore.launch_latitude = lat;
|
if (selectedPoint !== -1) {
|
||||||
$FlightParametersStore.launch_longitude = lng;
|
const point = $SavedPointsStore.find(p => p.id === selectedPoint);
|
||||||
$FlightParametersStore.launch_altitude = alt || 0; // Default to 0 if alt is NaN
|
if (point) {
|
||||||
|
updatePoint(point)
|
||||||
|
.then((updatedPoint) => {
|
||||||
|
$SavedPointsStore = $SavedPointsStore.map((p) => (p.id === updatedPoint.id ? updatedPoint : p));
|
||||||
|
SavedPointsStore.set($SavedPointsStore);
|
||||||
|
addToast({
|
||||||
|
header: "Точка обновлена",
|
||||||
|
body: `Точка "${updatedPoint.name}" успешно обновлена.`,
|
||||||
|
color: "success",
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
addToast({
|
||||||
|
header: "Ошибка обновления точки",
|
||||||
|
body: `Ошибка при обновлении точки: ${error.message}`,
|
||||||
|
color: "danger",
|
||||||
|
});
|
||||||
|
console.error("Ошибка при обновлении точки:", error);
|
||||||
|
});
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
console.error("Invalid coordinate input");
|
pointListModal?.openModalAndCreate(null, {
|
||||||
// TODO: Show validation error to user
|
id: 0,
|
||||||
|
name: `Новая точка ${new Date().toLocaleString()}`,
|
||||||
|
lat: parseFloat(inputLat),
|
||||||
|
lon: parseFloat(inputLng),
|
||||||
|
alt: parseFloat(inputAlt),
|
||||||
|
}, true, onModalSave);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onModalSave(savedPoint: SavedPoint) {
|
||||||
|
if (savedPoint) {
|
||||||
|
$FlightParametersStore.start_point = savedPoint.id;
|
||||||
|
selectedPoint = savedPoint.id;
|
||||||
|
setToCustomOnChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleClickPointListModal() {
|
||||||
|
pointListModal?.openModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
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_latitude = parseFloat(inputLat);
|
||||||
|
|
@ -173,6 +227,8 @@
|
||||||
});
|
});
|
||||||
return [];
|
return [];
|
||||||
});
|
});
|
||||||
|
selectedPoint = $FlightParametersStore.start_point;
|
||||||
|
console.log("ControlPanel mounted", selectedPoint);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -239,9 +295,11 @@
|
||||||
<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">
|
||||||
|
<div class="position-relative flex-grow-1">
|
||||||
<SelectSearchable
|
<SelectSearchable
|
||||||
|
style="min-height: calc(1.5em + .5rem + 2px); padding: .25rem .5rem; font-size: .875rem;"
|
||||||
id="startPoint"
|
id="startPoint"
|
||||||
bind:selected={$FlightParametersStore.start_point}
|
bind:selected={selectedPoint}
|
||||||
options={$SavedPointsStore.map(point => ({
|
options={$SavedPointsStore.map(point => ({
|
||||||
value: point.id,
|
value: point.id,
|
||||||
label: point.name,
|
label: point.name,
|
||||||
|
|
@ -249,13 +307,49 @@
|
||||||
placeholder="Выберите точку старта"
|
placeholder="Выберите точку старта"
|
||||||
searchPlaceholder="Поиск точки..."
|
searchPlaceholder="Поиск точки..."
|
||||||
/>
|
/>
|
||||||
<Button color="secondary" title="Edit Saved Locations" onclick={handleClickPointListModal}>
|
<Button
|
||||||
<span>Редакт.</span>
|
size="sm"
|
||||||
<Icon name="journal-bookmark-fill" />
|
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={() => {
|
||||||
|
selectedPoint = -1;
|
||||||
|
$FlightParametersStore.start_point = -1;
|
||||||
|
}}
|
||||||
|
disabled={selectedPoint === -1}
|
||||||
|
>
|
||||||
|
<Icon name="x" style="font-size: 16px;" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<Button color="success" size="sm" onclick={applySeletedPoint} title="Apply Coordinates">
|
||||||
|
✓
|
||||||
</Button>
|
</Button>
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
|
<div class="d-flex gap-2 mb-2">
|
||||||
|
<Button
|
||||||
|
color="secondary flex-fill"
|
||||||
|
size="sm"
|
||||||
|
onclick={handleClickPointListModal}
|
||||||
|
title="Открыть список точек"
|
||||||
|
>
|
||||||
|
Все точки
|
||||||
|
<Icon name="journal-bookmark-fill" />
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
color="primary flex-fill"
|
||||||
|
size="sm"
|
||||||
|
onclick={saveCurrentPoint}
|
||||||
|
title="Сохранить текущие координаты"
|
||||||
|
>
|
||||||
|
Сохранить точку
|
||||||
|
<Icon name="floppy2-fill" />
|
||||||
|
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<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">
|
||||||
|
|
@ -274,18 +368,11 @@
|
||||||
placeholder="Longitude"
|
placeholder="Longitude"
|
||||||
onchange={setToCustomOnChange}
|
onchange={setToCustomOnChange}
|
||||||
/>
|
/>
|
||||||
<Button color="success" size="sm" onclick={applyCoordinatesFromInput} title="Apply Coordinates">
|
<Button color="secondary" size="sm" onclick={handleClickSelectOnMap}>
|
||||||
✓
|
<Icon name="geo-alt-fill" />
|
||||||
</Button>
|
</Button>
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
<FormGroup spacing="mb-2">
|
|
||||||
<Button color="outline-secondary" size="sm" class="w-100" onclick={handleClickSelectOnMap}>
|
|
||||||
Указать на карте
|
|
||||||
</Button>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
<div class="d-flex gap-2">
|
<div class="d-flex gap-2">
|
||||||
<FormGroup class="flex-fill w-50" spacing="mb-2">
|
<FormGroup class="flex-fill w-50" spacing="mb-2">
|
||||||
<Label for="startHeight" class="form-label">Высота старта (м):</Label>
|
<Label for="startHeight" class="form-label">Высота старта (м):</Label>
|
||||||
|
|
@ -338,3 +425,4 @@
|
||||||
</CardBody>
|
</CardBody>
|
||||||
{/if}
|
{/if}
|
||||||
</Card>
|
</Card>
|
||||||
|
<PointListModal bind:this={pointListModal} />
|
||||||
|
|
@ -24,6 +24,7 @@
|
||||||
isOpen = $bindable(false),
|
isOpen = $bindable(false),
|
||||||
onClose = () => {},
|
onClose = () => {},
|
||||||
onChange = () => {},
|
onChange = () => {},
|
||||||
|
onSave = () => {},
|
||||||
point = null,
|
point = null,
|
||||||
coordinates = { id: 0, name: "", lat: 0, lon: 0, alt: 0 },
|
coordinates = { id: 0, name: "", lat: 0, lon: 0, alt: 0 },
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
@ -31,6 +32,7 @@
|
||||||
// Runes
|
// Runes
|
||||||
let selectedPoint = $state<SavedPoint | null>(point);
|
let selectedPoint = $state<SavedPoint | null>(point);
|
||||||
let newPoint = $state<SavedPoint>(coordinates as SavedPoint);
|
let newPoint = $state<SavedPoint>(coordinates as SavedPoint);
|
||||||
|
let closeOnSave = $state(false);
|
||||||
let isEditing = $state(false);
|
let isEditing = $state(false);
|
||||||
let isAlertVisible = $state(false);
|
let isAlertVisible = $state(false);
|
||||||
let isConfirmationVisible = $state(false);
|
let isConfirmationVisible = $state(false);
|
||||||
|
|
@ -56,8 +58,29 @@
|
||||||
isOpen = true;
|
isOpen = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function openModalAndCreate(
|
||||||
|
point: SavedPoint | null = null,
|
||||||
|
coordinates: SavedPoint = { id: 0, name: "", lat: 0, lon: 0, alt: 0 },
|
||||||
|
close: 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;
|
||||||
|
}
|
||||||
|
isOpen = true;
|
||||||
|
closeOnSave = close;
|
||||||
|
onSave = onSaveCallback;
|
||||||
|
}
|
||||||
|
|
||||||
function closeModal() {
|
function closeModal() {
|
||||||
isOpen = false;
|
isOpen = false;
|
||||||
|
closeOnSave = false;
|
||||||
onClose();
|
onClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -90,7 +113,7 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSavePoint() {
|
export function handleSavePoint() {
|
||||||
if (isEditing && selectedPoint) {
|
if (isEditing && selectedPoint) {
|
||||||
updatePoint(newPoint)
|
updatePoint(newPoint)
|
||||||
.then((updatedPoint) => {
|
.then((updatedPoint) => {
|
||||||
|
|
@ -102,6 +125,10 @@
|
||||||
body: `Точка "${updatedPoint.name}" успешно обновлена.`,
|
body: `Точка "${updatedPoint.name}" успешно обновлена.`,
|
||||||
color: "success",
|
color: "success",
|
||||||
});
|
});
|
||||||
|
if (closeOnSave) {
|
||||||
|
closeModal();
|
||||||
|
}
|
||||||
|
onSave(updatedPoint);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
showAlert(`Ошибка при обновлении точки: ${error.message}`);
|
showAlert(`Ошибка при обновлении точки: ${error.message}`);
|
||||||
|
|
@ -117,6 +144,10 @@
|
||||||
body: `Точка "${savedPoint.name}" успешно сохранена.`,
|
body: `Точка "${savedPoint.name}" успешно сохранена.`,
|
||||||
color: "success",
|
color: "success",
|
||||||
});
|
});
|
||||||
|
if (closeOnSave) {
|
||||||
|
closeModal();
|
||||||
|
}
|
||||||
|
onSave(savedPoint);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
showAlert(`Ошибка при сохранении точки: ${error.message}`);
|
showAlert(`Ошибка при сохранении точки: ${error.message}`);
|
||||||
|
|
@ -143,7 +174,15 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal {isOpen} toggle={closeModal} size="lg" fade={false} backdrop={true} scrollable class={isConfirmationVisible ? "modal-tinted" : ""}>
|
<Modal
|
||||||
|
{isOpen}
|
||||||
|
toggle={closeModal}
|
||||||
|
size="lg"
|
||||||
|
fade={false}
|
||||||
|
backdrop={true}
|
||||||
|
scrollable
|
||||||
|
class={isConfirmationVisible ? "modal-tinted" : ""}
|
||||||
|
>
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h5 class="modal-title">Сохраненные точки</h5>
|
<h5 class="modal-title">Сохраненные точки</h5>
|
||||||
<button type="button" class="btn-close" onclick={closeModal} aria-label="Close"></button>
|
<button type="button" class="btn-close" onclick={closeModal} aria-label="Close"></button>
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,8 @@
|
||||||
} from "@sveltestrap/sveltestrap";
|
} from "@sveltestrap/sveltestrap";
|
||||||
|
|
||||||
import { PROFILE_MAP } from "$lib/types";
|
import { PROFILE_MAP } from "$lib/types";
|
||||||
import { FlightParametersStore, writeLocalStorage } from "$lib/stores";
|
import { FlightParametersStore, writeLocalStorage, ScenarioStore, SavedScenarioTemplatesStore } from "$lib/stores";
|
||||||
|
import SelectSearchable from "$lib/components/SelectSearchable.svelte";
|
||||||
|
|
||||||
let isCollapsed = false;
|
let isCollapsed = false;
|
||||||
|
|
||||||
|
|
@ -55,9 +56,20 @@
|
||||||
{#if !isCollapsed}
|
{#if !isCollapsed}
|
||||||
<CardBody>
|
<CardBody>
|
||||||
<FormGroup spacing="mb-2">
|
<FormGroup spacing="mb-2">
|
||||||
<Label for="scenarioName" class="form-label">Название сценария:</Label>
|
<Label for="scenarioName" class="form-label">Cценарий:</Label>
|
||||||
<InputGroup size="sm">
|
<InputGroup size="sm">
|
||||||
<Input id="scenarioName" type="text" />
|
<SelectSearchable
|
||||||
|
id="startPoint"
|
||||||
|
options={$SavedScenarioTemplatesStore.map(scenario => ({
|
||||||
|
value: scenario.id,
|
||||||
|
label: scenario.name,
|
||||||
|
}))}
|
||||||
|
placeholder="Выберите сценарий..."
|
||||||
|
searchPlaceholder="Поиск сценариев..."
|
||||||
|
/>
|
||||||
|
<Button color="success" title="Применить сценарий">
|
||||||
|
<span>✓</span>
|
||||||
|
</Button>
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
|
|
|
||||||
0
src/lib/components/ScenarioTemplateEditor.svelte
Normal file
0
src/lib/components/ScenarioTemplateEditor.svelte
Normal file
|
|
@ -1,6 +1,8 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
import type { HTMLAttributes } from 'svelte/elements';
|
import type { HTMLAttributes } from 'svelte/elements';
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import { on } from 'svelte/events';
|
||||||
|
|
||||||
interface Props extends HTMLAttributes<HTMLDivElement> {
|
interface Props extends HTMLAttributes<HTMLDivElement> {
|
||||||
options?: { value: any; label:string }[];
|
options?: { value: any; label:string }[];
|
||||||
|
|
@ -40,6 +42,11 @@
|
||||||
options.find(opt => opt.value === selected)?.label || ''
|
options.find(opt => opt.value === selected)?.label || ''
|
||||||
);
|
);
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
// Update dropdown position on mount
|
||||||
|
updateDropdownPosition();
|
||||||
|
});
|
||||||
|
|
||||||
function updateDropdownPosition() {
|
function updateDropdownPosition() {
|
||||||
if (!selectElement) return;
|
if (!selectElement) return;
|
||||||
const rect = selectElement.getBoundingClientRect();
|
const rect = selectElement.getBoundingClientRect();
|
||||||
|
|
@ -106,7 +113,7 @@
|
||||||
<span class:text-muted={!selected}>{selectedLabel || placeholder}</span>
|
<span class:text-muted={!selected}>{selectedLabel || placeholder}</span>
|
||||||
|
|
||||||
{#if isOpen}
|
{#if isOpen}
|
||||||
<div class="dropdown-menu show" bind:this={dropdownElement} style={dropdownStyle}>
|
<div class="pt-0 dropdown-menu show" bind:this={dropdownElement} style={dropdownStyle}>
|
||||||
<div class="p-2">
|
<div class="p-2">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { writable } from "svelte/store";
|
import { writable } from "svelte/store";
|
||||||
import type { FlightParameters, RawTelemetry, Telemetry } from "./types";
|
import type { FlightParameters, RawTelemetry, Telemetry } from "./types";
|
||||||
import type { RawPrediction, Prediction } from "./types";
|
import type { RawPrediction, Prediction } from "./types";
|
||||||
import type { SavedPoint, SavedFlightProfile, SavedScenarioTemplate } from "./types";
|
import type { SavedPoint, SavedFlightProfile, SavedScenarioTemplate, TemplateData } from "./types";
|
||||||
|
|
||||||
export const readLocalStorage = <T>(key: string, defaultValue: T): T => {
|
export const readLocalStorage = <T>(key: string, defaultValue: T): T => {
|
||||||
const item = localStorage.getItem(key);
|
const item = localStorage.getItem(key);
|
||||||
|
|
@ -53,6 +53,18 @@ export const FlightParametersStore = writable<FlightParameters>(
|
||||||
readLocalStorage<FlightParameters>("flightParameters", flightParametersDefaults)
|
readLocalStorage<FlightParameters>("flightParameters", flightParametersDefaults)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const templateDataDefaults: TemplateData = {
|
||||||
|
description: "",
|
||||||
|
prediction_mode: false,
|
||||||
|
model: "",
|
||||||
|
dataset: "",
|
||||||
|
flight_parameters: flightParametersDefaults,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ScenarioStore = writable<TemplateData>(
|
||||||
|
readLocalStorage<TemplateData>("scenario", templateDataDefaults)
|
||||||
|
);
|
||||||
|
|
||||||
export const RawTelemetryStore = writable<RawTelemetry>(
|
export const RawTelemetryStore = writable<RawTelemetry>(
|
||||||
readLocalStorage<RawTelemetry>("rawTelemetry", {} as RawTelemetry)
|
readLocalStorage<RawTelemetry>("rawTelemetry", {} as RawTelemetry)
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,19 @@ export interface FlightParameters {
|
||||||
profile: (typeof PROFILE_MAP)[ProfileName];
|
profile: (typeof PROFILE_MAP)[ProfileName];
|
||||||
version: number;
|
version: number;
|
||||||
start_point?: number; // Optional, used for saved points
|
start_point?: number; // Optional, used for saved points
|
||||||
|
rate_profile?: number; // Optional, used for custom profiles
|
||||||
|
template?: number; // Optional, used for saved scenarios
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TemplateData {
|
||||||
|
description: string;
|
||||||
|
prediction_mode: boolean;
|
||||||
|
model: string;
|
||||||
|
dataset: string;
|
||||||
|
flight_parameters: FlightParameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface Point {
|
export interface Point {
|
||||||
latlng: LatLngLiteral & { alt?: number };
|
latlng: LatLngLiteral & { alt?: number };
|
||||||
datetime: Date;
|
datetime: Date;
|
||||||
|
|
@ -112,5 +123,5 @@ export interface SavedFlightProfile {
|
||||||
export interface SavedScenarioTemplate {
|
export interface SavedScenarioTemplate {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
template_data: object;
|
template_data: TemplateData;
|
||||||
}
|
}
|
||||||
|
|
@ -18,8 +18,6 @@
|
||||||
let selectionToastId: string | null = null;
|
let selectionToastId: string | null = null;
|
||||||
let activeTab: 'control' | 'scenario' | 'settings' | 'about' = 'scenario';
|
let activeTab: 'control' | 'scenario' | 'settings' | 'about' = 'scenario';
|
||||||
|
|
||||||
let pointListModal: PointListModal | null = null;
|
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
PredictionStore.subscribe((data) => {
|
PredictionStore.subscribe((data) => {
|
||||||
if (data) {
|
if (data) {
|
||||||
|
|
@ -43,8 +41,8 @@
|
||||||
console.log("Selection mode enabled");
|
console.log("Selection mode enabled");
|
||||||
if (!selectionToastId) {
|
if (!selectionToastId) {
|
||||||
selectionToastId = addToast({
|
selectionToastId = addToast({
|
||||||
header: "Selection Mode",
|
header: "Режим выбора координат",
|
||||||
body: "Click on the map to select a position.",
|
body: "Кликните на карту, чтобы выбрать координаты",
|
||||||
color: "info",
|
color: "info",
|
||||||
persistent: true,
|
persistent: true,
|
||||||
onRemoveCallback: () => {
|
onRemoveCallback: () => {
|
||||||
|
|
@ -67,13 +65,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleClickPointListModal() {
|
|
||||||
if (map) {
|
|
||||||
map.stopSelection();
|
|
||||||
console.log("Selection mode disabled");
|
|
||||||
}
|
|
||||||
pointListModal?.openModal();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -95,7 +86,7 @@
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
{#if activeTab === 'control'}
|
{#if activeTab === 'control'}
|
||||||
<ControlPanel {handleClickSelectOnMap} {handleClickPointListModal} bind:this={controlPanel} />
|
<ControlPanel {handleClickSelectOnMap} bind:this={controlPanel} />
|
||||||
{:else if activeTab === 'scenario'}
|
{:else if activeTab === 'scenario'}
|
||||||
<ScenarioPanel />
|
<ScenarioPanel />
|
||||||
{:else if activeTab === 'settings'}
|
{:else if activeTab === 'settings'}
|
||||||
|
|
@ -106,6 +97,5 @@
|
||||||
</div>
|
</div>
|
||||||
</PanelContainer>
|
</PanelContainer>
|
||||||
<ToastContainer />
|
<ToastContainer />
|
||||||
<PointListModal bind:this={pointListModal} />
|
|
||||||
</Map>
|
</Map>
|
||||||
</main>
|
</main>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue