From c6919f518a5a7332c97702a03e60b175f557d27d Mon Sep 17 00:00:00 2001 From: mbof Date: Fri, 23 Aug 2024 07:18:36 -0700 Subject: [PATCH] * Change icon to Maximize * Use default animation * Rename feature to "Fly to" * Move logic to stores so that it can be reused in the main menu (and eventually a keyboard shortcut) --- website/src/lib/components/Menu.svelte | 14 +- .../file-list/FileListNodeLabel.svelte | 39 +- website/src/lib/stores.ts | 622 +++++++----- website/src/locales/en.json | 951 +++++++++--------- 4 files changed, 843 insertions(+), 783 deletions(-) diff --git a/website/src/lib/components/Menu.svelte b/website/src/lib/components/Menu.svelte index 5ca99121..4b539301 100644 --- a/website/src/lib/components/Menu.svelte +++ b/website/src/lib/components/Menu.svelte @@ -41,7 +41,8 @@ FileStack, FileX, BookOpenText, - ChartArea + ChartArea, + Maximize } from 'lucide-svelte'; import { @@ -54,7 +55,9 @@ editMetadata, editStyle, exportState, - ExportState + ExportState, + flyToBounds, + gpxStatistics } from '$lib/stores'; import { copied, @@ -222,6 +225,13 @@ {$_('menu.style.button')} + flyToBounds($gpxStatistics.global.bounds, $map)} + > + + {$_('menu.fly_to_selection')} + { if ($allHidden) { diff --git a/website/src/lib/components/file-list/FileListNodeLabel.svelte b/website/src/lib/components/file-list/FileListNodeLabel.svelte index ba0355be..7f8137dd 100644 --- a/website/src/lib/components/file-list/FileListNodeLabel.svelte +++ b/website/src/lib/components/file-list/FileListNodeLabel.svelte @@ -15,7 +15,7 @@ EyeOff, ClipboardCopy, ClipboardPaste, - Maximize2, + Maximize, Scissors, FileStack, FileX @@ -45,9 +45,10 @@ editMetadata, editStyle, embedding, + flyToBounds, gpxLayers, - map, - updateTargetMapBounds + gpxStatistics, + map } from '$lib/stores'; import { GPXTreeElement, @@ -226,30 +227,14 @@ {$_('menu.style.button')} {/if} - {#if node instanceof GPXTreeElement} - { - const targetBounds = node.getStatistics().global.bounds; - const mapBoxBounds = new mapboxgl.LngLatBounds([ - [targetBounds.northEast.lon, targetBounds.northEast.lat], - [targetBounds.southWest.lon, targetBounds.southWest.lat] - ]); - $map?.fitBounds(mapBoxBounds, { - padding: 80, - linear: true, - duration: 1000, - easing: (t) => { - return t < 0.5 - ? (1 - Math.sqrt(1 - Math.pow(2 * t, 2))) / 2 - : (Math.sqrt(1 - Math.pow(-2 * t + 2, 2)) + 1) / 2; - } - }); - }} - > - - {$_('menu.fit_map')} - - {/if} + { + flyToBounds($gpxStatistics.global.bounds, $map); + }} + > + + {$_('menu.fly_to')} + { if ($allHidden) { diff --git a/website/src/lib/stores.ts b/website/src/lib/stores.ts index 397ae336..9490a893 100644 --- a/website/src/lib/stores.ts +++ b/website/src/lib/stores.ts @@ -6,8 +6,21 @@ import { tick } from 'svelte'; import { _ } from 'svelte-i18n'; import type { GPXLayer } from '$lib/components/gpx-layer/GPXLayer'; import { dbUtils, fileObservers, getFile, getStatistics, settings } from './db'; -import { addSelectItem, applyToOrderedSelectedItemsFromFile, selectFile, selectItem, selection } from '$lib/components/file-list/Selection'; -import { ListFileItem, ListItem, ListTrackItem, ListTrackSegmentItem, ListWaypointItem, ListWaypointsItem } from '$lib/components/file-list/FileList'; +import { + addSelectItem, + applyToOrderedSelectedItemsFromFile, + selectFile, + selectItem, + selection +} from '$lib/components/file-list/Selection'; +import { + ListFileItem, + ListItem, + ListTrackItem, + ListTrackSegmentItem, + ListWaypointItem, + ListWaypointsItem +} from '$lib/components/file-list/FileList'; import type { RoutingControls } from '$lib/components/toolbar/tools/routing/RoutingControls'; import { SplitType } from '$lib/components/toolbar/tools/scissors/Scissors.svelte'; @@ -18,369 +31,420 @@ export const embedding = writable(false); export const selectFiles = writable<{ [key: string]: (fileId?: string) => void }>({}); export const gpxStatistics: Writable = writable(new GPXStatistics()); -export const slicedGPXStatistics: Writable<[GPXStatistics, number, number] | undefined> = writable(undefined); +export const slicedGPXStatistics: Writable<[GPXStatistics, number, number] | undefined> = + writable(undefined); export function updateGPXData() { - let statistics = new GPXStatistics(); - applyToOrderedSelectedItemsFromFile((fileId, level, items) => { - let stats = getStatistics(fileId); - if (stats) { - let first = true; - items.forEach((item) => { - if (!(item instanceof ListWaypointItem || item instanceof ListWaypointsItem) || first) { - statistics.mergeWith(stats.getStatisticsFor(item)); - first = false; - } - }); - } - }, false); - gpxStatistics.set(statistics); + let statistics = new GPXStatistics(); + applyToOrderedSelectedItemsFromFile((fileId, level, items) => { + let stats = getStatistics(fileId); + if (stats) { + let first = true; + items.forEach((item) => { + if (!(item instanceof ListWaypointItem || item instanceof ListWaypointsItem) || first) { + statistics.mergeWith(stats.getStatisticsFor(item)); + first = false; + } + }); + } + }, false); + gpxStatistics.set(statistics); } let unsubscribes: Map void> = new Map(); -selection.subscribe(($selection) => { // Maintain up-to-date statistics for the current selection - updateGPXData(); +selection.subscribe(($selection) => { + // Maintain up-to-date statistics for the current selection + updateGPXData(); - while (unsubscribes.size > 0) { - let [fileId, unsubscribe] = unsubscribes.entries().next().value; - unsubscribe(); - unsubscribes.delete(fileId); - } + while (unsubscribes.size > 0) { + let [fileId, unsubscribe] = unsubscribes.entries().next().value; + unsubscribe(); + unsubscribes.delete(fileId); + } - $selection.forEach((item) => { - let fileId = item.getFileId(); - if (!unsubscribes.has(fileId)) { - let fileObserver = get(fileObservers).get(fileId); - if (fileObserver) { - let first = true; - unsubscribes.set(fileId, fileObserver.subscribe(() => { - if (first) first = false; - else updateGPXData(); - })); - } - } - }); + $selection.forEach((item) => { + let fileId = item.getFileId(); + if (!unsubscribes.has(fileId)) { + let fileObserver = get(fileObservers).get(fileId); + if (fileObserver) { + let first = true; + unsubscribes.set( + fileId, + fileObserver.subscribe(() => { + if (first) first = false; + else updateGPXData(); + }) + ); + } + } + }); }); gpxStatistics.subscribe(() => { - slicedGPXStatistics.set(undefined); + slicedGPXStatistics.set(undefined); }); const targetMapBounds = writable({ - bounds: new mapboxgl.LngLatBounds([180, 90, -180, -90]), - count: 0, - total: -1 + bounds: new mapboxgl.LngLatBounds([180, 90, -180, -90]), + count: 0, + total: -1 }); -derived([targetMapBounds, map], x => x).subscribe(([bounds, $map]) => { - if ($map === null || bounds.count !== bounds.total || (bounds.bounds.getSouth() === 90 && bounds.bounds.getWest() === 180 && bounds.bounds.getNorth() === -90 && bounds.bounds.getEast() === -180)) { - return; - } +derived([targetMapBounds, map], (x) => x).subscribe(([bounds, $map]) => { + if ( + $map === null || + bounds.count !== bounds.total || + (bounds.bounds.getSouth() === 90 && + bounds.bounds.getWest() === 180 && + bounds.bounds.getNorth() === -90 && + bounds.bounds.getEast() === -180) + ) { + return; + } - let currentBounds = $map.getBounds(); - if (bounds.count !== get(fileObservers).size && currentBounds) { - // There are other files on the map + let currentBounds = $map.getBounds(); + if (bounds.count !== get(fileObservers).size && currentBounds) { + // There are other files on the map - if (currentBounds.contains(bounds.bounds.getSouthEast()) && currentBounds.contains(bounds.bounds.getNorthWest())) { - return; - } + if ( + currentBounds.contains(bounds.bounds.getSouthEast()) && + currentBounds.contains(bounds.bounds.getNorthWest()) + ) { + return; + } - bounds.bounds.extend(currentBounds.getSouthWest()); - bounds.bounds.extend(currentBounds.getNorthEast()); - } + bounds.bounds.extend(currentBounds.getSouthWest()); + bounds.bounds.extend(currentBounds.getNorthEast()); + } - $map.fitBounds(bounds.bounds, { - padding: 80, - linear: true, - easing: () => 1 - }); + $map.fitBounds(bounds.bounds, { padding: 80, linear: true, easing: () => 1 }); }); - export function initTargetMapBounds(total: number) { - targetMapBounds.set({ - bounds: new mapboxgl.LngLatBounds([180, 90, -180, -90]), - count: 0, - total: total - }); + targetMapBounds.set({ + bounds: new mapboxgl.LngLatBounds([180, 90, -180, -90]), + count: 0, + total: total + }); } -export function updateTargetMapBounds(bounds: { - southWest: Coordinates, - northEast: Coordinates -}) { - if (bounds.southWest.lat == 90 && bounds.southWest.lon == 180 && bounds.northEast.lat == -90 && bounds.northEast.lon == -180) { // Avoid update for empty (new) files - targetMapBounds.update((target) => { - target.count += 1; - return target; - }); - return; - } +export function updateTargetMapBounds(bounds: { southWest: Coordinates; northEast: Coordinates }) { + if ( + bounds.southWest.lat == 90 && + bounds.southWest.lon == 180 && + bounds.northEast.lat == -90 && + bounds.northEast.lon == -180 + ) { + // Avoid update for empty (new) files + targetMapBounds.update((target) => { + target.count += 1; + return target; + }); + return; + } - targetMapBounds.update((target) => { - target.bounds.extend(bounds.southWest); - target.bounds.extend(bounds.northEast); - target.count += 1; - return target; - }); + targetMapBounds.update((target) => { + target.bounds.extend(bounds.southWest); + target.bounds.extend(bounds.northEast); + target.count += 1; + return target; + }); +} + +export function flyToBounds( + bounds: { southWest: Coordinates; northEast: Coordinates }, + map: mapboxgl.Map | null +) { + const mapBoxBounds = new mapboxgl.LngLatBounds([ + [bounds.northEast.lon, bounds.northEast.lat], + [bounds.southWest.lon, bounds.southWest.lat] + ]); + map?.fitBounds(mapBoxBounds, { + padding: 80 + }); } export const gpxLayers: Map = new Map(); export const routingControls: Map = new Map(); export enum Tool { - ROUTING, - WAYPOINT, - SCISSORS, - TIME, - MERGE, - EXTRACT, - REDUCE, - CLEAN + ROUTING, + WAYPOINT, + SCISSORS, + TIME, + MERGE, + EXTRACT, + REDUCE, + CLEAN } export const currentTool = writable(null); export const splitAs = writable(SplitType.FILES); export const streetViewEnabled = writable(false); export function newGPXFile() { - const newFileName = get(_)("menu.new_file"); + const newFileName = get(_)('menu.new_file'); - let file = new GPXFile(); + let file = new GPXFile(); - let maxNewFileNumber = 0; - get(fileObservers).forEach((f) => { - let file = get(f)?.file; - if (file && file.metadata.name && file.metadata.name.startsWith(newFileName)) { - let number = parseInt(file.metadata.name.split(' ').pop() ?? '0'); - if (!isNaN(number) && number > maxNewFileNumber) { - maxNewFileNumber = number; - } - } - }); + let maxNewFileNumber = 0; + get(fileObservers).forEach((f) => { + let file = get(f)?.file; + if (file && file.metadata.name && file.metadata.name.startsWith(newFileName)) { + let number = parseInt(file.metadata.name.split(' ').pop() ?? '0'); + if (!isNaN(number) && number > maxNewFileNumber) { + maxNewFileNumber = number; + } + } + }); - file.metadata.name = `${newFileName} ${maxNewFileNumber + 1}`; + file.metadata.name = `${newFileName} ${maxNewFileNumber + 1}`; - return file; + return file; } export function createFile() { - let file = newGPXFile(); + let file = newGPXFile(); - dbUtils.add(file); + dbUtils.add(file); - selectFileWhenLoaded(file._data.id); - currentTool.set(Tool.ROUTING); + selectFileWhenLoaded(file._data.id); + currentTool.set(Tool.ROUTING); } export function triggerFileInput() { - const input = document.createElement('input'); - input.type = 'file'; - input.accept = '.gpx'; - input.multiple = true; - input.className = 'hidden'; - input.onchange = () => { - if (input.files) { - loadFiles(input.files); - } - }; - input.click(); + const input = document.createElement('input'); + input.type = 'file'; + input.accept = '.gpx'; + input.multiple = true; + input.className = 'hidden'; + input.onchange = () => { + if (input.files) { + loadFiles(input.files); + } + }; + input.click(); } export async function loadFiles(list: FileList | File[]) { - let files = []; - for (let i = 0; i < list.length; i++) { - let file = await loadFile(list[i]); - if (file) { - files.push(file); - } - } + let files = []; + for (let i = 0; i < list.length; i++) { + let file = await loadFile(list[i]); + if (file) { + files.push(file); + } + } - initTargetMapBounds(list.length); + initTargetMapBounds(list.length); - dbUtils.addMultiple(files); + dbUtils.addMultiple(files); - selectFileWhenLoaded(files[0]._data.id); + selectFileWhenLoaded(files[0]._data.id); } export async function loadFile(file: File): Promise { - let result = await new Promise((resolve) => { - const reader = new FileReader(); - reader.onload = () => { - let data = reader.result?.toString() ?? null; - if (data) { - let gpx = parseGPX(data); - if (gpx.metadata === undefined) { - gpx.metadata = { name: file.name.split('.').slice(0, -1).join('.') }; - } else if (gpx.metadata.name === undefined) { - gpx.metadata['name'] = file.name.split('.').slice(0, -1).join('.'); - } - resolve(gpx); - } else { - resolve(null); - } - }; - reader.readAsText(file); - }); - return result; + let result = await new Promise((resolve) => { + const reader = new FileReader(); + reader.onload = () => { + let data = reader.result?.toString() ?? null; + if (data) { + let gpx = parseGPX(data); + if (gpx.metadata === undefined) { + gpx.metadata = { name: file.name.split('.').slice(0, -1).join('.') }; + } else if (gpx.metadata.name === undefined) { + gpx.metadata['name'] = file.name.split('.').slice(0, -1).join('.'); + } + resolve(gpx); + } else { + resolve(null); + } + }; + reader.readAsText(file); + }); + return result; } export function selectFileWhenLoaded(fileId: string) { - const unsubscribe = fileObservers.subscribe((files) => { - if (files.has(fileId)) { - tick().then(() => { - selectFile(fileId); - }); - unsubscribe(); - } - }); + const unsubscribe = fileObservers.subscribe((files) => { + if (files.has(fileId)) { + tick().then(() => { + selectFile(fileId); + }); + unsubscribe(); + } + }); } export function updateSelectionFromKey(down: boolean, shift: boolean) { - let selected = get(selection).getSelected(); - if (selected.length === 0) { - return; - } + let selected = get(selection).getSelected(); + if (selected.length === 0) { + return; + } - let next: ListItem | undefined = undefined; - if (selected[0] instanceof ListFileItem) { - let order = get(fileOrder); - let limitIndex: number | undefined = undefined; - selected.forEach((item) => { - let index = order.indexOf(item.getFileId()); - if (limitIndex === undefined || (down && index > limitIndex) || (!down && index < limitIndex)) { - limitIndex = index; - } - }); + let next: ListItem | undefined = undefined; + if (selected[0] instanceof ListFileItem) { + let order = get(fileOrder); + let limitIndex: number | undefined = undefined; + selected.forEach((item) => { + let index = order.indexOf(item.getFileId()); + if ( + limitIndex === undefined || + (down && index > limitIndex) || + (!down && index < limitIndex) + ) { + limitIndex = index; + } + }); - if (limitIndex !== undefined) { - let nextIndex = down ? limitIndex + 1 : limitIndex - 1; + if (limitIndex !== undefined) { + let nextIndex = down ? limitIndex + 1 : limitIndex - 1; - while (true) { - if (nextIndex < 0) { - nextIndex = order.length - 1; - } else if (nextIndex >= order.length) { - nextIndex = 0; - } + while (true) { + if (nextIndex < 0) { + nextIndex = order.length - 1; + } else if (nextIndex >= order.length) { + nextIndex = 0; + } - if (nextIndex === limitIndex) { - break; - } + if (nextIndex === limitIndex) { + break; + } - next = new ListFileItem(order[nextIndex]); - if (!get(selection).has(next)) { - break; - } + next = new ListFileItem(order[nextIndex]); + if (!get(selection).has(next)) { + break; + } - nextIndex += down ? 1 : -1; - } - } - } else if (selected[0] instanceof ListTrackItem && selected[selected.length - 1] instanceof ListTrackItem) { - let fileId = selected[0].getFileId(); - let file = getFile(fileId); - if (file) { - let numberOfTracks = file.trk.length; - let trackIndex = down ? selected[selected.length - 1].getTrackIndex() : selected[0].getTrackIndex(); - if (down && trackIndex < numberOfTracks - 1) { - next = new ListTrackItem(fileId, trackIndex + 1); - } else if (!down && trackIndex > 0) { - next = new ListTrackItem(fileId, trackIndex - 1); - } - } - } else if (selected[0] instanceof ListTrackSegmentItem && selected[selected.length - 1] instanceof ListTrackSegmentItem) { - let fileId = selected[0].getFileId(); - let file = getFile(fileId); - if (file) { - let trackIndex = selected[0].getTrackIndex(); - let numberOfSegments = file.trk[trackIndex].trkseg.length; - let segmentIndex = down ? selected[selected.length - 1].getSegmentIndex() : selected[0].getSegmentIndex(); - if (down && segmentIndex < numberOfSegments - 1) { - next = new ListTrackSegmentItem(fileId, trackIndex, segmentIndex + 1); - } else if (!down && segmentIndex > 0) { - next = new ListTrackSegmentItem(fileId, trackIndex, segmentIndex - 1); - } - } - } else if (selected[0] instanceof ListWaypointItem && selected[selected.length - 1] instanceof ListWaypointItem) { - let fileId = selected[0].getFileId(); - let file = getFile(fileId); - if (file) { - let numberOfWaypoints = file.wpt.length; - let waypointIndex = down ? selected[selected.length - 1].getWaypointIndex() : selected[0].getWaypointIndex(); - if (down && waypointIndex < numberOfWaypoints - 1) { - next = new ListWaypointItem(fileId, waypointIndex + 1); - } else if (!down && waypointIndex > 0) { - next = new ListWaypointItem(fileId, waypointIndex - 1); - } - } - } + nextIndex += down ? 1 : -1; + } + } + } else if ( + selected[0] instanceof ListTrackItem && + selected[selected.length - 1] instanceof ListTrackItem + ) { + let fileId = selected[0].getFileId(); + let file = getFile(fileId); + if (file) { + let numberOfTracks = file.trk.length; + let trackIndex = down + ? selected[selected.length - 1].getTrackIndex() + : selected[0].getTrackIndex(); + if (down && trackIndex < numberOfTracks - 1) { + next = new ListTrackItem(fileId, trackIndex + 1); + } else if (!down && trackIndex > 0) { + next = new ListTrackItem(fileId, trackIndex - 1); + } + } + } else if ( + selected[0] instanceof ListTrackSegmentItem && + selected[selected.length - 1] instanceof ListTrackSegmentItem + ) { + let fileId = selected[0].getFileId(); + let file = getFile(fileId); + if (file) { + let trackIndex = selected[0].getTrackIndex(); + let numberOfSegments = file.trk[trackIndex].trkseg.length; + let segmentIndex = down + ? selected[selected.length - 1].getSegmentIndex() + : selected[0].getSegmentIndex(); + if (down && segmentIndex < numberOfSegments - 1) { + next = new ListTrackSegmentItem(fileId, trackIndex, segmentIndex + 1); + } else if (!down && segmentIndex > 0) { + next = new ListTrackSegmentItem(fileId, trackIndex, segmentIndex - 1); + } + } + } else if ( + selected[0] instanceof ListWaypointItem && + selected[selected.length - 1] instanceof ListWaypointItem + ) { + let fileId = selected[0].getFileId(); + let file = getFile(fileId); + if (file) { + let numberOfWaypoints = file.wpt.length; + let waypointIndex = down + ? selected[selected.length - 1].getWaypointIndex() + : selected[0].getWaypointIndex(); + if (down && waypointIndex < numberOfWaypoints - 1) { + next = new ListWaypointItem(fileId, waypointIndex + 1); + } else if (!down && waypointIndex > 0) { + next = new ListWaypointItem(fileId, waypointIndex - 1); + } + } + } - if (next && (!get(selection).has(next) || !shift)) { - if (shift) { - addSelectItem(next); - } else { - selectItem(next); - } - } + if (next && (!get(selection).has(next) || !shift)) { + if (shift) { + addSelectItem(next); + } else { + selectItem(next); + } + } } async function exportFiles(fileIds: string[], exclude: string[]) { - for (let fileId of fileIds) { - let file = getFile(fileId); - if (file) { - exportFile(file, exclude); - await new Promise(resolve => setTimeout(resolve, 200)); - } - } + for (let fileId of fileIds) { + let file = getFile(fileId); + if (file) { + exportFile(file, exclude); + await new Promise((resolve) => setTimeout(resolve, 200)); + } + } } export function exportSelectedFiles(exclude: string[]) { - let fileIds: string[] = []; - applyToOrderedSelectedItemsFromFile(async (fileId, level, items) => { - fileIds.push(fileId); - }); - exportFiles(fileIds, exclude); + let fileIds: string[] = []; + applyToOrderedSelectedItemsFromFile(async (fileId, level, items) => { + fileIds.push(fileId); + }); + exportFiles(fileIds, exclude); } export function exportAllFiles(exclude: string[]) { - exportFiles(get(fileOrder), exclude); + exportFiles(get(fileOrder), exclude); } export function exportFile(file: GPXFile, exclude: string[]) { - let blob = new Blob([buildGPX(file, exclude)], { type: 'application/gpx+xml' }); - let url = URL.createObjectURL(blob); - let a = document.createElement('a'); - a.href = url; - a.download = file.metadata.name + '.gpx'; - a.click(); - URL.revokeObjectURL(url); + let blob = new Blob([buildGPX(file, exclude)], { type: 'application/gpx+xml' }); + let url = URL.createObjectURL(blob); + let a = document.createElement('a'); + a.href = url; + a.download = file.metadata.name + '.gpx'; + a.click(); + URL.revokeObjectURL(url); } export const allHidden = writable(false); export function updateAllHidden() { - let hidden = true; - applyToOrderedSelectedItemsFromFile((fileId, level, items) => { - let file = getFile(fileId); - if (file) { - for (let item of items) { - if (!hidden) { - return; - } + let hidden = true; + applyToOrderedSelectedItemsFromFile((fileId, level, items) => { + let file = getFile(fileId); + if (file) { + for (let item of items) { + if (!hidden) { + return; + } - if (item instanceof ListFileItem) { - hidden = hidden && (file._data.hidden === true); - } else if (item instanceof ListTrackItem && item.getTrackIndex() < file.trk.length) { - hidden = hidden && (file.trk[item.getTrackIndex()]._data.hidden === true); - } else if (item instanceof ListTrackSegmentItem && item.getTrackIndex() < file.trk.length && item.getSegmentIndex() < file.trk[item.getTrackIndex()].trkseg.length) { - hidden = hidden && (file.trk[item.getTrackIndex()].trkseg[item.getSegmentIndex()]._data.hidden === true); - } else if (item instanceof ListWaypointsItem) { - hidden = hidden && (file._data.hiddenWpt === true); - } else if (item instanceof ListWaypointItem && item.getWaypointIndex() < file.wpt.length) { - hidden = hidden && (file.wpt[item.getWaypointIndex()]._data.hidden === true); - } - } - } - }); - allHidden.set(hidden); + if (item instanceof ListFileItem) { + hidden = hidden && file._data.hidden === true; + } else if (item instanceof ListTrackItem && item.getTrackIndex() < file.trk.length) { + hidden = hidden && file.trk[item.getTrackIndex()]._data.hidden === true; + } else if ( + item instanceof ListTrackSegmentItem && + item.getTrackIndex() < file.trk.length && + item.getSegmentIndex() < file.trk[item.getTrackIndex()].trkseg.length + ) { + hidden = + hidden && + file.trk[item.getTrackIndex()].trkseg[item.getSegmentIndex()]._data.hidden === true; + } else if (item instanceof ListWaypointsItem) { + hidden = hidden && file._data.hiddenWpt === true; + } else if (item instanceof ListWaypointItem && item.getWaypointIndex() < file.wpt.length) { + hidden = hidden && file.wpt[item.getWaypointIndex()]._data.hidden === true; + } + } + } + }); + allHidden.set(hidden); } selection.subscribe(updateAllHidden); @@ -388,8 +452,8 @@ export const editMetadata = writable(false); export const editStyle = writable(false); export enum ExportState { - NONE, - SELECTION, - ALL + NONE, + SELECTION, + ALL } export const exportState = writable(ExportState.NONE); diff --git a/website/src/locales/en.json b/website/src/locales/en.json index 59dc5161..258a26b3 100644 --- a/website/src/locales/en.json +++ b/website/src/locales/en.json @@ -1,476 +1,477 @@ { - "metadata": { - "home_title": "home", - "app_title": "the online GPX file editor", - "embed_title": "the online GPX file editor", - "help_title": "help", - "404_title": "page not found", - "description": "View, edit, and create GPX files online with advanced route planning capabilities and file processing tools, beautiful maps and detailed data visualizations." - }, - "menu": { - "new": "New", - "new_file": "New file", - "new_track": "New track", - "new_segment": "New segment", - "open": "Open...", - "duplicate": "Duplicate", - "close": "Close", - "close_all": "Close all", - "copy": "Copy", - "paste": "Paste", - "cut": "Cut", - "export": "Export...", - "export_all": "Export all...", - "export_options": "Export options", - "support_message": "The tool is free to use, but not free to run. Please consider supporting the website if you use it frequently. Thank you!", - "support_button": "Help keep the website free", - "download_file": "Download file", - "download_files": "Download files", - "edit": "Edit", - "undo": "Undo", - "redo": "Redo", - "delete": "Delete", - "select_all": "Select all", - "view": "View", - "elevation_profile": "Elevation profile", - "vertical_file_view": "Vertical file list", - "switch_basemap": "Switch to previous basemap", - "toggle_overlays": "Toggle overlays", - "toggle_3d": "Toggle 3D", - "settings": "Settings", - "distance_units": "Distance units", - "metric": "Metric", - "imperial": "Imperial", - "velocity_units": "Velocity units", - "temperature_units": "Temperature units", - "celsius": "Celsius", - "fahrenheit": "Fahrenheit", - "language": "Language", - "mode": "Theme", - "system": "System", - "light": "Light", - "dark": "Dark", - "street_view_source": "Street view source", - "mapillary": "Mapillary", - "google": "Google", - "layers": "Map layers...", - "distance_markers": "Distance markers", - "direction_markers": "Direction arrows", - "help": "Help", - "more": "More...", - "donate": "Donate", - "ctrl": "Ctrl", - "click": "Click", - "drag": "Drag", - "metadata": { - "button": "Info...", - "name": "Name", - "description": "Description", - "save": "Save" - }, - "style": { - "button": "Appearance...", - "color": "Color", - "opacity": "Opacity", - "width": "Width" - }, - "hide": "Hide", - "unhide": "Unhide", - "open_in": "Open in", - "fit_map": "Fit on map" - }, - "toolbar": { - "routing": { - "tooltip": "Plan or edit a route", - "activity": "Activity", - "use_routing": "Routing", - "use_routing_tooltip": "Connect anchor points via road network, or in a straight line if disabled", - "allow_private": "Allow private roads", - "reverse": { - "button": "Reverse", - "tooltip": "Reverse the direction of the route" - }, - "route_back_to_start": { - "button": "Back to start", - "tooltip": "Connect the last point of the route with the starting point" - }, - "round_trip": { - "button": "Round trip", - "tooltip": "Return to the starting point by the same route" - }, - "start_loop_here": "Start loop here", - "help_no_file": "Select a trace to use the routing tool, or click on the map to start creating a new route.", - "help": "Click on the map to add a new anchor point, or drag existing ones to change the route.", - "activities": { - "bike": "Bike", - "racing_bike": "Road bike", - "gravel_bike": "Gravel bike", - "mountain_bike": "Mountain bike", - "foot": "Run/hike", - "motorcycle": "Motorcycle", - "water": "Water", - "railway": "Railway" - }, - "surface": { - "unknown": "Unknown", - "paved": "Paved", - "unpaved": "Unpaved", - "asphalt": "Asphalt", - "concrete": "Concrete", - "chipseal": "Chipseal", - "cobblestone": "Cobblestone", - "unhewn_cobblestone": "Unhewn cobblestone", - "paving_stones": "Paving stones", - "stepping_stones": "Stepping stones", - "sett": "Sett", - "metal": "Metal", - "wood": "Wood", - "compacted": "Compacted gravel", - "fine_gravel": "Fine gravel", - "gravel": "Gravel", - "pebblestone": "Pebblestone", - "rock": "Rock", - "dirt": "Dirt", - "ground": "Ground", - "earth": "Earth", - "snow": "Snow", - "ice": "Ice", - "salt": "Salt", - "mud": "Mud", - "sand": "Sand", - "woodchips": "Woodchips", - "grass": "Grass", - "grass_paver": "Grass paver" - }, - "error": { - "from": "The start point is too far from the nearest road", - "via": "The via point is too far from the nearest road", - "to": "The end point is too far from the nearest road", - "timeout": "Route calculation took too long, try adding points closer together" - } - }, - "scissors": { - "tooltip": "Crop or split", - "crop": "Crop", - "split_as": "Split the trace into", - "help_invalid_selection": "Select a trace to crop or split.", - "help": "Use the slider to crop the trace, or split it by clicking on one of the split markers or on the trace itself." - }, - "time": { - "tooltip": "Manage time data", - "start": "Start", - "end": "End", - "total_time": "Moving time", - "pick_date": "Pick a date", - "artificial": "Create realistic time data", - "update": "Update time data", - "help": "Use the form to set new time data.", - "help_invalid_selection": "Select a single trace to manage its time data." - }, - "merge": { - "merge_traces": "Connect the traces", - "merge_contents": "Merge the contents and keep the traces disconnected", - "merge_selection": "Merge selection", - "tooltip": "Merge items together", - "help_merge_traces": "Connecting the selected traces will create a single continuous trace.", - "help_cannot_merge_traces": "Your selection must contain several traces to connect them.", - "help_merge_contents": "Merging the contents of the selected items will group all the contents inside the first item.", - "help_cannot_merge_contents": "Your selection must contain several items to merge their contents." - }, - "extract": { - "tooltip": "Extract contents to separate items", - "button": "Extract", - "help": "Extracting the contents of the selected items will create a separate item for each of their contents.", - "help_invalid_selection": "Your selection must contain items with multiple traces to extract them." - }, - "waypoint": { - "tooltip": "Create and edit points of interest", - "icon": "Icon", - "link": "Link", - "longitude": "Longitude", - "latitude": "Latitude", - "create": "Create point of interest", - "add": "Add point of interest to file", - "help": "Fill in the form to create a new point of interest, or click on an existing one to edit it. Click on the map to fill the coordinates, or drag points of interest to move them.", - "help_no_selection": "Select a file to create or edit points of interest." - }, - "reduce": { - "tooltip": "Reduce the number of GPS points", - "tolerance": "Tolerance", - "number_of_points": "Number of GPS points", - "button": "Minify", - "help": "Use the slider to choose the number of GPS points to keep.", - "help_no_selection": "Select a trace to reduce the number of its GPS points." - }, - "clean": { - "tooltip": "Clean GPS points and points of interest with a rectangle selection", - "delete_trackpoints": "Delete GPS points", - "delete_waypoints": "Delete points of interest", - "delete_inside": "Delete inside selection", - "delete_outside": "Delete outside selection", - "button": "Delete", - "help": "Select a rectangle area on the map to remove GPS points and points of interest.", - "help_no_selection": "Select a trace to clean GPS points and points of interest." - } - }, - "layers": { - "settings": "Layer settings", - "settings_help": "Select the map layers you want to show in the interface, add custom ones, and adjust their settings.", - "selection": "Layer selection", - "custom_layers": { - "title": "Custom layers", - "new": "New custom layer", - "edit": "Edit custom layer", - "urls": "URL(s)", - "url_placeholder": "WMTS, WMS or Mapbox style JSON", - "max_zoom": "Max zoom", - "layer_type": "Layer type", - "basemap": "Basemap", - "overlay": "Overlay", - "create": "Create layer", - "update": "Update layer" - }, - "opacity": "Overlay opacity", - "label": { - "basemaps": "Basemaps", - "overlays": "Overlays", - "custom": "Custom", - "world": "World", - "countries": "Countries", - "belgium": "Belgium", - "bulgaria": "Bulgaria", - "finland": "Finland", - "france": "France", - "new_zealand": "New Zealand", - "norway": "Norway", - "spain": "Spain", - "sweden": "Sweden", - "switzerland": "Switzerland", - "united_kingdom": "United Kingdom", - "united_states": "United States", - "mapboxOutdoors": "Mapbox Outdoors", - "mapboxSatellite": "Mapbox Satellite", - "openStreetMap": "OpenStreetMap", - "openTopoMap": "OpenTopoMap", - "openHikingMap": "OpenHikingMap", - "cyclOSM": "CyclOSM", - "linz": "LINZ Topo", - "linzTopo": "LINZ Topo50", - "swisstopoRaster": "swisstopo Raster", - "swisstopoVector": "swisstopo Vector", - "swisstopoSatellite": "swisstopo Satellite", - "ignBe": "IGN Topo", - "ignFrPlan": "IGN Plan", - "ignFrTopo": "IGN Topo", - "ignFrScan25": "IGN SCAN25", - "ignFrSatellite": "IGN Satellite", - "ignEs": "IGN", - "ordnanceSurvey": "Ordnance Survey", - "norwayTopo": "Topografisk Norgeskart 4", - "swedenTopo": "Lantmäteriet Topo", - "finlandTopo": "Lantmäteriverket Terrängkarta", - "bgMountains": "BGMountains", - "usgs": "USGS", - "cyclOSMlite": "CyclOSM Lite", - "swisstopoSlope": "swisstopo Slope", - "swisstopoHiking": "swisstopo Hiking", - "swisstopoHikingClosures": "swisstopo Hiking Closures", - "swisstopoCycling": "swisstopo Cycling", - "swisstopoCyclingClosures": "swisstopo Cycling Closures", - "swisstopoMountainBike": "swisstopo MTB", - "swisstopoMountainBikeClosures": "swisstopo MTB Closures", - "swisstopoSkiTouring": "swisstopo Ski Touring", - "ignFrCadastre": "IGN Cadastre", - "ignSlope": "IGN Slope", - "ignSkiTouring": "IGN Ski Touring", - "waymarked_trails": "Waymarked Trails", - "waymarkedTrailsHiking": "Hiking", - "waymarkedTrailsCycling": "Cycling", - "waymarkedTrailsMTB": "MTB", - "waymarkedTrailsSkating": "Skating", - "waymarkedTrailsHorseRiding": "Horse Riding", - "waymarkedTrailsWinter": "Winter", - "points_of_interest": "Points of interest", - "food": "Food", - "bakery": "Bakery", - "food-store": "Food Store", - "eat-and-drink": "Eat and Drink", - "amenities": "Amenities", - "toilets": "Toilets", - "water": "Water", - "shower": "Shower", - "shelter": "Shelter", - "motorized": "Cars and Motorcycles", - "fuel-station": "Fuel Station", - "parking": "Parking", - "garage": "Garage", - "barrier": "Barrier", - "tourism": "Tourism", - "attraction": "Attraction", - "viewpoint": "Viewpoint", - "hotel": "Hotel", - "campsite": "Campsite", - "hut": "Hut", - "picnic": "Picnic Area", - "summit": "Summit", - "pass": "Pass", - "climbing": "Climbing", - "bicycle": "Bicycle", - "bicycle-parking": "Bicycle Parking", - "bicycle-rental": "Bicycle Rental", - "bicycle-shop": "Bicycle Shop", - "public-transport": "Public Transport", - "railway-station": "Railway Station", - "tram-stop": "Tram Stop", - "bus-stop": "Bus Stop", - "ferry": "Ferry" - }, - "color": { - "blue": "Blue", - "bluered": "Blue Red", - "gray": "Gray", - "hot": "Hot", - "purple": "Purple", - "orange": "Orange" - } - }, - "chart": { - "show_slope": "Show slope data", - "show_surface": "Show surface data", - "show_speed": "Show speed data", - "show_pace": "Show pace data", - "show_heartrate": "Show heart rate data", - "show_cadence": "Show cadence data", - "show_temperature": "Show temperature data", - "show_power": "Show power data" - }, - "quantities": { - "distance": "Distance", - "elevation": "Elevation", - "temperature": "Temperature", - "speed": "Speed", - "pace": "Pace", - "heartrate": "Heart rate", - "cadence": "Cadence", - "power": "Power", - "slope": "Slope", - "surface": "Surface", - "time": "Time", - "moving": "Moving", - "total": "Total" - }, - "units": { - "meters": "m", - "feet": "ft", - "kilometers": "km", - "miles": "mi", - "celsius": "°C", - "fahrenheit": "°F", - "kilometers_per_hour": "km/h", - "miles_per_hour": "mph", - "minutes_per_kilometer": "min/km", - "minutes_per_mile": "min/mi", - "heartrate": "bpm", - "cadence": "rpm", - "power": "W" - }, - "gpx": { - "file": "File", - "files": "Files", - "track": "Track", - "tracks": "Tracks", - "segment": "Segment", - "segments": "Segments", - "waypoint": "Point of interest", - "waypoints": "Points of interest", - "symbol": { - "alert": "Alert", - "anchor": "Anchor", - "bank": "Bank", - "beach": "Beach", - "bike_trail": "Bike Trail", - "binoculars": "Binoculars", - "bridge": "Bridge", - "building": "Building", - "campground": "Campsite", - "car": "Car", - "car_repair": "Garage", - "convenience_store": "Convenience Store", - "crossing": "Crossing", - "department_store": "Department Store", - "drinking_water": "Water", - "exit": "Exit", - "lodge": "Hut", - "lodging": "Accommodation", - "forest": "Forest", - "gas_station": "Fuel Station", - "ground_transportation": "Ground Transportation", - "hotel": "Hotel", - "house": "House", - "information": "Information", - "park": "Park", - "parking_area": "Parking", - "pharmacy": "Pharmacy", - "picnic_area": "Picnic Area", - "restaurant": "Restaurant", - "restricted_area": "Restricted Area", - "restroom": "Toilets", - "road": "Road", - "scenic_area": "Scenic Area", - "shelter": "Shelter", - "shopping_center": "Shopping Center", - "shower": "Shower", - "summit": "Summit", - "telephone": "Telephone", - "tunnel": "Tunnel", - "water_source": "Water Source" - } - }, - "homepage": { - "website": "Website", - "home": "Home", - "app": "App", - "contact": "Contact", - "x": "X", - "facebook": "Facebook", - "github": "GitHub", - "crowdin": "Crowdin", - "email": "Email", - "contribute": "Contribute", - "supported_by": "supported by", - "support_button": "Support gpx.studio on Ko-fi", - "route_planning": "Route planning", - "route_planning_description": "An intuitive interface to create itineraries tailored to each sport, based on OpenStreetMap data.", - "file_processing": "Advanced file processing", - "file_processing_description": "A suite of tools for performing all common file processing tasks, and which can be applied to multiple files at once.", - "maps": "Global and local maps", - "maps_description": "A large collection of basemaps, overlays and points of interest to help you craft your next outdoor adventure, or visualize your latest achievement.", - "data_visualization": "Data visualization", - "data_visualization_description": "An interactive elevation profile with detailed statistics to analyze recorded activities and future objectives.", - "identity": "Free, ad-free and open source", - "identity_description": "The website is free to use, without ads, and the source code is publicly available on GitHub. This is only possible thanks to the incredible support of the community." - }, - "embedding": { - "title": "Create your own map", - "mapbox_token": "Mapbox access token", - "file_urls": "File URLs (separated by commas)", - "drive_ids": "Google Drive file IDs (separated by commas)", - "basemap": "Basemap", - "height": "Height", - "fill_by": "Fill by", - "none": "None", - "show_controls": "Show controls", - "manual_camera": "Manual camera", - "manual_camera_description": "You can move the map below to adjust the camera position.", - "latitude": "Latitude", - "longitude": "Longitude", - "zoom": "Zoom", - "pitch": "Pitch", - "bearing": "Bearing", - "preview": "Preview", - "code": "Integration code" - }, - "webgl2_required": "WebGL 2 is required to display the map.", - "enable_webgl2": "Learn how to enable WebGL 2 in your browser", - "page_not_found": "page not found" -} \ No newline at end of file + "metadata": { + "home_title": "home", + "app_title": "the online GPX file editor", + "embed_title": "the online GPX file editor", + "help_title": "help", + "404_title": "page not found", + "description": "View, edit, and create GPX files online with advanced route planning capabilities and file processing tools, beautiful maps and detailed data visualizations." + }, + "menu": { + "new": "New", + "new_file": "New file", + "new_track": "New track", + "new_segment": "New segment", + "open": "Open...", + "duplicate": "Duplicate", + "close": "Close", + "close_all": "Close all", + "copy": "Copy", + "paste": "Paste", + "cut": "Cut", + "export": "Export...", + "export_all": "Export all...", + "export_options": "Export options", + "support_message": "The tool is free to use, but not free to run. Please consider supporting the website if you use it frequently. Thank you!", + "support_button": "Help keep the website free", + "download_file": "Download file", + "download_files": "Download files", + "edit": "Edit", + "undo": "Undo", + "redo": "Redo", + "delete": "Delete", + "select_all": "Select all", + "view": "View", + "elevation_profile": "Elevation profile", + "vertical_file_view": "Vertical file list", + "switch_basemap": "Switch to previous basemap", + "toggle_overlays": "Toggle overlays", + "toggle_3d": "Toggle 3D", + "settings": "Settings", + "distance_units": "Distance units", + "metric": "Metric", + "imperial": "Imperial", + "velocity_units": "Velocity units", + "temperature_units": "Temperature units", + "celsius": "Celsius", + "fahrenheit": "Fahrenheit", + "language": "Language", + "mode": "Theme", + "system": "System", + "light": "Light", + "dark": "Dark", + "street_view_source": "Street view source", + "mapillary": "Mapillary", + "google": "Google", + "layers": "Map layers...", + "distance_markers": "Distance markers", + "direction_markers": "Direction arrows", + "help": "Help", + "more": "More...", + "donate": "Donate", + "ctrl": "Ctrl", + "click": "Click", + "drag": "Drag", + "metadata": { + "button": "Info...", + "name": "Name", + "description": "Description", + "save": "Save" + }, + "style": { + "button": "Appearance...", + "color": "Color", + "opacity": "Opacity", + "width": "Width" + }, + "hide": "Hide", + "unhide": "Unhide", + "open_in": "Open in", + "fly_to": "Fly to", + "fly_to_selection": "Fly to selection" + }, + "toolbar": { + "routing": { + "tooltip": "Plan or edit a route", + "activity": "Activity", + "use_routing": "Routing", + "use_routing_tooltip": "Connect anchor points via road network, or in a straight line if disabled", + "allow_private": "Allow private roads", + "reverse": { + "button": "Reverse", + "tooltip": "Reverse the direction of the route" + }, + "route_back_to_start": { + "button": "Back to start", + "tooltip": "Connect the last point of the route with the starting point" + }, + "round_trip": { + "button": "Round trip", + "tooltip": "Return to the starting point by the same route" + }, + "start_loop_here": "Start loop here", + "help_no_file": "Select a trace to use the routing tool, or click on the map to start creating a new route.", + "help": "Click on the map to add a new anchor point, or drag existing ones to change the route.", + "activities": { + "bike": "Bike", + "racing_bike": "Road bike", + "gravel_bike": "Gravel bike", + "mountain_bike": "Mountain bike", + "foot": "Run/hike", + "motorcycle": "Motorcycle", + "water": "Water", + "railway": "Railway" + }, + "surface": { + "unknown": "Unknown", + "paved": "Paved", + "unpaved": "Unpaved", + "asphalt": "Asphalt", + "concrete": "Concrete", + "chipseal": "Chipseal", + "cobblestone": "Cobblestone", + "unhewn_cobblestone": "Unhewn cobblestone", + "paving_stones": "Paving stones", + "stepping_stones": "Stepping stones", + "sett": "Sett", + "metal": "Metal", + "wood": "Wood", + "compacted": "Compacted gravel", + "fine_gravel": "Fine gravel", + "gravel": "Gravel", + "pebblestone": "Pebblestone", + "rock": "Rock", + "dirt": "Dirt", + "ground": "Ground", + "earth": "Earth", + "snow": "Snow", + "ice": "Ice", + "salt": "Salt", + "mud": "Mud", + "sand": "Sand", + "woodchips": "Woodchips", + "grass": "Grass", + "grass_paver": "Grass paver" + }, + "error": { + "from": "The start point is too far from the nearest road", + "via": "The via point is too far from the nearest road", + "to": "The end point is too far from the nearest road", + "timeout": "Route calculation took too long, try adding points closer together" + } + }, + "scissors": { + "tooltip": "Crop or split", + "crop": "Crop", + "split_as": "Split the trace into", + "help_invalid_selection": "Select a trace to crop or split.", + "help": "Use the slider to crop the trace, or split it by clicking on one of the split markers or on the trace itself." + }, + "time": { + "tooltip": "Manage time data", + "start": "Start", + "end": "End", + "total_time": "Moving time", + "pick_date": "Pick a date", + "artificial": "Create realistic time data", + "update": "Update time data", + "help": "Use the form to set new time data.", + "help_invalid_selection": "Select a single trace to manage its time data." + }, + "merge": { + "merge_traces": "Connect the traces", + "merge_contents": "Merge the contents and keep the traces disconnected", + "merge_selection": "Merge selection", + "tooltip": "Merge items together", + "help_merge_traces": "Connecting the selected traces will create a single continuous trace.", + "help_cannot_merge_traces": "Your selection must contain several traces to connect them.", + "help_merge_contents": "Merging the contents of the selected items will group all the contents inside the first item.", + "help_cannot_merge_contents": "Your selection must contain several items to merge their contents." + }, + "extract": { + "tooltip": "Extract contents to separate items", + "button": "Extract", + "help": "Extracting the contents of the selected items will create a separate item for each of their contents.", + "help_invalid_selection": "Your selection must contain items with multiple traces to extract them." + }, + "waypoint": { + "tooltip": "Create and edit points of interest", + "icon": "Icon", + "link": "Link", + "longitude": "Longitude", + "latitude": "Latitude", + "create": "Create point of interest", + "add": "Add point of interest to file", + "help": "Fill in the form to create a new point of interest, or click on an existing one to edit it. Click on the map to fill the coordinates, or drag points of interest to move them.", + "help_no_selection": "Select a file to create or edit points of interest." + }, + "reduce": { + "tooltip": "Reduce the number of GPS points", + "tolerance": "Tolerance", + "number_of_points": "Number of GPS points", + "button": "Minify", + "help": "Use the slider to choose the number of GPS points to keep.", + "help_no_selection": "Select a trace to reduce the number of its GPS points." + }, + "clean": { + "tooltip": "Clean GPS points and points of interest with a rectangle selection", + "delete_trackpoints": "Delete GPS points", + "delete_waypoints": "Delete points of interest", + "delete_inside": "Delete inside selection", + "delete_outside": "Delete outside selection", + "button": "Delete", + "help": "Select a rectangle area on the map to remove GPS points and points of interest.", + "help_no_selection": "Select a trace to clean GPS points and points of interest." + } + }, + "layers": { + "settings": "Layer settings", + "settings_help": "Select the map layers you want to show in the interface, add custom ones, and adjust their settings.", + "selection": "Layer selection", + "custom_layers": { + "title": "Custom layers", + "new": "New custom layer", + "edit": "Edit custom layer", + "urls": "URL(s)", + "url_placeholder": "WMTS, WMS or Mapbox style JSON", + "max_zoom": "Max zoom", + "layer_type": "Layer type", + "basemap": "Basemap", + "overlay": "Overlay", + "create": "Create layer", + "update": "Update layer" + }, + "opacity": "Overlay opacity", + "label": { + "basemaps": "Basemaps", + "overlays": "Overlays", + "custom": "Custom", + "world": "World", + "countries": "Countries", + "belgium": "Belgium", + "bulgaria": "Bulgaria", + "finland": "Finland", + "france": "France", + "new_zealand": "New Zealand", + "norway": "Norway", + "spain": "Spain", + "sweden": "Sweden", + "switzerland": "Switzerland", + "united_kingdom": "United Kingdom", + "united_states": "United States", + "mapboxOutdoors": "Mapbox Outdoors", + "mapboxSatellite": "Mapbox Satellite", + "openStreetMap": "OpenStreetMap", + "openTopoMap": "OpenTopoMap", + "openHikingMap": "OpenHikingMap", + "cyclOSM": "CyclOSM", + "linz": "LINZ Topo", + "linzTopo": "LINZ Topo50", + "swisstopoRaster": "swisstopo Raster", + "swisstopoVector": "swisstopo Vector", + "swisstopoSatellite": "swisstopo Satellite", + "ignBe": "IGN Topo", + "ignFrPlan": "IGN Plan", + "ignFrTopo": "IGN Topo", + "ignFrScan25": "IGN SCAN25", + "ignFrSatellite": "IGN Satellite", + "ignEs": "IGN", + "ordnanceSurvey": "Ordnance Survey", + "norwayTopo": "Topografisk Norgeskart 4", + "swedenTopo": "Lantmäteriet Topo", + "finlandTopo": "Lantmäteriverket Terrängkarta", + "bgMountains": "BGMountains", + "usgs": "USGS", + "cyclOSMlite": "CyclOSM Lite", + "swisstopoSlope": "swisstopo Slope", + "swisstopoHiking": "swisstopo Hiking", + "swisstopoHikingClosures": "swisstopo Hiking Closures", + "swisstopoCycling": "swisstopo Cycling", + "swisstopoCyclingClosures": "swisstopo Cycling Closures", + "swisstopoMountainBike": "swisstopo MTB", + "swisstopoMountainBikeClosures": "swisstopo MTB Closures", + "swisstopoSkiTouring": "swisstopo Ski Touring", + "ignFrCadastre": "IGN Cadastre", + "ignSlope": "IGN Slope", + "ignSkiTouring": "IGN Ski Touring", + "waymarked_trails": "Waymarked Trails", + "waymarkedTrailsHiking": "Hiking", + "waymarkedTrailsCycling": "Cycling", + "waymarkedTrailsMTB": "MTB", + "waymarkedTrailsSkating": "Skating", + "waymarkedTrailsHorseRiding": "Horse Riding", + "waymarkedTrailsWinter": "Winter", + "points_of_interest": "Points of interest", + "food": "Food", + "bakery": "Bakery", + "food-store": "Food Store", + "eat-and-drink": "Eat and Drink", + "amenities": "Amenities", + "toilets": "Toilets", + "water": "Water", + "shower": "Shower", + "shelter": "Shelter", + "motorized": "Cars and Motorcycles", + "fuel-station": "Fuel Station", + "parking": "Parking", + "garage": "Garage", + "barrier": "Barrier", + "tourism": "Tourism", + "attraction": "Attraction", + "viewpoint": "Viewpoint", + "hotel": "Hotel", + "campsite": "Campsite", + "hut": "Hut", + "picnic": "Picnic Area", + "summit": "Summit", + "pass": "Pass", + "climbing": "Climbing", + "bicycle": "Bicycle", + "bicycle-parking": "Bicycle Parking", + "bicycle-rental": "Bicycle Rental", + "bicycle-shop": "Bicycle Shop", + "public-transport": "Public Transport", + "railway-station": "Railway Station", + "tram-stop": "Tram Stop", + "bus-stop": "Bus Stop", + "ferry": "Ferry" + }, + "color": { + "blue": "Blue", + "bluered": "Blue Red", + "gray": "Gray", + "hot": "Hot", + "purple": "Purple", + "orange": "Orange" + } + }, + "chart": { + "show_slope": "Show slope data", + "show_surface": "Show surface data", + "show_speed": "Show speed data", + "show_pace": "Show pace data", + "show_heartrate": "Show heart rate data", + "show_cadence": "Show cadence data", + "show_temperature": "Show temperature data", + "show_power": "Show power data" + }, + "quantities": { + "distance": "Distance", + "elevation": "Elevation", + "temperature": "Temperature", + "speed": "Speed", + "pace": "Pace", + "heartrate": "Heart rate", + "cadence": "Cadence", + "power": "Power", + "slope": "Slope", + "surface": "Surface", + "time": "Time", + "moving": "Moving", + "total": "Total" + }, + "units": { + "meters": "m", + "feet": "ft", + "kilometers": "km", + "miles": "mi", + "celsius": "°C", + "fahrenheit": "°F", + "kilometers_per_hour": "km/h", + "miles_per_hour": "mph", + "minutes_per_kilometer": "min/km", + "minutes_per_mile": "min/mi", + "heartrate": "bpm", + "cadence": "rpm", + "power": "W" + }, + "gpx": { + "file": "File", + "files": "Files", + "track": "Track", + "tracks": "Tracks", + "segment": "Segment", + "segments": "Segments", + "waypoint": "Point of interest", + "waypoints": "Points of interest", + "symbol": { + "alert": "Alert", + "anchor": "Anchor", + "bank": "Bank", + "beach": "Beach", + "bike_trail": "Bike Trail", + "binoculars": "Binoculars", + "bridge": "Bridge", + "building": "Building", + "campground": "Campsite", + "car": "Car", + "car_repair": "Garage", + "convenience_store": "Convenience Store", + "crossing": "Crossing", + "department_store": "Department Store", + "drinking_water": "Water", + "exit": "Exit", + "lodge": "Hut", + "lodging": "Accommodation", + "forest": "Forest", + "gas_station": "Fuel Station", + "ground_transportation": "Ground Transportation", + "hotel": "Hotel", + "house": "House", + "information": "Information", + "park": "Park", + "parking_area": "Parking", + "pharmacy": "Pharmacy", + "picnic_area": "Picnic Area", + "restaurant": "Restaurant", + "restricted_area": "Restricted Area", + "restroom": "Toilets", + "road": "Road", + "scenic_area": "Scenic Area", + "shelter": "Shelter", + "shopping_center": "Shopping Center", + "shower": "Shower", + "summit": "Summit", + "telephone": "Telephone", + "tunnel": "Tunnel", + "water_source": "Water Source" + } + }, + "homepage": { + "website": "Website", + "home": "Home", + "app": "App", + "contact": "Contact", + "x": "X", + "facebook": "Facebook", + "github": "GitHub", + "crowdin": "Crowdin", + "email": "Email", + "contribute": "Contribute", + "supported_by": "supported by", + "support_button": "Support gpx.studio on Ko-fi", + "route_planning": "Route planning", + "route_planning_description": "An intuitive interface to create itineraries tailored to each sport, based on OpenStreetMap data.", + "file_processing": "Advanced file processing", + "file_processing_description": "A suite of tools for performing all common file processing tasks, and which can be applied to multiple files at once.", + "maps": "Global and local maps", + "maps_description": "A large collection of basemaps, overlays and points of interest to help you craft your next outdoor adventure, or visualize your latest achievement.", + "data_visualization": "Data visualization", + "data_visualization_description": "An interactive elevation profile with detailed statistics to analyze recorded activities and future objectives.", + "identity": "Free, ad-free and open source", + "identity_description": "The website is free to use, without ads, and the source code is publicly available on GitHub. This is only possible thanks to the incredible support of the community." + }, + "embedding": { + "title": "Create your own map", + "mapbox_token": "Mapbox access token", + "file_urls": "File URLs (separated by commas)", + "drive_ids": "Google Drive file IDs (separated by commas)", + "basemap": "Basemap", + "height": "Height", + "fill_by": "Fill by", + "none": "None", + "show_controls": "Show controls", + "manual_camera": "Manual camera", + "manual_camera_description": "You can move the map below to adjust the camera position.", + "latitude": "Latitude", + "longitude": "Longitude", + "zoom": "Zoom", + "pitch": "Pitch", + "bearing": "Bearing", + "preview": "Preview", + "code": "Integration code" + }, + "webgl2_required": "WebGL 2 is required to display the map.", + "enable_webgl2": "Learn how to enable WebGL 2 in your browser", + "page_not_found": "page not found" +}