From c313d9a5c7d60928a51c82200d06422cac28f0d9 Mon Sep 17 00:00:00 2001 From: vcoppe Date: Fri, 24 May 2024 13:16:41 +0200 Subject: [PATCH] selection utilities --- gpx/src/gpx.ts | 13 ++- .../CollapsibleTreeNode.svelte | 10 ++- .../src/lib/components/file-list/FileList.ts | 75 ++++++++++++---- .../components/file-list/FileListNode.svelte | 27 ++++-- .../file-list/FileListNodeContent.svelte | 90 +++++++++++++------ .../file-list/FileListNodeLabel.svelte | 40 +++++++-- .../src/lib/components/file-list/Selection.ts | 8 +- .../src/lib/components/gpx-layer/GPXLayer.ts | 63 ++++++++++--- website/src/lib/db.ts | 24 ++--- website/src/locales/en.json | 6 ++ 10 files changed, 268 insertions(+), 88 deletions(-) diff --git a/gpx/src/gpx.ts b/gpx/src/gpx.ts index 3643d455..781b42f3 100644 --- a/gpx/src/gpx.ts +++ b/gpx/src/gpx.ts @@ -136,7 +136,7 @@ export class GPXFile extends GPXTreeNode{ if (gpx) { this.attributes = gpx.attributes this.metadata = gpx.metadata; - this.wpt = gpx.wpt ? gpx.wpt.map((waypoint) => new Waypoint(waypoint)) : []; + this.wpt = gpx.wpt ? gpx.wpt.map((waypoint, index) => new Waypoint(waypoint, index)) : []; this.trk = gpx.trk ? gpx.trk.map((track) => new Track(track)) : []; if (gpx.hasOwnProperty('_data')) { this._data = gpx._data; @@ -576,6 +576,8 @@ export class TrackPoint { }; export class Waypoint { + [immerable] = true; + attributes: Coordinates; ele?: number; time?: Date; @@ -585,8 +587,9 @@ export class Waypoint { link?: Link; sym?: string; type?: string; + _data: { [key: string]: any } = {}; - constructor(waypoint: WaypointType | Waypoint) { + constructor(waypoint: WaypointType & { _data?: any } | Waypoint, index?: number) { this.attributes = waypoint.attributes; this.ele = waypoint.ele; this.time = waypoint.time; @@ -596,6 +599,12 @@ export class Waypoint { this.link = waypoint.link; this.sym = waypoint.sym; this.type = waypoint.type; + if (waypoint.hasOwnProperty('_data')) { + this._data = waypoint._data; + } + if (index !== undefined) { + this._data['index'] = index; + } } getCoordinates(): Coordinates { diff --git a/website/src/lib/components/collapsible-tree/CollapsibleTreeNode.svelte b/website/src/lib/components/collapsible-tree/CollapsibleTreeNode.svelte index 386a135d..9207a6da 100644 --- a/website/src/lib/components/collapsible-tree/CollapsibleTreeNode.svelte +++ b/website/src/lib/components/collapsible-tree/CollapsibleTreeNode.svelte @@ -3,7 +3,7 @@ import { Button } from '$lib/components/ui/button'; import { ChevronDown, ChevronLeft, ChevronRight } from 'lucide-svelte'; import { getContext, setContext } from 'svelte'; - import type { Writable } from 'svelte/store'; + import { get, type Writable } from 'svelte/store'; export let id: string | number; @@ -23,6 +23,14 @@ } return value; }); + + export function openNode() { + if (get(open)[fullId]) return; + open.update((value) => { + value[fullId] = true; + return value; + }); + } diff --git a/website/src/lib/components/file-list/FileList.ts b/website/src/lib/components/file-list/FileList.ts index 08a153d0..857316d5 100644 --- a/website/src/lib/components/file-list/FileList.ts +++ b/website/src/lib/components/file-list/FileList.ts @@ -54,6 +54,38 @@ export class SelectionTreeType { return false; } + hasAnyParent(item: ListItem, self: boolean = true): boolean { + if (this.selected && this.item.level <= item.level && (self || this.item.level < item.level)) { + return this.selected; + } + let id = item.getIdAtLevel(this.item.level); + if (id !== undefined) { + if (this.children.hasOwnProperty(id)) { + return this.children[id].hasAnyParent(item, self); + } + } + return false; + } + + hasAnyChildren(item: ListItem, self: boolean = true): boolean { + if (this.selected && this.item.level >= item.level && (self || this.item.level > item.level)) { + return this.selected; + } + let id = item.getIdAtLevel(this.item.level); + if (id !== undefined) { + if (this.children.hasOwnProperty(id)) { + return this.children[id].hasAnyChildren(item, self); + } + } else { + for (let key in this.children) { + if (this.children[key].hasAnyChildren(item, self)) { + return true; + } + } + } + return false; + } + getSelected(selection?: ListItem[]): ListItem[] { if (selection === undefined) { selection = []; @@ -93,7 +125,14 @@ export class SelectionTreeType { } }; -export type ListLevel = 'root' | 'file' | 'track' | 'segment' | 'waypoints' | 'waypoint'; +export enum ListLevel { + ROOT, + FILE, + TRACK, + SEGMENT, + WAYPOINTS, + WAYPOINT +} export abstract class ListItem { level: ListLevel; @@ -110,7 +149,7 @@ export abstract class ListItem { export class ListRootItem extends ListItem { constructor() { - super('root'); + super(ListLevel.ROOT); } getId(): string { @@ -134,7 +173,7 @@ export class ListFileItem extends ListItem { fileId: string; constructor(fileId: string) { - super('file'); + super(ListLevel.FILE); this.fileId = fileId; } @@ -144,7 +183,7 @@ export class ListFileItem extends ListItem { getIdAtLevel(level: ListLevel): string | number | undefined { switch (level) { - case 'root': + case ListLevel.ROOT: return this.fileId; default: return undefined; @@ -169,7 +208,7 @@ export class ListTrackItem extends ListItem { trackIndex: number; constructor(fileId: string, trackIndex: number) { - super('track'); + super(ListLevel.TRACK); this.fileId = fileId; this.trackIndex = trackIndex; } @@ -180,9 +219,9 @@ export class ListTrackItem extends ListItem { getIdAtLevel(level: ListLevel): string | number | undefined { switch (level) { - case 'root': + case ListLevel.ROOT: return this.fileId; - case 'file': + case ListLevel.FILE: return this.trackIndex; default: return undefined; @@ -208,7 +247,7 @@ export class ListTrackSegmentItem extends ListItem { segmentIndex: number; constructor(fileId: string, trackIndex: number, segmentIndex: number) { - super('segment'); + super(ListLevel.SEGMENT); this.fileId = fileId; this.trackIndex = trackIndex; this.segmentIndex = segmentIndex; @@ -220,11 +259,11 @@ export class ListTrackSegmentItem extends ListItem { getIdAtLevel(level: ListLevel): string | number | undefined { switch (level) { - case 'root': + case ListLevel.ROOT: return this.fileId; - case 'file': + case ListLevel.FILE: return this.trackIndex; - case 'track': + case ListLevel.TRACK: return this.segmentIndex; default: return undefined; @@ -252,7 +291,7 @@ export class ListWaypointsItem extends ListItem { fileId: string; constructor(fileId: string) { - super('waypoints'); + super(ListLevel.WAYPOINTS); this.fileId = fileId; } @@ -262,9 +301,9 @@ export class ListWaypointsItem extends ListItem { getIdAtLevel(level: ListLevel): string | number | undefined { switch (level) { - case 'root': + case ListLevel.ROOT: return this.fileId; - case 'file': + case ListLevel.FILE: return 'waypoints'; default: return undefined; @@ -285,7 +324,7 @@ export class ListWaypointItem extends ListItem { waypointIndex: number; constructor(fileId: string, waypointIndex: number) { - super('waypoint'); + super(ListLevel.WAYPOINT); this.fileId = fileId; this.waypointIndex = waypointIndex; } @@ -296,11 +335,11 @@ export class ListWaypointItem extends ListItem { getIdAtLevel(level: ListLevel): string | number | undefined { switch (level) { - case 'root': + case ListLevel.ROOT: return this.fileId; - case 'file': + case ListLevel.FILE: return 'waypoints'; - case 'waypoints': + case ListLevel.WAYPOINTS: return this.waypointIndex; default: return undefined; diff --git a/website/src/lib/components/file-list/FileListNode.svelte b/website/src/lib/components/file-list/FileListNode.svelte index 95535532..747292ec 100644 --- a/website/src/lib/components/file-list/FileListNode.svelte +++ b/website/src/lib/components/file-list/FileListNode.svelte @@ -1,12 +1,14 @@ {#if node instanceof Map} {:else if recursive} - +
diff --git a/website/src/lib/components/file-list/FileListNodeContent.svelte b/website/src/lib/components/file-list/FileListNodeContent.svelte index 2b466b92..c6b1e52b 100644 --- a/website/src/lib/components/file-list/FileListNodeContent.svelte +++ b/website/src/lib/components/file-list/FileListNodeContent.svelte @@ -7,8 +7,9 @@ import FileListNodeStore from './FileListNodeStore.svelte'; import FileListNode from './FileListNode.svelte'; import FileListNodeLabel from './FileListNodeLabel.svelte'; - import { type ListItem } from './FileList'; + import { ListLevel, type ListItem } from './FileList'; import { selection } from './Selection'; + import { _ } from 'svelte-i18n'; export let node: | Map> @@ -19,16 +20,16 @@ let container: HTMLElement; let elements: { [id: string | number]: HTMLElement } = {}; - let sortableLevel = + let sortableLevel: ListLevel = node instanceof Map - ? 'file' + ? ListLevel.FILE : node instanceof GPXFile ? waypointRoot - ? 'waypoints' - : 'track' + ? ListLevel.WAYPOINTS + : ListLevel.TRACK : node instanceof Track - ? 'segment' - : 'waypoint'; + ? ListLevel.SEGMENT + : ListLevel.WAYPOINT; let pull: Record = { file: ['file', 'track'], track: ['file', 'track'], @@ -36,23 +37,28 @@ waypoint: ['waypoint'] }; let sortable: Sortable; - let orientation = getContext<'vertical' | 'horizontal'>('orientation'); function onSelectChange() { - selection.update(($selection) => { - $selection.clear(); - Object.entries(elements).forEach(([id, element]) => { - let realId = sortableLevel === 'file' || sortableLevel === 'waypoints' ? id : parseInt(id); - $selection.set(item.extend(realId), element.classList.contains('sortable-selected')); + let changed = getChangedIds(); + if (changed.length > 0) { + selection.update(($selection) => { + $selection.clear(); + Object.entries(elements).forEach(([id, element]) => { + let realId = + sortableLevel === ListLevel.FILE || sortableLevel === ListLevel.WAYPOINTS + ? id + : parseInt(id); + $selection.set(item.extend(realId), element.classList.contains('sortable-selected')); + }); + return $selection; }); - return $selection; - }); + } } const { fileOrder } = settings; function syncFileOrder() { - if (!sortable || sortableLevel !== 'file') { + if (!sortable || sortableLevel !== ListLevel.FILE) { return; } @@ -100,9 +106,9 @@ avoidImplicitDeselect: true, onSelect: onSelectChange, onDeselect: onSelectChange, - sort: sortableLevel !== 'waypoint', + sort: sortableLevel !== ListLevel.WAYPOINT, onSort: () => { - if (sortableLevel !== 'file') { + if (sortableLevel !== ListLevel.FILE) { return; } @@ -120,6 +126,7 @@ } } }); + selection.set(get(selection)); }); $: if ($fileOrder) { @@ -128,13 +135,13 @@ afterUpdate(() => { syncFileOrder(); - if (sortableLevel === 'file') { + if (sortableLevel === ListLevel.FILE) { Object.keys(elements).forEach((fileId) => { if (!get(fileObservers).has(fileId)) { delete elements[fileId]; } }); - } else if (sortableLevel === 'waypoints') { + } else if (sortableLevel === ListLevel.WAYPOINTS) { if (node.wpt.length === 0) { delete elements['waypoints']; } @@ -150,17 +157,39 @@ }); const unsubscribe = selection.subscribe(($selection) => { + let changed = getChangedIds(); + for (let id of changed) { + let element = elements[id]; + if (element) { + if ($selection.has(item.extend(id))) { + Sortable.utils.select(element); + } else { + Sortable.utils.deselect(element); + } + } + } + }); + + function getChangedIds() { + let changed: (string | number)[] = []; Object.entries(elements).forEach(([id, element]) => { - let realId = sortableLevel === 'file' || sortableLevel === 'waypoints' ? id : parseInt(id); - let inSelection = $selection.has(item.extend(realId)); + if (element === null) { + console.log('element is null', orientation, sortableLevel, id); + return; + } + let realId = + sortableLevel === ListLevel.FILE || sortableLevel === ListLevel.WAYPOINTS + ? id + : parseInt(id); + let realItem = item.extend(realId); + let inSelection = get(selection).has(realItem); let isSelected = element.classList.contains('sortable-selected'); - if (inSelection && !isSelected) { - Sortable.utils.select(element); - } else if (!inSelection && isSelected) { - Sortable.utils.deselect(element); + if (inSelection !== isSelected) { + changed.push(realId); } }); - }); + return changed; + } onDestroy(() => { unsubscribe(); @@ -194,13 +223,16 @@ {:else if node instanceof Track} {#each node.children as child, i}
- +
{/each} {:else if Array.isArray(node) && node.length > 0 && node[0] instanceof Waypoint} {#each node as wpt, i}
- +
{/each} {/if} diff --git a/website/src/lib/components/file-list/FileListNodeLabel.svelte b/website/src/lib/components/file-list/FileListNodeLabel.svelte index b761776e..54a7e985 100644 --- a/website/src/lib/components/file-list/FileListNodeLabel.svelte +++ b/website/src/lib/components/file-list/FileListNodeLabel.svelte @@ -2,13 +2,14 @@ import { Button } from '$lib/components/ui/button'; import * as ContextMenu from '$lib/components/ui/context-menu'; import Shortcut from '$lib/components/Shortcut.svelte'; - import { dbUtils } from '$lib/db'; - import { Copy, Trash2 } from 'lucide-svelte'; - import { type ListItem } from './FileList'; + import { dbUtils, fileObservers } from '$lib/db'; + import { Copy, MapPin, Trash2, Waypoints } from 'lucide-svelte'; + import { ListLevel, ListWaypointItem, type ListItem } from './FileList'; import { selectItem, selection } from './Selection'; import { _ } from 'svelte-i18n'; import { getContext } from 'svelte'; import { get } from 'svelte/store'; + import { gpxLayers } from '$lib/stores'; export let item: ListItem; export let label: string | undefined; @@ -34,7 +35,7 @@ : 'h-9 px-1.5 shadow-md'}" > { e.stopPropagation(); // Avoid toggling the collapsible element }} @@ -46,13 +47,40 @@ $selection.toggle(item); } }} + on:mouseenter={() => { + if (item instanceof ListWaypointItem) { + let layer = get(gpxLayers).get(item.getFileId()); + let fileStore = get(fileObservers).get(item.getFileId()); + if (layer && fileStore) { + let waypoint = get(fileStore)?.file.wpt[item.getWaypointIndex()]; + if (waypoint) { + layer.showWaypointPopup(waypoint); + } + } + } + }} + on:mouseleave={() => { + if (item instanceof ListWaypointItem) { + let layer = get(gpxLayers).get(item.getFileId()); + if (layer) { + layer.hideWaypointPopup(); + } + } + }} > - {label} + {#if item.level === ListLevel.SEGMENT} + + {:else if item.level === ListLevel.WAYPOINT} + + {/if} + + {label} + - {#if item.level !== 'waypoints'} + {#if item.level !== ListLevel.WAYPOINTS} {$_('menu.duplicate')} diff --git a/website/src/lib/components/file-list/Selection.ts b/website/src/lib/components/file-list/Selection.ts index f97c3c26..320fd436 100644 --- a/website/src/lib/components/file-list/Selection.ts +++ b/website/src/lib/components/file-list/Selection.ts @@ -16,13 +16,17 @@ export function selectFile(fileId: string) { selectItem(new ListFileItem(fileId)); } -export function addSelect(fileId: string) { +export function addSelectItem(item: ListItem) { selection.update(($selection) => { - $selection.toggle(new ListFileItem(fileId)); + $selection.toggle(item); return $selection; }); } +export function addSelectFile(fileId: string) { + addSelectItem(new ListFileItem(fileId)); +} + export function selectAll() { selection.update(($selection) => { let item: ListItem = new ListRootItem(); diff --git a/website/src/lib/components/gpx-layer/GPXLayer.ts b/website/src/lib/components/gpx-layer/GPXLayer.ts index 8909c76e..ce587961 100644 --- a/website/src/lib/components/gpx-layer/GPXLayer.ts +++ b/website/src/lib/components/gpx-layer/GPXLayer.ts @@ -3,8 +3,9 @@ import { settings, type GPXFileWithStatistics } from "$lib/db"; import { get, type Readable } from "svelte/store"; import mapboxgl from "mapbox-gl"; import { currentWaypoint, waypointPopup } from "./WaypointPopup"; -import { addSelect, selectFile, selection } from "$lib/components/file-list/Selection"; -import { ListTrackSegmentItem, type ListItem, ListFileItem, ListTrackItem } from "$lib/components/file-list/FileList"; +import { addSelectItem, selectItem, selection } from "$lib/components/file-list/Selection"; +import { ListTrackSegmentItem, type ListItem, ListWaypointItem, ListWaypointsItem, ListTrackItem, ListFileItem } from "$lib/components/file-list/FileList"; +import type { Waypoint } from "gpx"; let defaultWeight = 5; let defaultOpacity = 1; @@ -39,7 +40,7 @@ function decrementColor(color: string) { colorCount[color]--; } -const { directionMarkers } = settings; +const { directionMarkers, verticalFileView } = settings; export class GPXLayer { map: mapboxgl.Map; @@ -146,18 +147,23 @@ export class GPXLayer { file.wpt.forEach((waypoint) => { // Update markers if (markerIndex < this.markers.length) { this.markers[markerIndex].setLngLat(waypoint.getCoordinates()); + Object.defineProperty(this.markers[markerIndex], '_waypoint', { value: waypoint, writable: true }); } else { let marker = new mapboxgl.Marker().setLngLat(waypoint.getCoordinates()); + Object.defineProperty(marker, '_waypoint', { value: waypoint, writable: true }); marker.getElement().addEventListener('mouseover', (e) => { - currentWaypoint.set(waypoint); - marker.setPopup(waypointPopup); - marker.togglePopup(); + this.showWaypointPopup(marker._waypoint); e.stopPropagation(); }); marker.getElement().addEventListener('mouseout', () => { - marker.togglePopup(); + this.hideWaypointPopup(); + }); + marker.getElement().addEventListener('click', (e) => { + if (get(verticalFileView)) { + selectItem(new ListWaypointItem(this.fileId, marker._waypoint._data.index)); + e.stopPropagation(); + } }); - this.markers.push(marker); } markerIndex++; @@ -210,13 +216,44 @@ export class GPXLayer { if (get(currentTool) === Tool.ROUTING) { return; } - if (e.originalEvent.shiftKey) { - addSelect(this.fileId); + + let file = get(this.file)?.file; + if (!file) { + return; + } + + let item = undefined; + if (get(verticalFileView) && file.children.length > 1) { // Select inner item + let trackIndex = e.features[0].properties.trackIndex; + let segmentIndex = e.features[0].properties.segmentIndex; + item = file.children[trackIndex].children.length > 1 ? new ListTrackSegmentItem(this.fileId, trackIndex, segmentIndex) : new ListTrackItem(this.fileId, trackIndex); } else { - selectFile(this.fileId); + item = new ListFileItem(this.fileId); + } + + if (e.originalEvent.shiftKey || e.originalEvent.ctrlKey || e.originalEvent.metaKey) { + addSelectItem(item); + } else { + selectItem(item); } } + showWaypointPopup(waypoint: Waypoint) { + let marker = this.markers[waypoint._data.index]; + currentWaypoint.set(waypoint); + marker.setPopup(waypointPopup); + marker.togglePopup(); + } + + hideWaypointPopup() { + let waypoint = get(currentWaypoint); + if (waypoint) { + let marker = this.markers[waypoint._data.index]; + marker.togglePopup(); + } + } + + getGeoJSON(): GeoJSON.FeatureCollection { let file = get(this.file)?.file; if (!file) { @@ -242,9 +279,11 @@ export class GPXLayer { if (!feature.properties.opacity) { feature.properties.opacity = defaultOpacity; } - if (get(selection).has(new ListFileItem(this.fileId)) || get(selection).has(new ListTrackItem(this.fileId, trackIndex)) || get(selection).has(new ListTrackSegmentItem(this.fileId, trackIndex, segmentIndex))) { + if (get(selection).hasAnyParent(new ListTrackSegmentItem(this.fileId, trackIndex, segmentIndex)) || get(selection).hasAnyChildren(new ListWaypointsItem(this.fileId), true)) { feature.properties.weight = feature.properties.weight + 2; } + feature.properties.trackIndex = trackIndex; + feature.properties.segmentIndex = segmentIndex; segmentIndex++; if (segmentIndex >= file.trk[trackIndex].trkseg.length) { diff --git a/website/src/lib/db.ts b/website/src/lib/db.ts index c0797d14..d169b916 100644 --- a/website/src/lib/db.ts +++ b/website/src/lib/db.ts @@ -6,7 +6,7 @@ import { initTargetMapBounds, updateTargetMapBounds } from './stores'; import { mode } from 'mode-watcher'; import { defaultBasemap, defaultBasemapTree, defaultOverlayTree, defaultOverlays } from './assets/layers'; import { applyToOrderedSelectedItemsFromFile, selection } from '$lib/components/file-list/Selection'; -import { ListFileItem, ListItem, ListTrackItem, type ListLevel, ListTrackSegmentItem, ListWaypointItem } from '$lib/components/file-list/FileList'; +import { ListFileItem, ListItem, ListTrackItem, ListLevel, ListTrackSegmentItem, ListWaypointItem } from '$lib/components/file-list/FileList'; import { updateAnchorPoints } from '$lib/components/toolbar/tools/routing/Simplify'; enableMapSet(); @@ -121,12 +121,12 @@ export class GPXStatisticsTree { constructor(element: GPXFile | Track) { if (element instanceof GPXFile) { - this.level = 'file'; + this.level = ListLevel.FILE; element.children.forEach((child, index) => { this.statistics[index] = new GPXStatisticsTree(child); }); } else { - this.level = 'track'; + this.level = ListLevel.TRACK; element.children.forEach((child, index) => { this.statistics[index] = child.getStatistics(); }); @@ -374,21 +374,21 @@ export const dbUtils = { let file = original(draft)?.get(fileId); if (file) { let newFile = file; - if (level === 'file') { + if (level === ListLevel.FILE) { newFile = file.clone(); newFile._data.id = ids[index++]; - } else if (level === 'track') { + } else if (level === ListLevel.TRACK) { for (let item of items) { let trackIndex = (item as ListTrackItem).getTrackIndex(); newFile = newFile.replaceTracks(trackIndex + 1, trackIndex, [file.trk[trackIndex].clone()]); } - } else if (level === 'segment') { + } else if (level === ListLevel.SEGMENT) { for (let item of items) { let trackIndex = (item as ListTrackSegmentItem).getTrackIndex(); let segmentIndex = (item as ListTrackSegmentItem).getSegmentIndex(); newFile = newFile.replaceTrackSegments(trackIndex, segmentIndex + 1, segmentIndex, [file.trk[trackIndex].trkseg[segmentIndex].clone()]); } - } else if (level === 'waypoint') { + } else if (level === ListLevel.WAYPOINT) { for (let item of items) { let waypointIndex = (item as ListWaypointItem).getWaypointIndex(); newFile = newFile.replaceWaypoints(waypointIndex + 1, waypointIndex, [file.wpt[waypointIndex].clone()]); @@ -405,26 +405,26 @@ export const dbUtils = { } applyGlobal((draft) => { applyToOrderedSelectedItemsFromFile((fileId, level, items) => { - if (level === 'file') { + if (level === ListLevel.FILE) { draft.delete(fileId); } else { let file = original(draft)?.get(fileId); if (file) { let newFile = file; - if (level === 'track') { + if (level === ListLevel.TRACK) { for (let item of items) { let trackIndex = (item as ListTrackItem).getTrackIndex(); newFile = newFile.replaceTracks(trackIndex, trackIndex, []); } - } else if (level === 'segment') { + } else if (level === ListLevel.SEGMENT) { for (let item of items) { let trackIndex = (item as ListTrackSegmentItem).getTrackIndex(); let segmentIndex = (item as ListTrackSegmentItem).getSegmentIndex(); newFile = newFile.replaceTrackSegments(trackIndex, segmentIndex, segmentIndex, []); } - } else if (level === 'waypoints') { + } else if (level === ListLevel.WAYPOINTS) { newFile = newFile.replaceWaypoints(0, newFile.wpt.length - 1, []); - } else if (level === 'waypoint') { + } else if (level === ListLevel.WAYPOINT) { for (let item of items) { let waypointIndex = (item as ListWaypointItem).getWaypointIndex(); newFile = newFile.replaceWaypoints(waypointIndex, waypointIndex, []); diff --git a/website/src/locales/en.json b/website/src/locales/en.json index 374ecf42..28d13c96 100644 --- a/website/src/locales/en.json +++ b/website/src/locales/en.json @@ -204,5 +204,11 @@ "heartrate": "bpm", "cadence": "rpm", "power": "W" + }, + "gpx": { + "track": "Track", + "segment": "Segment", + "waypoint": "Waypoint", + "waypoints": "Waypoints" } } \ No newline at end of file