mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2025-09-01 08:12:32 +00:00
sonner on routing error, and dark mode
This commit is contained in:
18
website/package-lock.json
generated
18
website/package-lock.json
generated
@@ -15,8 +15,10 @@
|
||||
"gpx": "file:../gpx",
|
||||
"lucide-svelte": "^0.365.0",
|
||||
"mapbox-gl": "^3.2.0",
|
||||
"mode-watcher": "^0.3.0",
|
||||
"sortablejs": "^1.15.2",
|
||||
"svelte-i18n": "^4.0.0",
|
||||
"svelte-sonner": "^0.3.22",
|
||||
"tailwind-merge": "^2.2.2",
|
||||
"tailwind-variants": "^0.2.1"
|
||||
},
|
||||
@@ -3939,6 +3941,14 @@
|
||||
"mkdirp": "bin/cmd.js"
|
||||
}
|
||||
},
|
||||
"node_modules/mode-watcher": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/mode-watcher/-/mode-watcher-0.3.0.tgz",
|
||||
"integrity": "sha512-k8jjuTx94HaaRKWO6JDf8wL761hFatrTIHJKl+E+3JWcnv+GnMBH062zcLsy0lbCI3n7RZxxHaWi66auFnUO4g==",
|
||||
"peerDependencies": {
|
||||
"svelte": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/mri": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
|
||||
@@ -5745,6 +5755,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/svelte-sonner": {
|
||||
"version": "0.3.22",
|
||||
"resolved": "https://registry.npmjs.org/svelte-sonner/-/svelte-sonner-0.3.22.tgz",
|
||||
"integrity": "sha512-1AEBl7rTP4oeMAmBmkcvoHNOwB8gPzz73RYApcY8pyDwbjBewU8ATnXV8N42omV1sQvtSX/X0o5A1nfkN3T6cg==",
|
||||
"peerDependencies": {
|
||||
"svelte": ">=3 <5"
|
||||
}
|
||||
},
|
||||
"node_modules/tabbable": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz",
|
||||
|
@@ -48,8 +48,10 @@
|
||||
"gpx": "file:../gpx",
|
||||
"lucide-svelte": "^0.365.0",
|
||||
"mapbox-gl": "^3.2.0",
|
||||
"mode-watcher": "^0.3.0",
|
||||
"sortablejs": "^1.15.2",
|
||||
"svelte-i18n": "^4.0.0",
|
||||
"svelte-sonner": "^0.3.22",
|
||||
"tailwind-merge": "^2.2.2",
|
||||
"tailwind-variants": "^0.2.1"
|
||||
}
|
||||
|
@@ -7,6 +7,7 @@
|
||||
import Menu from '$lib/components/Menu.svelte';
|
||||
import Toolbar from '$lib/components/toolbar/Toolbar.svelte';
|
||||
import LayerControl from '$lib/components/layer-control/LayerControl.svelte';
|
||||
import { Toaster } from '$lib/components/ui/sonner';
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col w-screen h-screen">
|
||||
@@ -17,9 +18,18 @@
|
||||
<LayerControl />
|
||||
<GPXLayers />
|
||||
<FileList />
|
||||
<Toaster richColors />
|
||||
</div>
|
||||
<div class="h-48 flex flex-row gap-2 overflow-hidden">
|
||||
<GPXData />
|
||||
<ElevationProfile />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
div :global(.toaster.group) {
|
||||
@apply absolute;
|
||||
@apply right-2;
|
||||
--offset: 50px !important;
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { fileOrder, files, getFileIndex, selectedFiles, selectFiles } from '$lib/stores';
|
||||
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { ScrollArea } from '$lib/components/ui/scroll-area/index';
|
||||
import Sortable from 'sortablejs/Sortable';
|
||||
|
||||
@@ -9,8 +10,8 @@
|
||||
import { afterUpdate, onMount } from 'svelte';
|
||||
import { get } from 'svelte/store';
|
||||
|
||||
let tabs: HTMLDivElement;
|
||||
let buttons: HTMLButtonElement[] = [];
|
||||
let container: HTMLDivElement;
|
||||
let buttons: HTMLDivElement[] = [];
|
||||
let sortable: Sortable;
|
||||
|
||||
function selectFile(file: GPXFile) {
|
||||
@@ -60,7 +61,7 @@
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
sortable = Sortable.create(tabs, {
|
||||
sortable = Sortable.create(container, {
|
||||
forceAutoScrollFallback: true,
|
||||
multiDrag: true,
|
||||
multiDragKey: 'shift',
|
||||
@@ -125,22 +126,29 @@
|
||||
|
||||
<div class="h-10 -translate-y-10 w-full pointer-events-none">
|
||||
<ScrollArea orientation="horizontal" class="w-full h-full" scrollbarXClasses="h-2">
|
||||
<div bind:this={tabs} class="flex flex-row gap-1">
|
||||
<div bind:this={container} class="flex flex-row gap-1">
|
||||
{#each $files as file, index}
|
||||
<button
|
||||
<div
|
||||
bind:this={buttons[index]}
|
||||
data-id={index}
|
||||
class="my-1 px-1.5 py-1 rounded bg-secondary hover:bg-gray-200 shadow-none first:ml-1 last:mr-1 pointer-events-auto"
|
||||
class="pointer-events-auto first:ml-1 last:mr-1 mb-1 bg-transparent"
|
||||
>
|
||||
{get(file).metadata.name}
|
||||
</button>
|
||||
<Button variant="outline" class="h-9 px-1.5 py-1 border-none shadow-md">
|
||||
{get(file).metadata.name}
|
||||
</Button>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
div :global(.sortable-selected) {
|
||||
@apply bg-background shadow;
|
||||
div :global(button) {
|
||||
@apply bg-accent;
|
||||
@apply hover:bg-background;
|
||||
}
|
||||
|
||||
div :global(.sortable-selected > button) {
|
||||
@apply bg-background;
|
||||
}
|
||||
</style>
|
||||
|
@@ -162,11 +162,11 @@
|
||||
}
|
||||
|
||||
div :global(.mapboxgl-ctrl-bottom-left) {
|
||||
@apply bottom-9;
|
||||
@apply bottom-[42px];
|
||||
}
|
||||
|
||||
div :global(.mapboxgl-ctrl-bottom-right) {
|
||||
@apply bottom-9;
|
||||
@apply bottom-[42px];
|
||||
}
|
||||
|
||||
div :global(.mapboxgl-popup) {
|
||||
|
@@ -17,10 +17,29 @@
|
||||
settings
|
||||
} from '$lib/stores';
|
||||
|
||||
import { mode, resetMode, setMode } from 'mode-watcher';
|
||||
|
||||
import { _ } from 'svelte-i18n';
|
||||
import { derived, get } from 'svelte/store';
|
||||
|
||||
let showDistanceMarkers = false;
|
||||
let showDirectionMarkers = false;
|
||||
|
||||
let currentMode = derived(mode, ($mode) => {
|
||||
if (!$mode) {
|
||||
return 'system';
|
||||
} else {
|
||||
return $mode;
|
||||
}
|
||||
});
|
||||
|
||||
$: if ($settings.mode !== get(currentMode)) {
|
||||
if ($settings.mode === 'system') {
|
||||
resetMode();
|
||||
} else {
|
||||
setMode($settings.mode);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="absolute top-2 left-0 right-0 z-20 flex flex-row justify-center pointer-events-none">
|
||||
@@ -126,6 +145,17 @@
|
||||
</Menubar.SubContent>
|
||||
</Menubar.Sub>
|
||||
<Menubar.Separator />
|
||||
<Menubar.Sub>
|
||||
<Menubar.SubTrigger inset>{$_('menu.mode')}</Menubar.SubTrigger>
|
||||
<Menubar.SubContent>
|
||||
<Menubar.RadioGroup bind:value={$settings.mode}>
|
||||
<Menubar.RadioItem value="light">{$_('menu.light')}</Menubar.RadioItem>
|
||||
<Menubar.RadioItem value="dark">{$_('menu.dark')}</Menubar.RadioItem>
|
||||
<Menubar.RadioItem value="system">{$_('menu.system')}</Menubar.RadioItem>
|
||||
</Menubar.RadioGroup>
|
||||
</Menubar.SubContent>
|
||||
</Menubar.Sub>
|
||||
<Menubar.Separator />
|
||||
<Menubar.CheckboxItem bind:checked={showDistanceMarkers}>
|
||||
{$_('menu.distance_markers')}
|
||||
</Menubar.CheckboxItem>
|
||||
|
@@ -38,6 +38,12 @@ async function getRoute(points: Coordinates[], brouterProfile: string, privateRo
|
||||
let url = `https://routing.gpx.studio?lonlats=${points.map(point => `${point.lon.toFixed(8)},${point.lat.toFixed(8)}`).join('|')}&profile=${brouterProfile + (privateRoads ? '-private' : '')}&format=geojson&alternativeidx=0`;
|
||||
|
||||
let response = await fetch(url);
|
||||
|
||||
// Check if the response is ok
|
||||
if (!response.ok) {
|
||||
throw new Error(`${await response.text()}`);
|
||||
}
|
||||
|
||||
let geojson = await response.json();
|
||||
|
||||
let route: TrackPoint[] = [];
|
||||
@@ -66,7 +72,7 @@ async function getRoute(points: Coordinates[], brouterProfile: string, privateRo
|
||||
coordinates[i][1] == Number(messages[messageIdx][latIdx]) / 1000000) {
|
||||
messageIdx++;
|
||||
|
||||
if (messageIdx == messages.length) surface = "missing";
|
||||
if (messageIdx == messages.length) surface = "unknown";
|
||||
else surface = getSurface(messages[messageIdx][tagIdx]);
|
||||
}
|
||||
}
|
||||
|
@@ -3,7 +3,11 @@ import { get, type Writable } from "svelte/store";
|
||||
import { computeAnchorPoints, type SimplifiedTrackPoint } from "./Simplify";
|
||||
import mapboxgl from "mapbox-gl";
|
||||
import { route } from "./Routing";
|
||||
import { applyToFileElement, applyToFileStore } from "$lib/stores";
|
||||
import { applyToFileElement } from "$lib/stores";
|
||||
|
||||
import { toast } from "svelte-sonner";
|
||||
|
||||
import { _ } from "svelte-i18n";
|
||||
|
||||
export class RoutingControls {
|
||||
map: mapboxgl.Map;
|
||||
@@ -100,7 +104,7 @@ export class RoutingControls {
|
||||
|
||||
createMarker(anchor: SimplifiedTrackPoint) {
|
||||
let element = document.createElement('div');
|
||||
element.className = `h-3 w-3 rounded-full bg-background border-2 border-black cursor-pointer`;
|
||||
element.className = `h-3 w-3 rounded-full bg-white border-2 border-black cursor-pointer`;
|
||||
|
||||
let marker = new mapboxgl.Marker({
|
||||
draggable: true,
|
||||
@@ -380,7 +384,14 @@ export class RoutingControls {
|
||||
try {
|
||||
response = await route(targetCoordinates);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
console.log(e);
|
||||
if (e.message.includes('from-position not mapped in existing datafile')) {
|
||||
toast.error(get(_)("toolbar.routing.error.from"));
|
||||
} else if (e.message.includes('to-position not mapped in existing datafile')) {
|
||||
toast.error(get(_)("toolbar.routing.error.to"));
|
||||
} else {
|
||||
toast.error(e.message);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
1
website/src/lib/components/ui/sonner/index.ts
Normal file
1
website/src/lib/components/ui/sonner/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default as Toaster } from "./sonner.svelte";
|
20
website/src/lib/components/ui/sonner/sonner.svelte
Normal file
20
website/src/lib/components/ui/sonner/sonner.svelte
Normal file
@@ -0,0 +1,20 @@
|
||||
<script lang="ts">
|
||||
import { Toaster as Sonner, type ToasterProps as SonnerProps } from "svelte-sonner";
|
||||
import { mode } from "mode-watcher";
|
||||
|
||||
type $$Props = SonnerProps;
|
||||
</script>
|
||||
|
||||
<Sonner
|
||||
theme={$mode}
|
||||
class="toaster group"
|
||||
toastOptions={{
|
||||
classes: {
|
||||
toast: "group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
|
||||
description: "group-[.toast]:text-muted-foreground",
|
||||
actionButton: "group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
|
||||
cancelButton: "group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
|
||||
},
|
||||
}}
|
||||
{...$$restProps}
|
||||
/>
|
@@ -12,6 +12,7 @@ export const settings = writable<{ [key: string]: any }>({
|
||||
distanceUnits: 'metric',
|
||||
velocityUnits: 'speed',
|
||||
temperatureUnits: 'celsius',
|
||||
mode: 'system'
|
||||
});
|
||||
export enum Tool {
|
||||
ROUTING
|
||||
|
@@ -20,6 +20,10 @@
|
||||
"temperature_units": "Temperature units",
|
||||
"celsius": "Celsius",
|
||||
"fahrenheit": "Fahrenheit",
|
||||
"mode": "Mode",
|
||||
"light": "Light",
|
||||
"dark": "Dark",
|
||||
"system": "System",
|
||||
"distance_markers": "Show distance markers",
|
||||
"direction_markers": "Show direction markers",
|
||||
"about": "About",
|
||||
@@ -42,6 +46,10 @@
|
||||
"motorcycle": "Motorcycle",
|
||||
"water": "Water",
|
||||
"railway": "Railway"
|
||||
},
|
||||
"error": {
|
||||
"from": "The start point is too far from the nearest road",
|
||||
"to": "The end point is too far from the nearest road"
|
||||
}
|
||||
},
|
||||
"time_tooltip": "Change the time and speed data",
|
||||
|
@@ -1,5 +1,9 @@
|
||||
<script>
|
||||
import '../app.pcss';
|
||||
|
||||
import { ModeWatcher } from 'mode-watcher';
|
||||
</script>
|
||||
|
||||
<ModeWatcher />
|
||||
|
||||
<slot />
|
||||
|
Reference in New Issue
Block a user