Map coordinates selection
This commit is contained in:
parent
72c0d5e609
commit
3d609771de
5 changed files with 157 additions and 23 deletions
|
|
@ -20,7 +20,7 @@
|
||||||
let selectedProfile: ProfileName = "Normal";
|
let selectedProfile: ProfileName = "Normal";
|
||||||
let startPoint = "Custom";
|
let startPoint = "Custom";
|
||||||
|
|
||||||
export let element: HTMLDivElement | null = null;
|
let element: HTMLDivElement | null = null;
|
||||||
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
let startDate = now.toISOString().split("T")[0]; // YYYY-MM-DD
|
let startDate = now.toISOString().split("T")[0]; // YYYY-MM-DD
|
||||||
|
|
@ -53,6 +53,10 @@
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export let handleClickSelectOnMap = () => {
|
||||||
|
console.log("Select on map clicked");
|
||||||
|
}
|
||||||
|
|
||||||
const applyCoordinatesFromInput = () => {
|
const applyCoordinatesFromInput = () => {
|
||||||
const lat = parseFloat(inputLat);
|
const lat = parseFloat(inputLat);
|
||||||
const lng = parseFloat(inputLng);
|
const lng = parseFloat(inputLng);
|
||||||
|
|
@ -80,8 +84,8 @@
|
||||||
$FlightParametersStore.launch_latitude = lat;
|
$FlightParametersStore.launch_latitude = lat;
|
||||||
$FlightParametersStore.launch_longitude = lng;
|
$FlightParametersStore.launch_longitude = lng;
|
||||||
console.log("Launch position updated:", lat, lng);
|
console.log("Launch position updated:", lat, lng);
|
||||||
inputLat = lat.toString();
|
inputLat = lat.toFixed(6).toString();
|
||||||
inputLng = lng.toString();
|
inputLng = lng.toFixed(6).toString();
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getElement = () => {
|
export const getElement = () => {
|
||||||
|
|
@ -89,7 +93,11 @@
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div bind:this={element} style="width: 23rem; max-height: 80vh; overflow-y: auto; z-index: 1000;" class="position-absolute shadow-lg bottom-0 end-0 m-3">
|
<div
|
||||||
|
bind:this={element}
|
||||||
|
style="width: 23rem; max-height: 80vh; overflow-y: auto; z-index: 1000;"
|
||||||
|
class="position-absolute shadow-lg panel-container"
|
||||||
|
>
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader
|
<CardHeader
|
||||||
class="bg-primary text-white d-flex justify-content-between align-items-center card-header"
|
class="bg-primary text-white d-flex justify-content-between align-items-center card-header"
|
||||||
|
|
@ -221,7 +229,7 @@
|
||||||
color="outline-secondary"
|
color="outline-secondary"
|
||||||
size="sm"
|
size="sm"
|
||||||
class="w-100"
|
class="w-100"
|
||||||
on:click={() => console.log("Select on map clicked")}>Указать на карте</Button
|
on:click={ handleClickSelectOnMap }>Указать на карте</Button
|
||||||
>
|
>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
|
|
|
||||||
97
src/routes/Toast.svelte
Normal file
97
src/routes/Toast.svelte
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
<script context="module">
|
||||||
|
import { writable } from 'svelte/store';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'info' | 'light' | 'dark'} ToastColor
|
||||||
|
* @typedef {object} ToastMessage
|
||||||
|
* @property {string} id - Unique identifier
|
||||||
|
* @property {string} header - Toast title
|
||||||
|
* @property {string} body - Toast message content
|
||||||
|
* @property {ToastColor} [color='info'] - The color of the toast header icon
|
||||||
|
* @property {boolean} [persistent=false] - If true, toast will not auto-close
|
||||||
|
* @property {function} [onRemoveCallback=null] - Callback function to be called when the toast is removed
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @type {import('svelte/store').Writable<ToastMessage[]>} */
|
||||||
|
export const toasts = writable([]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new toast to the list.
|
||||||
|
* @param {Omit<ToastMessage, 'id'>} toast
|
||||||
|
* @returns {string} The ID of the new toast.
|
||||||
|
*/
|
||||||
|
export function addToast(toast) {
|
||||||
|
const id = crypto.randomUUID();
|
||||||
|
toasts.update((all) => [...all, { id, ...toast }]);
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a toast by its ID.
|
||||||
|
* @param {string} id
|
||||||
|
*/
|
||||||
|
export function removeToast(id) {
|
||||||
|
// call the onRemoveCallback if it exists
|
||||||
|
toasts.update((all) => {
|
||||||
|
const toast = all.find((t) => t.id === id);
|
||||||
|
if (toast && toast.onRemoveCallback) {
|
||||||
|
toast.onRemoveCallback(id);
|
||||||
|
}
|
||||||
|
return all.filter((t) => t.id !== id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback function to be called when a toast is removed.
|
||||||
|
* @param {string} id - The ID of the removed toast.
|
||||||
|
*/
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { Toast, ToastBody, ToastHeader } from '@sveltestrap/sveltestrap';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a toast from the list by its ID.
|
||||||
|
* @param {string} id
|
||||||
|
*/
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
This container holds all the toasts.
|
||||||
|
To use this component:
|
||||||
|
1. Import it into your layout or page: `import ToastContainer from './Toast.svelte';`
|
||||||
|
2. Place `<ToastContainer />` in your markup.
|
||||||
|
3. To show a toast from any other component:
|
||||||
|
import { addToast } from './Toast.svelte';
|
||||||
|
|
||||||
|
// For an auto-closing error message
|
||||||
|
addToast({ header: 'Error', body: 'Something went wrong.', color: 'danger' });
|
||||||
|
|
||||||
|
// For a persistent "map mode" indication
|
||||||
|
addToast({ header: 'Map Mode', body: 'You are in satellite view.', color: 'info', persistent: true });
|
||||||
|
-->
|
||||||
|
<div class="toast-container position-fixed bottom-0 end-0 p-3">
|
||||||
|
{#each $toasts as toast (toast.id)}
|
||||||
|
<Toast
|
||||||
|
isOpen={true}
|
||||||
|
autohide={!toast.persistent}
|
||||||
|
delay={5000}
|
||||||
|
color={toast.color || 'info'}
|
||||||
|
on:close={() => removeToast(toast.id)}
|
||||||
|
>
|
||||||
|
<ToastHeader toggle={() => removeToast(toast.id)}>
|
||||||
|
{toast.header}
|
||||||
|
</ToastHeader>
|
||||||
|
<ToastBody>
|
||||||
|
{toast.body}
|
||||||
|
</ToastBody>
|
||||||
|
</Toast>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.toast-container {
|
||||||
|
z-index: 1090; /* High z-index to appear above other elements */
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -7,8 +7,6 @@
|
||||||
import { distHaversine } from "../lib/mathutil.ts";
|
import { distHaversine } from "../lib/mathutil.ts";
|
||||||
import type { PredictionData, TelemetryData } from "../lib/types.ts";
|
import type { PredictionData, TelemetryData } from "../lib/types.ts";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {'prediction' | 'telemetry'}
|
* @type {'prediction' | 'telemetry'}
|
||||||
*/
|
*/
|
||||||
|
|
@ -153,9 +151,7 @@
|
||||||
{mouseLng.toFixed(6)}
|
{mouseLng.toFixed(6)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-container">
|
<slot />
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
{#if map}
|
{#if map}
|
||||||
<VelocityLayer {map} {velocityOptions} />
|
<VelocityLayer {map} {velocityOptions} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,17 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Map from '../Map.svelte';
|
import Map from "../Map.svelte";
|
||||||
import ControlPanel from '../ControlPanel.svelte';
|
import ControlPanel from "../ControlPanel.svelte";
|
||||||
import Navbar from '../Navbar.svelte';
|
import Navbar from "../Navbar.svelte";
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from "svelte";
|
||||||
import { PredictionStore } from '$lib/stores';
|
import { PredictionStore } from "$lib/stores";
|
||||||
import { Modal } from '@sveltestrap/sveltestrap';
|
import { Modal } from "@sveltestrap/sveltestrap";
|
||||||
import L from 'leaflet';
|
import { addToast, removeToast } from "../Toast.svelte"
|
||||||
|
import ToastContainer from '../Toast.svelte';
|
||||||
|
import L from "leaflet";
|
||||||
|
|
||||||
let map: Map | null = null;
|
let map: Map | null = null;
|
||||||
let panel: ControlPanel | null = null;
|
let panel: ControlPanel | null = null;
|
||||||
|
let selectionToastId: string | null = null;
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
PredictionStore.subscribe((data) => {
|
PredictionStore.subscribe((data) => {
|
||||||
|
|
@ -17,7 +19,7 @@
|
||||||
map?.clearMapLayers();
|
map?.clearMapLayers();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
console.log('ControlPanel mounted');
|
console.log("ControlPanel mounted");
|
||||||
console.log(panel);
|
console.log(panel);
|
||||||
|
|
||||||
if (panel) {
|
if (panel) {
|
||||||
|
|
@ -26,11 +28,42 @@
|
||||||
L.DomEvent.disableScrollPropagation(element);
|
L.DomEvent.disableScrollPropagation(element);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function handleClickSelectOnMap() {
|
||||||
|
if (map) {
|
||||||
|
map.startSelection();
|
||||||
|
console.log("Selection mode enabled");
|
||||||
|
if (!selectionToastId) {
|
||||||
|
selectionToastId = addToast({
|
||||||
|
header: "Selection Mode",
|
||||||
|
body: "Click on the map to select a position.",
|
||||||
|
color: "info",
|
||||||
|
persistent: true,
|
||||||
|
onRemoveCallback: () => {
|
||||||
|
selectionToastId = null;
|
||||||
|
map?.stopSelection();
|
||||||
|
console.log("Selection mode disabled");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCoordinateSelection(event: CustomEvent<{ lat: number; lng: number }>) {
|
||||||
|
const { lat, lng } = event.detail;
|
||||||
|
panel?.updateLaunchPosition(lat, lng);
|
||||||
|
console.log(`Selected coordinates: ${lat}, ${lng}`);
|
||||||
|
if (selectionToastId) {
|
||||||
|
removeToast(selectionToastId);
|
||||||
|
selectionToastId = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
<Navbar />
|
<Navbar />
|
||||||
<Map bind:this={map} mode="prediction" bind:data={$PredictionStore}>
|
<Map bind:this={map} mode="prediction" bind:data={$PredictionStore} on:coordinatesSelected={handleCoordinateSelection}>
|
||||||
<ControlPanel bind:this={panel} />
|
<ControlPanel bind:this={panel} {handleClickSelectOnMap} />
|
||||||
|
<ToastContainer />
|
||||||
</Map>
|
</Map>
|
||||||
</main>
|
</main>
|
||||||
|
|
@ -78,6 +78,6 @@
|
||||||
.panel-container {
|
.panel-container {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 20px;
|
bottom: 20px;
|
||||||
right: 20px;
|
left: 20px;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue