feat: polish

This commit is contained in:
Anatoly Antonov 2026-04-22 01:27:38 +09:00
parent 2e6177fe74
commit 4bd927bb4e
137 changed files with 6357 additions and 137560 deletions

View file

@ -1,130 +1,113 @@
<script lang="ts">
import Map from "$lib/components/Map.svelte";
import ControlPanel from "$lib/components/ControlPanel.svelte";
import Navbar from "$lib/components/Navbar.svelte";
import PanelContainer from "$lib/components/PanelContainer.svelte";
import ScenarioPanel from "$lib/components/ScenarioPanel.svelte";
import TabComponent from "$lib/components/ui/TabComponent.svelte";
import PointEditor from "$lib/components/editors/PointEditor.svelte";
import { onMount } from "svelte";
import { PredictionStore } from "$lib/stores";
import { addToast, removeToast } from "$lib/components/ui/Toast.svelte";
import ToastContainer from '$lib/components/ui/Toast.svelte';
import GenericPanel from "$lib/components/GenericPanel.svelte";
import TimeLine from "$lib/components/TimeLine.svelte";
import { onMount } from 'svelte';
import { Map as MapView } from '$map';
import type { IMap } from '$map';
import { startCoordinateSelection } from '$map';
import { Navbar } from '$features/auth';
import { PanelContainer, TabBar } from '$ui';
import { addToast, removeToast } from '$ui';
import { ControlPanel, ScenarioPanel } from '$features/prediction';
import {
WorkspacesPanel,
WorkspaceRenderer,
workspacesStore,
} from '$features/workspaces';
import { SettingsPanel } from '$features/settings';
import { TimeLine } from '$features/timeline';
import { t } from '$i18n';
import { requireAuthenticated } from '$auth';
let map: Map | null = null;
let panelContainer: PanelContainer | null = null;
let controlPanel: ControlPanel | null = null;
let selectionToastId: string | null = null;
let activeTabLeft: 'control' | 'scenario' | 'about' = 'scenario';
let activeTabRight: 'layers' | 'settings' | 'results' = 'results';
type LeftTab = 'scenario' | 'conditions' | 'about';
type RightTab = 'results' | 'workspaces' | 'settings';
onMount(() => {
PredictionStore.subscribe((data) => {
if (data) {
map?.clearMapLayers();
}
});
console.log("ControlPanel mounted");
console.log(panelContainer);
let mapComponent = $state<MapView | null>(null);
let controlPanelRef = $state<ControlPanel | null>(null);
let selectionToastId: string | null = null;
let selectionDispose: (() => void) | null = null;
if (panelContainer) {
let element = panelContainer.getElement();
if (!element) return;
let leftTab = $state<LeftTab>('scenario');
let rightTab = $state<RightTab>('workspaces');
// Disable click and scroll propagation to prevent map interaction
element.addEventListener('click', (e) => e.stopPropagation());
element.addEventListener('dblclick', (e) => e.stopPropagation());
element.addEventListener('mousedown', (e) => e.stopPropagation());
element.addEventListener('touchstart', (e) => e.stopPropagation());
element.addEventListener('wheel', (e) => e.stopPropagation());
}
});
onMount(async () => {
const ok = await requireAuthenticated('/login');
if (!ok) return;
if ($workspacesStore.items.length === 0) {
workspacesStore.add({ name: $t('workspaces.defaultName', { n: 1 }) });
}
});
function handleClickSelectOnMap() {
if (map) {
map.startSelection();
console.log("Selection mode enabled");
if (!selectionToastId) {
selectionToastId = addToast({
header: "Режим выбора координат",
body: "Кликните на карту, чтобы выбрать координаты",
color: "info",
persistent: true,
onRemoveCallback: () => {
selectionToastId = null;
map?.stopSelection();
console.log("Selection mode disabled");
}
});
}
}
}
function handleMapReady(_map: IMap) {
// Map is ready; consumers inside <MapView /> already have access via context.
}
function handleCoordinateSelection(event: CustomEvent<{ lat: number; lng: number }>) {
const { lat, lng } = event.detail;
controlPanel?.updateLaunchPosition(lat, lng);
console.log(`Selected coordinates: ${lat}, ${lng}`);
if (selectionToastId) {
removeToast(selectionToastId);
selectionToastId = null;
}
}
function handleSelectOnMap() {
const map = mapComponent?.getInstance();
if (!map) return;
function handleTimeUpdate(event: CustomEvent<{ index: number; lat: number; lng: number; alt: number; datetime: Date }>) {
const { lat, lng } = event.detail;
map?.updateAnimatedMarker(lat, lng);
}
selectionDispose = startCoordinateSelection(map, ({ lat, lng }) => {
controlPanelRef?.updateLaunchPosition(lat, lng);
if (selectionToastId) {
removeToast(selectionToastId);
selectionToastId = null;
}
selectionDispose = null;
});
selectionToastId = addToast({
header: $t('selection.header'),
body: $t('selection.body'),
color: 'info',
persistent: true,
onRemove: () => {
selectionDispose?.();
selectionDispose = null;
selectionToastId = null;
},
});
}
</script>
<main>
<Navbar />
<div style="height: var(--navbar-height);"></div> <!-- Spacer for fixed navbar -->
<Map bind:this={map} mode="prediction" bind:data={$PredictionStore} on:coordinatesSelected={handleCoordinateSelection}>
<PanelContainer bind:this={panelContainer} position="left">
<TabComponent
tabs={[
{ id: 'scenario', icon: 'file-earmark-play', label: 'Сценарий' },
{ id: 'control', icon: 'sliders', label: 'Условия' },
{ id: 'about', icon: 'info-circle', label: 'О проекте' }
]}
bind:activeTab={activeTabLeft}
/>
<div>
{#if activeTabLeft === 'control'}
<ControlPanel onSelectOnMapClick={handleClickSelectOnMap} bind:this={controlPanel} />
{:else if activeTabLeft === 'scenario'}
<ScenarioPanel />
{:else if activeTabLeft === 'about'}
<!-- <AboutPanel /> -->
{/if}
</div>
</PanelContainer>
<PanelContainer position="right">
<TabComponent
justify="end"
tabs={[
{ id: 'results', icon: 'bar-chart-line', label: 'Результаты' },
{ id: 'layers', icon: 'layers', label: 'Слои' },
{ id: 'settings', icon: 'gear', label: 'Настройки' },
]}
bind:activeTab={activeTabRight}
/>
<div>
{#if activeTabRight === 'results'}
<GenericPanel />
{:else if activeTabRight === 'layers'}
<GenericPanel />
{/if}
</div>
</PanelContainer>
<ToastContainer />
{#if $PredictionStore}
<TimeLine prediction={$PredictionStore} on:timeUpdate={handleTimeUpdate} />
{/if}
</Map>
<Navbar />
<div style="height: var(--navbar-height);"></div>
<MapView bind:this={mapComponent} onReady={handleMapReady}>
<WorkspaceRenderer />
<PanelContainer position="left">
<TabBar
tabs={[
{ id: 'scenario', icon: 'file-earmark-play', label: $t('panel.scenario') },
{ id: 'conditions', icon: 'sliders', label: $t('panel.conditions') },
{ id: 'about', icon: 'info-circle', label: $t('panel.about') },
]}
bind:active={leftTab} />
<div>
{#if leftTab === 'scenario'}
<ScenarioPanel />
{:else if leftTab === 'conditions'}
<ControlPanel bind:this={controlPanelRef} onSelectOnMapClick={handleSelectOnMap} />
{/if}
</div>
</PanelContainer>
<PanelContainer position="right">
<TabBar
justify="end"
tabs={[
{ id: 'workspaces', icon: 'layers', label: $t('panel.workspaces') },
{ id: 'results', icon: 'bar-chart-line', label: $t('panel.results') },
{ id: 'settings', icon: 'gear', label: $t('panel.settings') },
]}
bind:active={rightTab} />
<div>
{#if rightTab === 'workspaces'}
<WorkspacesPanel />
{:else if rightTab === 'settings'}
<SettingsPanel />
{/if}
</div>
</PanelContainer>
<TimeLine />
</MapView>
</main>

View file

@ -1 +0,0 @@
export const ssr =false;