Map coordinates selection

This commit is contained in:
ThePetrovich 2025-06-27 21:14:14 +08:00
parent 72c0d5e609
commit 3d609771de
5 changed files with 157 additions and 23 deletions

View file

@ -20,7 +20,7 @@
let selectedProfile: ProfileName = "Normal";
let startPoint = "Custom";
export let element: HTMLDivElement | null = null;
let element: HTMLDivElement | null = null;
const now = new Date();
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 lat = parseFloat(inputLat);
const lng = parseFloat(inputLng);
@ -80,8 +84,8 @@
$FlightParametersStore.launch_latitude = lat;
$FlightParametersStore.launch_longitude = lng;
console.log("Launch position updated:", lat, lng);
inputLat = lat.toString();
inputLng = lng.toString();
inputLat = lat.toFixed(6).toString();
inputLng = lng.toFixed(6).toString();
};
export const getElement = () => {
@ -89,7 +93,11 @@
};
</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>
<CardHeader
class="bg-primary text-white d-flex justify-content-between align-items-center card-header"
@ -221,7 +229,7 @@
color="outline-secondary"
size="sm"
class="w-100"
on:click={() => console.log("Select on map clicked")}>Указать на карте</Button
on:click={ handleClickSelectOnMap }>Указать на карте</Button
>
</FormGroup>

97
src/routes/Toast.svelte Normal file
View 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>

View file

@ -7,8 +7,6 @@
import { distHaversine } from "../lib/mathutil.ts";
import type { PredictionData, TelemetryData } from "../lib/types.ts";
/**
* @type {'prediction' | 'telemetry'}
*/
@ -153,9 +151,7 @@
{mouseLng.toFixed(6)}
</p>
</div>
<div class="panel-container">
<slot />
</div>
<slot />
{#if map}
<VelocityLayer {map} {velocityOptions} />
{/if}

View file

@ -1,15 +1,17 @@
<script lang="ts">
import Map from '../Map.svelte';
import ControlPanel from '../ControlPanel.svelte';
import Navbar from '../Navbar.svelte';
import { onMount } from 'svelte';
import { PredictionStore } from '$lib/stores';
import { Modal } from '@sveltestrap/sveltestrap';
import L from 'leaflet';
import Map from "../Map.svelte";
import ControlPanel from "../ControlPanel.svelte";
import Navbar from "../Navbar.svelte";
import { onMount } from "svelte";
import { PredictionStore } from "$lib/stores";
import { Modal } from "@sveltestrap/sveltestrap";
import { addToast, removeToast } from "../Toast.svelte"
import ToastContainer from '../Toast.svelte';
import L from "leaflet";
let map: Map | null = null;
let panel: ControlPanel | null = null;
let selectionToastId: string | null = null;
onMount(() => {
PredictionStore.subscribe((data) => {
@ -17,7 +19,7 @@
map?.clearMapLayers();
}
});
console.log('ControlPanel mounted');
console.log("ControlPanel mounted");
console.log(panel);
if (panel) {
@ -26,11 +28,42 @@
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>
<main>
<Navbar />
<Map bind:this={map} mode="prediction" bind:data={$PredictionStore}>
<ControlPanel bind:this={panel} />
<Map bind:this={map} mode="prediction" bind:data={$PredictionStore} on:coordinatesSelected={handleCoordinateSelection}>
<ControlPanel bind:this={panel} {handleClickSelectOnMap} />
<ToastContainer />
</Map>
</main>
</main>

View file

@ -78,6 +78,6 @@
.panel-container {
position: absolute;
bottom: 20px;
right: 20px;
left: 20px;
z-index: 1000;
}