about page

This commit is contained in:
vcoppe
2024-07-05 16:08:16 +02:00
parent 9edcc5b55b
commit cb735db216
27 changed files with 44025 additions and 167 deletions

View File

@@ -12,15 +12,30 @@
import { Toaster } from '$lib/components/ui/sonner';
import { settings } from '$lib/db';
import { gpxStatistics, slicedGPXStatistics } from '$lib/stores';
const { verticalFileView, elevationProfile, bottomPanelSize, rightPanelSize } = settings;
const {
verticalFileView,
elevationProfile,
bottomPanelSize,
rightPanelSize,
distanceUnits,
velocityUnits,
temperatureUnits,
additionalDatasets,
elevationFill
} = settings;
</script>
<div class="fixed flex flex-row w-screen h-screen h-dvh">
<div class="flex flex-col grow h-full min-w-0">
<div class="grow relative">
<Menu />
<Toolbar />
<div
class="absolute top-0 bottom-0 left-0 z-20 flex flex-col justify-center pointer-events-none"
>
<Toolbar />
</div>
<Map class="h-full {$verticalFileView ? '' : 'horizontal'}" />
<StreetViewControl />
<LayerControl />
@@ -39,9 +54,25 @@
class="{$elevationProfile ? '' : 'h-10'} flex flex-row gap-2"
style={$elevationProfile ? `height: ${$bottomPanelSize}px` : ''}
>
<GPXStatistics />
<GPXStatistics
{gpxStatistics}
{slicedGPXStatistics}
panelSize={$bottomPanelSize}
orientation={$elevationProfile ? 'vertical' : 'horizontal'}
velocityUnits={$velocityUnits}
/>
{#if $elevationProfile}
<ElevationProfile />
<ElevationProfile
{gpxStatistics}
{slicedGPXStatistics}
bind:additionalDatasets={$additionalDatasets}
bind:elevationFill={$elevationFill}
panelSize={$bottomPanelSize}
distanceUnits={$distanceUnits}
velocityUnits={$velocityUnits}
temperatureUnits={$temperatureUnits}
class="py-2 pr-4"
/>
{/if}
</div>
</div>

View File

@@ -3,8 +3,7 @@
import Tooltip from '$lib/components/Tooltip.svelte';
import Chart from 'chart.js/auto';
import mapboxgl from 'mapbox-gl';
import { map, gpxStatistics, slicedGPXStatistics } from '$lib/stores';
import { settings } from '$lib/db';
import { map } from '$lib/stores';
import { onDestroy, onMount } from 'svelte';
import {
BrickWall,
@@ -37,8 +36,18 @@
getVelocityWithUnits,
secondsToHHMMSS
} from '$lib/units';
import { get } from 'svelte/store';
import type { Writable } from 'svelte/store';
import { DateFormatter } from '@internationalized/date';
import type { GPXStatistics } from 'gpx';
export let gpxStatistics: Writable<GPXStatistics>;
export let slicedGPXStatistics: Writable<[GPXStatistics, number, number] | undefined>;
export let distanceUnits: 'metric' | 'imperial';
export let velocityUnits: 'speed' | 'pace';
export let temperatureUnits: 'celsius' | 'fahrenheit';
export let panelSize: number;
export let additionalDatasets: string[];
export let elevationFill: 'slope' | 'surface' | undefined;
let df: DateFormatter;
@@ -50,20 +59,19 @@
}
let canvas: HTMLCanvasElement;
let showAdditionalScales = true;
let updateShowAdditionalScales = () => {
showAdditionalScales = canvas.width >= 1200;
};
let overlay: HTMLCanvasElement;
let chart: Chart;
Chart.defaults.font.family =
'ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"'; // Tailwind CSS font
let elevationFill: string;
let additionalDatasets: string[];
let marker: mapboxgl.Marker | null = null;
let dragging = false;
let { distanceUnits, velocityUnits, temperatureUnits, bottomPanelSize } = settings;
let options = {
animation: false,
parsing: false,
@@ -127,7 +135,7 @@
}
return `${$_('quantities.elevation')}: ${getElevationWithUnits(point.y, false)}`;
} else if (context.datasetIndex === 1) {
return `${$velocityUnits === 'speed' ? $_('quantities.speed') : $_('quantities.pace')}: ${getVelocityWithUnits(point.y, false)}`;
return `${velocityUnits === 'speed' ? $_('quantities.speed') : $_('quantities.pace')}: ${getVelocityWithUnits(point.y, false)}`;
} else if (context.datasetIndex === 2) {
return `${$_('quantities.heartrate')}: ${getHeartRateWithUnits(point.y)}`;
} else if (context.datasetIndex === 3) {
@@ -173,6 +181,7 @@
onResize: function (chart, size) {
overlay.width = size.width;
overlay.height = size.height;
updateShowAdditionalScales();
}
};
@@ -185,7 +194,7 @@
} = {
speed: {
id: 'speed',
getLabel: () => ($velocityUnits === 'speed' ? $_('quantities.speed') : $_('quantities.pace')),
getLabel: () => (velocityUnits === 'speed' ? $_('quantities.speed') : $_('quantities.pace')),
getUnits: () => getVelocityUnits()
},
hr: {
@@ -225,13 +234,13 @@
grid: {
display: false
},
reverse: () => id === 'speed' && $velocityUnits === 'pace',
reverse: () => id === 'speed' && velocityUnits === 'pace',
display: false
};
}
options.scales.yspeed['ticks'] = {
callback: function (value: number) {
if ($velocityUnits === 'speed') {
if (velocityUnits === 'speed') {
return value;
} else {
return secondsToHHMMSS(value);
@@ -268,6 +277,8 @@
element
});
updateShowAdditionalScales();
// Overlay canvas to create a selection rectangle
overlay.width = canvas.width;
overlay.height = canvas.height;
@@ -288,7 +299,7 @@
if (points.length === 0) {
return evt.x - rect.left <= chart.chartArea.left
? 0
: get(gpxStatistics).local.points.length - 1;
: $gpxStatistics.local.points.length - 1;
}
let point = points.find((point) => point.element.raw);
if (point) {
@@ -309,14 +320,11 @@
dragging = true;
endIndex = getIndex(evt);
if (startIndex !== endIndex) {
slicedGPXStatistics.set([
get(gpxStatistics).slice(
Math.min(startIndex, endIndex),
Math.max(startIndex, endIndex)
),
$slicedGPXStatistics = [
$gpxStatistics.slice(Math.min(startIndex, endIndex), Math.max(startIndex, endIndex)),
Math.min(startIndex, endIndex),
Math.max(startIndex, endIndex)
]);
];
}
}
}
@@ -326,7 +334,7 @@
canvas.style.cursor = '';
endIndex = getIndex(evt);
if (startIndex === endIndex) {
slicedGPXStatistics.set(undefined);
$slicedGPXStatistics = undefined;
}
}
canvas.addEventListener('pointerdown', onMouseDown);
@@ -334,7 +342,7 @@
canvas.addEventListener('pointerup', onMouseUp);
});
$: if (chart && $distanceUnits && $velocityUnits && $temperatureUnits) {
$: if (chart && distanceUnits && velocityUnits && temperatureUnits) {
let data = $gpxStatistics;
// update data
@@ -488,11 +496,12 @@
chart.data.datasets[4].hidden = !includeTemperature;
chart.data.datasets[5].hidden = !includePower;
}
chart.options.scales[`y${datasets.speed.id}`].display = includeSpeed;
chart.options.scales[`y${datasets.hr.id}`].display = includeHeartRate;
chart.options.scales[`y${datasets.cad.id}`].display = includeCadence;
chart.options.scales[`y${datasets.atemp.id}`].display = includeTemperature;
chart.options.scales[`y${datasets.power.id}`].display = includePower;
chart.options.scales[`y${datasets.speed.id}`].display = includeSpeed && showAdditionalScales;
chart.options.scales[`y${datasets.hr.id}`].display = includeHeartRate && showAdditionalScales;
chart.options.scales[`y${datasets.cad.id}`].display = includeCadence && showAdditionalScales;
chart.options.scales[`y${datasets.atemp.id}`].display =
includeTemperature && showAdditionalScales;
chart.options.scales[`y${datasets.power.id}`].display = includePower && showAdditionalScales;
chart.update();
}
@@ -507,10 +516,10 @@
selectionContext.clearRect(0, 0, overlay.width, overlay.height);
let startPixel = chart.scales.x.getPixelForValue(
getConvertedDistance(get(gpxStatistics).local.distance.total[startIndex])
getConvertedDistance($gpxStatistics.local.distance.total[startIndex])
);
let endPixel = chart.scales.x.getPixelForValue(
getConvertedDistance(get(gpxStatistics).local.distance.total[endIndex])
getConvertedDistance($gpxStatistics.local.distance.total[endIndex])
);
selectionContext.fillRect(
@@ -534,17 +543,14 @@
});
</script>
<div class="h-full grow min-w-0 flex flex-row gap-4 items-center py-2 pr-4">
<div class="h-full grow min-w-0 flex flex-row gap-4 items-center {$$props.class ?? ''}">
<div class="grow h-full min-w-0">
<canvas bind:this={overlay} class="absolute pointer-events-none"></canvas>
<canvas bind:this={canvas} class="w-full h-full"></canvas>
</div>
<div
class="h-full flex flex-col justify-center"
style="width: {$bottomPanelSize > 158 ? 22 : 42}px"
>
<div class="h-full flex flex-col justify-center" style="width: {panelSize > 158 ? 22 : 42}px">
<ToggleGroup.Root
class="{$bottomPanelSize > 158
class="{panelSize > 158
? 'flex-col'
: 'flex-row'} flex-wrap gap-0 min-h-0 content-center border rounded-t-md"
type="single"
@@ -564,7 +570,7 @@
</ToggleGroup.Item>
</ToggleGroup.Root>
<ToggleGroup.Root
class="{$bottomPanelSize > 158
class="{panelSize > 158
? 'flex-col'
: 'flex-row'} flex-wrap gap-0 min-h-0 content-center border rounded-b-md -mt-[1px]"
type="multiple"
@@ -574,7 +580,7 @@
<Tooltip side="left">
<Zap slot="data" size="15" />
<span slot="tooltip"
>{$velocityUnits === 'speed' ? $_('chart.show_speed') : $_('chart.show_pace')}</span
>{velocityUnits === 'speed' ? $_('chart.show_speed') : $_('chart.show_pace')}</span
>
</Tooltip>
</ToggleGroup.Item>

View File

@@ -3,15 +3,17 @@
import Tooltip from '$lib/components/Tooltip.svelte';
import WithUnits from '$lib/components/WithUnits.svelte';
import { gpxStatistics, slicedGPXStatistics } from '$lib/stores';
import { settings } from '$lib/db';
import { MoveDownRight, MoveUpRight, Ruler, Timer, Zap } from 'lucide-svelte';
import { _ } from 'svelte-i18n';
import type { GPXStatistics } from 'gpx';
import type { Writable } from 'svelte/store';
const { velocityUnits, elevationProfile, bottomPanelSize } = settings;
export let gpxStatistics: Writable<GPXStatistics>;
export let slicedGPXStatistics: Writable<[GPXStatistics, number, number] | undefined>;
export let velocityUnits: 'speed' | 'pace';
export let orientation: 'horizontal' | 'vertical';
export let panelSize: number;
let statistics: GPXStatistics;
@@ -23,14 +25,14 @@
</script>
<Card.Root
class="h-full {$elevationProfile
? ''
: 'w-full pr-4'} overflow-hidden border-none shadow-none min-w-52 pl-4"
class="h-full {orientation === 'vertical'
? 'min-w-52'
: 'w-full pr-4'} border-none shadow-none pl-4"
>
<Card.Content
class="h-full flex {$elevationProfile
class="h-full flex {orientation === 'vertical'
? 'flex-col justify-center'
: 'flex-row w-full justify-between'} flex-wrap gap-4 p-0"
: 'flex-row w-full justify-between'} gap-4 p-0"
>
<Tooltip>
<span slot="data" class="flex flex-row items-center">
@@ -48,8 +50,8 @@
</span>
<span slot="tooltip">{$_('quantities.elevation')}</span>
</Tooltip>
{#if $bottomPanelSize > 120 || !$elevationProfile}
<Tooltip>
{#if panelSize > 120 || orientation === 'horizontal'}
<Tooltip class={orientation === 'horizontal' ? 'hidden xs:block' : ''}>
<span slot="data" class="flex flex-row items-center">
<Zap size="18" class="mr-1" />
<WithUnits value={statistics.global.speed.moving} type="speed" showUnits={false} />
@@ -57,14 +59,14 @@
<WithUnits value={statistics.global.speed.total} type="speed" />
</span>
<span slot="tooltip"
>{$velocityUnits === 'speed' ? $_('quantities.speed') : $_('quantities.pace')} ({$_(
>{velocityUnits === 'speed' ? $_('quantities.speed') : $_('quantities.pace')} ({$_(
'quantities.moving'
)} / {$_('quantities.total')})</span
>
</Tooltip>
{/if}
{#if $bottomPanelSize > 160 || !$elevationProfile}
<Tooltip>
{#if panelSize > 160 || orientation === 'horizontal'}
<Tooltip class={orientation === 'horizontal' ? 'hidden md:block' : ''}>
<span slot="data" class="flex flex-row items-center">
<Timer size="18" class="mr-1" />
<WithUnits value={statistics.global.time.moving} type="time" />

View File

@@ -5,7 +5,7 @@
</script>
<Tooltip.Root>
<Tooltip.Trigger>
<Tooltip.Trigger {...$$restProps}>
<slot name="data" />
</Tooltip.Trigger>
<Tooltip.Content {side}>

View File

@@ -15,7 +15,7 @@
</script>
{#if module !== undefined}
<div class="markdown">
<div class="markdown space-y-3">
<svelte:component this={module} />
</div>
{/if}
@@ -26,13 +26,13 @@
:global(.markdown h1) {
@apply text-3xl;
@apply font-bold;
@apply mb-3;
@apply font-semibold;
@apply mb-6;
}
:global(.markdown h2) {
@apply text-2xl;
@apply font-bold;
@apply font-semibold;
@apply mb-3;
}
@@ -41,6 +41,11 @@
@apply hover:underline;
}
:global(.markdown p > a) {
@apply text-blue-500;
@apply hover:underline;
}
:global(.markdown ul) {
@apply list-disc;
@apply pl-4;

View File

@@ -16,44 +16,42 @@
import ToolbarItemMenu from './ToolbarItemMenu.svelte';
</script>
<div class="absolute top-0 bottom-0 left-0 z-20 flex flex-col justify-center pointer-events-none">
<div class="flex flex-row w-screen items-center">
<div
class="h-fit flex flex-col p-1 gap-1.5 bg-background rounded-r-md pointer-events-auto shadow-md"
>
<ToolbarItem tool={Tool.ROUTING}>
<Pencil slot="icon" size="18" class="h-" />
<span slot="tooltip">{$_('toolbar.routing.tooltip')}</span>
</ToolbarItem>
<ToolbarItem tool={Tool.WAYPOINT}>
<MapPin slot="icon" size="18" />
<span slot="tooltip">{$_('toolbar.waypoint.tooltip')}</span>
</ToolbarItem>
<ToolbarItem tool={Tool.SCISSORS}>
<Scissors slot="icon" size="18" />
<span slot="tooltip">{$_('toolbar.scissors.tooltip')}</span>
</ToolbarItem>
<ToolbarItem tool={Tool.TIME}>
<CalendarClock slot="icon" size="18" />
<span slot="tooltip">{$_('toolbar.time.tooltip')}</span>
</ToolbarItem>
<ToolbarItem tool={Tool.MERGE}>
<Group slot="icon" size="18" />
<span slot="tooltip">{$_('toolbar.merge.tooltip')}</span>
</ToolbarItem>
<ToolbarItem tool={Tool.EXTRACT}>
<Ungroup slot="icon" size="18" />
<span slot="tooltip">{$_('toolbar.extract.tooltip')}</span>
</ToolbarItem>
<ToolbarItem tool={Tool.REDUCE}>
<Filter slot="icon" size="18" />
<span slot="tooltip">{$_('toolbar.reduce.tooltip')}</span>
</ToolbarItem>
<ToolbarItem tool={Tool.CLEAN}>
<SquareDashedMousePointer slot="icon" size="18" />
<span slot="tooltip">{$_('toolbar.clean.tooltip')}</span>
</ToolbarItem>
</div>
<ToolbarItemMenu />
<div class="flex flex-row w-full items-center">
<div
class="h-fit flex flex-col p-1 gap-1.5 bg-background rounded-r-md pointer-events-auto shadow-md"
>
<ToolbarItem tool={Tool.ROUTING}>
<Pencil slot="icon" size="18" class="h-" />
<span slot="tooltip">{$_('toolbar.routing.tooltip')}</span>
</ToolbarItem>
<ToolbarItem tool={Tool.WAYPOINT}>
<MapPin slot="icon" size="18" />
<span slot="tooltip">{$_('toolbar.waypoint.tooltip')}</span>
</ToolbarItem>
<ToolbarItem tool={Tool.SCISSORS}>
<Scissors slot="icon" size="18" />
<span slot="tooltip">{$_('toolbar.scissors.tooltip')}</span>
</ToolbarItem>
<ToolbarItem tool={Tool.TIME}>
<CalendarClock slot="icon" size="18" />
<span slot="tooltip">{$_('toolbar.time.tooltip')}</span>
</ToolbarItem>
<ToolbarItem tool={Tool.MERGE}>
<Group slot="icon" size="18" />
<span slot="tooltip">{$_('toolbar.merge.tooltip')}</span>
</ToolbarItem>
<ToolbarItem tool={Tool.EXTRACT}>
<Ungroup slot="icon" size="18" />
<span slot="tooltip">{$_('toolbar.extract.tooltip')}</span>
</ToolbarItem>
<ToolbarItem tool={Tool.REDUCE}>
<Filter slot="icon" size="18" />
<span slot="tooltip">{$_('toolbar.reduce.tooltip')}</span>
</ToolbarItem>
<ToolbarItem tool={Tool.CLEAN}>
<SquareDashedMousePointer slot="icon" size="18" />
<span slot="tooltip">{$_('toolbar.clean.tooltip')}</span>
</ToolbarItem>
</div>
<ToolbarItemMenu />
</div>

View File

@@ -1,5 +1,6 @@
<script lang="ts">
import { Tool, currentTool } from '$lib/stores';
import { settings } from '$lib/db';
import { flyAndScale } from '$lib/utils';
import * as Card from '$lib/components/ui/card';
import Routing from '$lib/components/toolbar/tools/routing/Routing.svelte';
@@ -14,6 +15,8 @@
import { onMount } from 'svelte';
import mapboxgl from 'mapbox-gl';
const { minimizeRoutingMenu } = settings;
let popupElement: HTMLElement;
let popup: mapboxgl.Popup;
@@ -36,7 +39,7 @@
<Card.Root class="rounded-md border-none">
<Card.Content class="p-2.5">
{#if $currentTool === Tool.ROUTING}
<Routing {popup} {popupElement} />
<Routing {popup} {popupElement} bind:minimized={$minimizeRoutingMenu} />
{:else if $currentTool === Tool.SCISSORS}
<Scissors />
{:else if $currentTool === Tool.WAYPOINT}

View File

@@ -36,13 +36,15 @@
import { onDestroy, onMount } from 'svelte';
import { TrackPoint } from 'gpx';
export let popup: mapboxgl.Popup;
export let popupElement: HTMLElement;
export let minimized = false;
export let minimizable = true;
export let popup: mapboxgl.Popup | undefined = undefined;
export let popupElement: HTMLElement | undefined = undefined;
let selectedItem: ListItem | null = null;
const { minimizeRoutingMenu, privateRoads, routing } = settings;
const { privateRoads, routing } = settings;
$: if ($map) {
$: if ($map && popup && popupElement) {
// remove controls for deleted files
routingControls.forEach((controls, fileId) => {
if (!$fileObservers.has(fileId)) {
@@ -93,9 +95,9 @@
});
</script>
{#if $minimizeRoutingMenu}
{#if minimized}
<div class="-m-1.5 -mb-2">
<Button variant="ghost" class="px-1 h-[26px]" on:click={() => ($minimizeRoutingMenu = false)}>
<Button variant="ghost" class="px-1 h-[26px]" on:click={() => (minimized = false)}>
<SquareArrowOutDownRight size="18" />
</Button>
</div>
@@ -207,9 +209,11 @@
<div>{$_('toolbar.routing.help')}</div>
{/if}
</Help>
<Button variant="ghost" class="px-1 h-6" on:click={() => ($minimizeRoutingMenu = true)}>
<SquareArrowUpLeft size="18" />
</Button>
{#if minimizable}
<Button variant="ghost" class="px-1 h-6" on:click={() => (minimized = true)}>
<SquareArrowUpLeft size="18" />
</Button>
{/if}
</div>
</div>
{/if}