mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2025-09-08 11:17:51 +00:00
prettier config + format all, closes #175
This commit is contained in:
@@ -1,11 +1,17 @@
|
||||
|
||||
import { settings } from "$lib/db";
|
||||
import { gpxStatistics } from "$lib/stores";
|
||||
import { get } from "svelte/store";
|
||||
import { settings } from '$lib/db';
|
||||
import { gpxStatistics } from '$lib/stores';
|
||||
import { get } from 'svelte/store';
|
||||
|
||||
const { distanceMarkers, distanceUnits } = settings;
|
||||
|
||||
const stops = [[100, 0], [50, 7], [25, 8, 10], [10, 10], [5, 11], [1, 13]];
|
||||
const stops = [
|
||||
[100, 0],
|
||||
[50, 7],
|
||||
[25, 8, 10],
|
||||
[10, 10],
|
||||
[5, 11],
|
||||
[1, 13],
|
||||
];
|
||||
|
||||
export class DistanceMarkers {
|
||||
map: mapboxgl.Map;
|
||||
@@ -30,7 +36,7 @@ export class DistanceMarkers {
|
||||
} else {
|
||||
this.map.addSource('distance-markers', {
|
||||
type: 'geojson',
|
||||
data: this.getDistanceMarkersGeoJSON()
|
||||
data: this.getDistanceMarkersGeoJSON(),
|
||||
});
|
||||
}
|
||||
stops.forEach(([d, minzoom, maxzoom]) => {
|
||||
@@ -39,7 +45,14 @@ export class DistanceMarkers {
|
||||
id: `distance-markers-${d}`,
|
||||
type: 'symbol',
|
||||
source: 'distance-markers',
|
||||
filter: d === 5 ? ['any', ['==', ['get', 'level'], 5], ['==', ['get', 'level'], 25]] : ['==', ['get', 'level'], d],
|
||||
filter:
|
||||
d === 5
|
||||
? [
|
||||
'any',
|
||||
['==', ['get', 'level'], 5],
|
||||
['==', ['get', 'level'], 25],
|
||||
]
|
||||
: ['==', ['get', 'level'], d],
|
||||
minzoom: minzoom,
|
||||
maxzoom: maxzoom ?? 24,
|
||||
layout: {
|
||||
@@ -51,7 +64,7 @@ export class DistanceMarkers {
|
||||
'text-color': 'black',
|
||||
'text-halo-width': 2,
|
||||
'text-halo-color': 'white',
|
||||
}
|
||||
},
|
||||
});
|
||||
} else {
|
||||
this.map.moveLayer(`distance-markers-${d}`);
|
||||
@@ -64,13 +77,14 @@ export class DistanceMarkers {
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (e) { // No reliable way to check if the map is ready to add sources and layers
|
||||
} catch (e) {
|
||||
// No reliable way to check if the map is ready to add sources and layers
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
remove() {
|
||||
this.unsubscribes.forEach(unsubscribe => unsubscribe());
|
||||
this.unsubscribes.forEach((unsubscribe) => unsubscribe());
|
||||
}
|
||||
|
||||
getDistanceMarkersGeoJSON(): GeoJSON.FeatureCollection {
|
||||
@@ -79,20 +93,28 @@ export class DistanceMarkers {
|
||||
let features = [];
|
||||
let currentTargetDistance = 1;
|
||||
for (let i = 0; i < statistics.local.distance.total.length; i++) {
|
||||
if (statistics.local.distance.total[i] >= currentTargetDistance * (get(distanceUnits) === 'metric' ? 1 : 1.60934)) {
|
||||
if (
|
||||
statistics.local.distance.total[i] >=
|
||||
currentTargetDistance * (get(distanceUnits) === 'metric' ? 1 : 1.60934)
|
||||
) {
|
||||
let distance = currentTargetDistance.toFixed(0);
|
||||
let [level, minzoom] = stops.find(([d]) => currentTargetDistance % d === 0) ?? [0, 0];
|
||||
let [level, minzoom] = stops.find(([d]) => currentTargetDistance % d === 0) ?? [
|
||||
0, 0,
|
||||
];
|
||||
features.push({
|
||||
type: 'Feature',
|
||||
geometry: {
|
||||
type: 'Point',
|
||||
coordinates: [statistics.local.points[i].getLongitude(), statistics.local.points[i].getLatitude()]
|
||||
coordinates: [
|
||||
statistics.local.points[i].getLongitude(),
|
||||
statistics.local.points[i].getLatitude(),
|
||||
],
|
||||
},
|
||||
properties: {
|
||||
distance,
|
||||
level,
|
||||
minzoom,
|
||||
}
|
||||
},
|
||||
} as GeoJSON.Feature);
|
||||
currentTargetDistance += 1;
|
||||
}
|
||||
@@ -100,7 +122,7 @@ export class DistanceMarkers {
|
||||
|
||||
return {
|
||||
type: 'FeatureCollection',
|
||||
features
|
||||
features,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,14 +1,28 @@
|
||||
import { currentTool, map, Tool } from "$lib/stores";
|
||||
import { settings, type GPXFileWithStatistics, dbUtils } from "$lib/db";
|
||||
import { get, type Readable } from "svelte/store";
|
||||
import mapboxgl from "mapbox-gl";
|
||||
import { waypointPopup, deleteWaypoint, trackpointPopup } from "./GPXLayerPopup";
|
||||
import { addSelectItem, selectItem, selection } from "$lib/components/file-list/Selection";
|
||||
import { ListTrackSegmentItem, ListWaypointItem, ListWaypointsItem, ListTrackItem, ListFileItem, ListRootItem } from "$lib/components/file-list/FileList";
|
||||
import { getClosestLinePoint, getElevation, resetCursor, setGrabbingCursor, setPointerCursor, setScissorsCursor } from "$lib/utils";
|
||||
import { selectedWaypoint } from "$lib/components/toolbar/tools/Waypoint.svelte";
|
||||
import { MapPin, Square } from "lucide-static";
|
||||
import { getSymbolKey, symbols } from "$lib/assets/symbols";
|
||||
import { currentTool, map, Tool } from '$lib/stores';
|
||||
import { settings, type GPXFileWithStatistics, dbUtils } from '$lib/db';
|
||||
import { get, type Readable } from 'svelte/store';
|
||||
import mapboxgl from 'mapbox-gl';
|
||||
import { waypointPopup, deleteWaypoint, trackpointPopup } from './GPXLayerPopup';
|
||||
import { addSelectItem, selectItem, selection } from '$lib/components/file-list/Selection';
|
||||
import {
|
||||
ListTrackSegmentItem,
|
||||
ListWaypointItem,
|
||||
ListWaypointsItem,
|
||||
ListTrackItem,
|
||||
ListFileItem,
|
||||
ListRootItem,
|
||||
} from '$lib/components/file-list/FileList';
|
||||
import {
|
||||
getClosestLinePoint,
|
||||
getElevation,
|
||||
resetCursor,
|
||||
setGrabbingCursor,
|
||||
setPointerCursor,
|
||||
setScissorsCursor,
|
||||
} from '$lib/utils';
|
||||
import { selectedWaypoint } from '$lib/components/toolbar/tools/Waypoint.svelte';
|
||||
import { MapPin, Square } from 'lucide-static';
|
||||
import { getSymbolKey, symbols } from '$lib/assets/symbols';
|
||||
|
||||
const colors = [
|
||||
'#ff0000',
|
||||
@@ -21,7 +35,7 @@ const colors = [
|
||||
'#288228',
|
||||
'#9933ff',
|
||||
'#50f0be',
|
||||
'#8c645a'
|
||||
'#8c645a',
|
||||
];
|
||||
|
||||
const colorCount: { [key: string]: number } = {};
|
||||
@@ -56,12 +70,12 @@ class KeyDown {
|
||||
if (e.key === this.key) {
|
||||
this.down = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
onKeyUp = (e: KeyboardEvent) => {
|
||||
if (e.key === this.key) {
|
||||
this.down = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
isDown() {
|
||||
return this.down;
|
||||
}
|
||||
@@ -70,22 +84,26 @@ class KeyDown {
|
||||
function getMarkerForSymbol(symbol: string | undefined, layerColor: string) {
|
||||
let symbolSvg = symbol ? symbols[symbol]?.iconSvg : undefined;
|
||||
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
${Square
|
||||
.replace('width="24"', 'width="12"')
|
||||
.replace('height="24"', 'height="12"')
|
||||
.replace('stroke="currentColor"', 'stroke="SteelBlue"')
|
||||
.replace('stroke-width="2"', 'stroke-width="1.5" x="9.6" y="0.4"')
|
||||
.replace('fill="none"', `fill="${layerColor}"`)}
|
||||
${MapPin
|
||||
.replace('width="24"', '')
|
||||
.replace('height="24"', '')
|
||||
.replace('stroke="currentColor"', '')
|
||||
.replace('path', `path fill="#3fb1ce" stroke="SteelBlue" stroke-width="1"`)
|
||||
.replace('circle', `circle fill="${symbolSvg ? 'none' : 'white'}" stroke="${symbolSvg ? 'none' : 'white'}" stroke-width="2"`)}
|
||||
${symbolSvg?.replace('width="24"', 'width="10"')
|
||||
${Square.replace('width="24"', 'width="12"')
|
||||
.replace('height="24"', 'height="12"')
|
||||
.replace('stroke="currentColor"', 'stroke="SteelBlue"')
|
||||
.replace('stroke-width="2"', 'stroke-width="1.5" x="9.6" y="0.4"')
|
||||
.replace('fill="none"', `fill="${layerColor}"`)}
|
||||
${MapPin.replace('width="24"', '')
|
||||
.replace('height="24"', '')
|
||||
.replace('stroke="currentColor"', '')
|
||||
.replace('path', `path fill="#3fb1ce" stroke="SteelBlue" stroke-width="1"`)
|
||||
.replace(
|
||||
'circle',
|
||||
`circle fill="${symbolSvg ? 'none' : 'white'}" stroke="${symbolSvg ? 'none' : 'white'}" stroke-width="2"`
|
||||
)}
|
||||
${
|
||||
symbolSvg
|
||||
?.replace('width="24"', 'width="10"')
|
||||
.replace('height="24"', 'height="10"')
|
||||
.replace('stroke="currentColor"', 'stroke="white"')
|
||||
.replace('stroke-width="2"', 'stroke-width="2.5" x="7" y="5"') ?? ''}
|
||||
.replace('stroke-width="2"', 'stroke-width="2.5" x="7" y="5"') ?? ''
|
||||
}
|
||||
</svg>`;
|
||||
}
|
||||
|
||||
@@ -108,32 +126,40 @@ export class GPXLayer {
|
||||
layerOnClickBinded: (e: any) => void = this.layerOnClick.bind(this);
|
||||
layerOnContextMenuBinded: (e: any) => void = this.layerOnContextMenu.bind(this);
|
||||
|
||||
constructor(map: mapboxgl.Map, fileId: string, file: Readable<GPXFileWithStatistics | undefined>) {
|
||||
constructor(
|
||||
map: mapboxgl.Map,
|
||||
fileId: string,
|
||||
file: Readable<GPXFileWithStatistics | undefined>
|
||||
) {
|
||||
this.map = map;
|
||||
this.fileId = fileId;
|
||||
this.file = file;
|
||||
this.layerColor = getColor();
|
||||
this.unsubscribe.push(file.subscribe(this.updateBinded));
|
||||
this.unsubscribe.push(selection.subscribe($selection => {
|
||||
let newSelected = $selection.hasAnyChildren(new ListFileItem(this.fileId));
|
||||
if (this.selected || newSelected) {
|
||||
this.selected = newSelected;
|
||||
this.update();
|
||||
}
|
||||
if (newSelected) {
|
||||
this.moveToFront();
|
||||
}
|
||||
}));
|
||||
this.unsubscribe.push(
|
||||
selection.subscribe(($selection) => {
|
||||
let newSelected = $selection.hasAnyChildren(new ListFileItem(this.fileId));
|
||||
if (this.selected || newSelected) {
|
||||
this.selected = newSelected;
|
||||
this.update();
|
||||
}
|
||||
if (newSelected) {
|
||||
this.moveToFront();
|
||||
}
|
||||
})
|
||||
);
|
||||
this.unsubscribe.push(directionMarkers.subscribe(this.updateBinded));
|
||||
this.unsubscribe.push(currentTool.subscribe(tool => {
|
||||
if (tool === Tool.WAYPOINT && !this.draggable) {
|
||||
this.draggable = true;
|
||||
this.markers.forEach(marker => marker.setDraggable(true));
|
||||
} else if (tool !== Tool.WAYPOINT && this.draggable) {
|
||||
this.draggable = false;
|
||||
this.markers.forEach(marker => marker.setDraggable(false));
|
||||
}
|
||||
}));
|
||||
this.unsubscribe.push(
|
||||
currentTool.subscribe((tool) => {
|
||||
if (tool === Tool.WAYPOINT && !this.draggable) {
|
||||
this.draggable = true;
|
||||
this.markers.forEach((marker) => marker.setDraggable(true));
|
||||
} else if (tool !== Tool.WAYPOINT && this.draggable) {
|
||||
this.draggable = false;
|
||||
this.markers.forEach((marker) => marker.setDraggable(false));
|
||||
}
|
||||
})
|
||||
);
|
||||
this.draggable = get(currentTool) === Tool.WAYPOINT;
|
||||
|
||||
this.map.on('style.import.load', this.updateBinded);
|
||||
@@ -149,7 +175,11 @@ export class GPXLayer {
|
||||
return;
|
||||
}
|
||||
|
||||
if (file._data.style && file._data.style.color && this.layerColor !== `#${file._data.style.color}`) {
|
||||
if (
|
||||
file._data.style &&
|
||||
file._data.style.color &&
|
||||
this.layerColor !== `#${file._data.style.color}`
|
||||
) {
|
||||
decrementColor(this.layerColor);
|
||||
this.layerColor = `#${file._data.style.color}`;
|
||||
}
|
||||
@@ -161,7 +191,7 @@ export class GPXLayer {
|
||||
} else {
|
||||
this.map.addSource(this.fileId, {
|
||||
type: 'geojson',
|
||||
data: this.getGeoJSON()
|
||||
data: this.getGeoJSON(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -172,13 +202,13 @@ export class GPXLayer {
|
||||
source: this.fileId,
|
||||
layout: {
|
||||
'line-join': 'round',
|
||||
'line-cap': 'round'
|
||||
'line-cap': 'round',
|
||||
},
|
||||
paint: {
|
||||
'line-color': ['get', 'color'],
|
||||
'line-width': ['get', 'width'],
|
||||
'line-opacity': ['get', 'opacity']
|
||||
}
|
||||
'line-opacity': ['get', 'opacity'],
|
||||
},
|
||||
});
|
||||
|
||||
this.map.on('click', this.fileId, this.layerOnClickBinded);
|
||||
@@ -190,27 +220,30 @@ export class GPXLayer {
|
||||
|
||||
if (get(directionMarkers)) {
|
||||
if (!this.map.getLayer(this.fileId + '-direction')) {
|
||||
this.map.addLayer({
|
||||
id: this.fileId + '-direction',
|
||||
type: 'symbol',
|
||||
source: this.fileId,
|
||||
layout: {
|
||||
'text-field': '»',
|
||||
'text-offset': [0, -0.1],
|
||||
'text-keep-upright': false,
|
||||
'text-max-angle': 361,
|
||||
'text-allow-overlap': true,
|
||||
'text-font': ['Open Sans Bold'],
|
||||
'symbol-placement': 'line',
|
||||
'symbol-spacing': 20,
|
||||
this.map.addLayer(
|
||||
{
|
||||
id: this.fileId + '-direction',
|
||||
type: 'symbol',
|
||||
source: this.fileId,
|
||||
layout: {
|
||||
'text-field': '»',
|
||||
'text-offset': [0, -0.1],
|
||||
'text-keep-upright': false,
|
||||
'text-max-angle': 361,
|
||||
'text-allow-overlap': true,
|
||||
'text-font': ['Open Sans Bold'],
|
||||
'symbol-placement': 'line',
|
||||
'symbol-spacing': 20,
|
||||
},
|
||||
paint: {
|
||||
'text-color': 'white',
|
||||
'text-opacity': 0.7,
|
||||
'text-halo-width': 0.2,
|
||||
'text-halo-color': 'white',
|
||||
},
|
||||
},
|
||||
paint: {
|
||||
'text-color': 'white',
|
||||
'text-opacity': 0.7,
|
||||
'text-halo-width': 0.2,
|
||||
'text-halo-color': 'white'
|
||||
}
|
||||
}, this.map.getLayer('distance-markers') ? 'distance-markers' : undefined);
|
||||
this.map.getLayer('distance-markers') ? 'distance-markers' : undefined
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (this.map.getLayer(this.fileId + '-direction')) {
|
||||
@@ -225,23 +258,53 @@ export class GPXLayer {
|
||||
}
|
||||
});
|
||||
|
||||
this.map.setFilter(this.fileId, ['any', ...visibleItems.map(([trackIndex, segmentIndex]) => ['all', ['==', 'trackIndex', trackIndex], ['==', 'segmentIndex', segmentIndex]])], { validate: false });
|
||||
this.map.setFilter(
|
||||
this.fileId,
|
||||
[
|
||||
'any',
|
||||
...visibleItems.map(([trackIndex, segmentIndex]) => [
|
||||
'all',
|
||||
['==', 'trackIndex', trackIndex],
|
||||
['==', 'segmentIndex', segmentIndex],
|
||||
]),
|
||||
],
|
||||
{ validate: false }
|
||||
);
|
||||
if (this.map.getLayer(this.fileId + '-direction')) {
|
||||
this.map.setFilter(this.fileId + '-direction', ['any', ...visibleItems.map(([trackIndex, segmentIndex]) => ['all', ['==', 'trackIndex', trackIndex], ['==', 'segmentIndex', segmentIndex]])], { validate: false });
|
||||
this.map.setFilter(
|
||||
this.fileId + '-direction',
|
||||
[
|
||||
'any',
|
||||
...visibleItems.map(([trackIndex, segmentIndex]) => [
|
||||
'all',
|
||||
['==', 'trackIndex', trackIndex],
|
||||
['==', 'segmentIndex', segmentIndex],
|
||||
]),
|
||||
],
|
||||
{ validate: false }
|
||||
);
|
||||
}
|
||||
} catch (e) { // No reliable way to check if the map is ready to add sources and layers
|
||||
} catch (e) {
|
||||
// No reliable way to check if the map is ready to add sources and layers
|
||||
return;
|
||||
}
|
||||
|
||||
let markerIndex = 0;
|
||||
|
||||
if (get(selection).hasAnyChildren(new ListFileItem(this.fileId))) {
|
||||
file.wpt.forEach((waypoint) => { // Update markers
|
||||
file.wpt.forEach((waypoint) => {
|
||||
// Update markers
|
||||
let symbolKey = getSymbolKey(waypoint.sym);
|
||||
if (markerIndex < this.markers.length) {
|
||||
this.markers[markerIndex].getElement().innerHTML = getMarkerForSymbol(symbolKey, this.layerColor);
|
||||
this.markers[markerIndex].getElement().innerHTML = getMarkerForSymbol(
|
||||
symbolKey,
|
||||
this.layerColor
|
||||
);
|
||||
this.markers[markerIndex].setLngLat(waypoint.getCoordinates());
|
||||
Object.defineProperty(this.markers[markerIndex], '_waypoint', { value: waypoint, writable: true });
|
||||
Object.defineProperty(this.markers[markerIndex], '_waypoint', {
|
||||
value: waypoint,
|
||||
writable: true,
|
||||
});
|
||||
} else {
|
||||
let element = document.createElement('div');
|
||||
element.classList.add('w-8', 'h-8', 'drop-shadow-xl');
|
||||
@@ -249,7 +312,7 @@ export class GPXLayer {
|
||||
let marker = new mapboxgl.Marker({
|
||||
draggable: this.draggable,
|
||||
element,
|
||||
anchor: 'bottom'
|
||||
anchor: 'bottom',
|
||||
}).setLngLat(waypoint.getCoordinates());
|
||||
Object.defineProperty(marker, '_waypoint', { value: waypoint, writable: true });
|
||||
let dragEndTimestamp = 0;
|
||||
@@ -272,10 +335,20 @@ export class GPXLayer {
|
||||
}
|
||||
|
||||
if (get(treeFileView)) {
|
||||
if ((e.ctrlKey || e.metaKey) && get(selection).hasAnyChildren(new ListWaypointsItem(this.fileId), false)) {
|
||||
addSelectItem(new ListWaypointItem(this.fileId, marker._waypoint._data.index));
|
||||
if (
|
||||
(e.ctrlKey || e.metaKey) &&
|
||||
get(selection).hasAnyChildren(
|
||||
new ListWaypointsItem(this.fileId),
|
||||
false
|
||||
)
|
||||
) {
|
||||
addSelectItem(
|
||||
new ListWaypointItem(this.fileId, marker._waypoint._data.index)
|
||||
);
|
||||
} else {
|
||||
selectItem(new ListWaypointItem(this.fileId, marker._waypoint._data.index));
|
||||
selectItem(
|
||||
new ListWaypointItem(this.fileId, marker._waypoint._data.index)
|
||||
);
|
||||
}
|
||||
} else if (get(currentTool) === Tool.WAYPOINT) {
|
||||
selectedWaypoint.set([marker._waypoint, this.fileId]);
|
||||
@@ -298,12 +371,12 @@ export class GPXLayer {
|
||||
let wpt = file.wpt[marker._waypoint._data.index];
|
||||
wpt.setCoordinates({
|
||||
lat: latLng.lat,
|
||||
lon: latLng.lng
|
||||
lon: latLng.lng,
|
||||
});
|
||||
wpt.ele = ele[0];
|
||||
});
|
||||
});
|
||||
dragEndTimestamp = Date.now()
|
||||
dragEndTimestamp = Date.now();
|
||||
});
|
||||
this.markers.push(marker);
|
||||
}
|
||||
@@ -311,7 +384,8 @@ export class GPXLayer {
|
||||
});
|
||||
}
|
||||
|
||||
while (markerIndex < this.markers.length) { // Remove extra markers
|
||||
while (markerIndex < this.markers.length) {
|
||||
// Remove extra markers
|
||||
this.markers.pop()?.remove();
|
||||
}
|
||||
|
||||
@@ -364,7 +438,10 @@ export class GPXLayer {
|
||||
this.map.moveLayer(this.fileId);
|
||||
}
|
||||
if (this.map.getLayer(this.fileId + '-direction')) {
|
||||
this.map.moveLayer(this.fileId + '-direction', this.map.getLayer('distance-markers') ? 'distance-markers' : undefined);
|
||||
this.map.moveLayer(
|
||||
this.fileId + '-direction',
|
||||
this.map.getLayer('distance-markers') ? 'distance-markers' : undefined
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -372,7 +449,12 @@ export class GPXLayer {
|
||||
let trackIndex = e.features[0].properties.trackIndex;
|
||||
let segmentIndex = e.features[0].properties.segmentIndex;
|
||||
|
||||
if (get(currentTool) === Tool.SCISSORS && get(selection).hasAnyParent(new ListTrackSegmentItem(this.fileId, trackIndex, segmentIndex))) {
|
||||
if (
|
||||
get(currentTool) === Tool.SCISSORS &&
|
||||
get(selection).hasAnyParent(
|
||||
new ListTrackSegmentItem(this.fileId, trackIndex, segmentIndex)
|
||||
)
|
||||
) {
|
||||
setScissorsCursor();
|
||||
} else {
|
||||
setPointerCursor();
|
||||
@@ -390,22 +472,36 @@ export class GPXLayer {
|
||||
|
||||
const file = get(this.file)?.file;
|
||||
if (file) {
|
||||
const closest = getClosestLinePoint(file.trk[trackIndex].trkseg[segmentIndex].trkpt, { lat: e.lngLat.lat, lon: e.lngLat.lng });
|
||||
const closest = getClosestLinePoint(
|
||||
file.trk[trackIndex].trkseg[segmentIndex].trkpt,
|
||||
{ lat: e.lngLat.lat, lon: e.lngLat.lng }
|
||||
);
|
||||
trackpointPopup?.setItem({ item: closest, fileId: this.fileId });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
layerOnClick(e: any) {
|
||||
if (get(currentTool) === Tool.ROUTING && get(selection).hasAnyChildren(new ListRootItem(), true, ['waypoints'])) {
|
||||
if (
|
||||
get(currentTool) === Tool.ROUTING &&
|
||||
get(selection).hasAnyChildren(new ListRootItem(), true, ['waypoints'])
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
let trackIndex = e.features[0].properties.trackIndex;
|
||||
let segmentIndex = e.features[0].properties.segmentIndex;
|
||||
|
||||
if (get(currentTool) === Tool.SCISSORS && get(selection).hasAnyParent(new ListTrackSegmentItem(this.fileId, trackIndex, segmentIndex))) {
|
||||
dbUtils.split(this.fileId, trackIndex, segmentIndex, { lat: e.lngLat.lat, lon: e.lngLat.lng });
|
||||
if (
|
||||
get(currentTool) === Tool.SCISSORS &&
|
||||
get(selection).hasAnyParent(
|
||||
new ListTrackSegmentItem(this.fileId, trackIndex, segmentIndex)
|
||||
)
|
||||
) {
|
||||
dbUtils.split(this.fileId, trackIndex, segmentIndex, {
|
||||
lat: e.lngLat.lat,
|
||||
lon: e.lngLat.lng,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -415,8 +511,12 @@ export class GPXLayer {
|
||||
}
|
||||
|
||||
let item = undefined;
|
||||
if (get(treeFileView) && file.getSegments().length > 1) { // Select inner item
|
||||
item = file.children[trackIndex].children.length > 1 ? new ListTrackSegmentItem(this.fileId, trackIndex, segmentIndex) : new ListTrackItem(this.fileId, trackIndex);
|
||||
if (get(treeFileView) && file.getSegments().length > 1) {
|
||||
// Select inner item
|
||||
item =
|
||||
file.children[trackIndex].children.length > 1
|
||||
? new ListTrackSegmentItem(this.fileId, trackIndex, segmentIndex)
|
||||
: new ListTrackItem(this.fileId, trackIndex);
|
||||
} else {
|
||||
item = new ListFileItem(this.fileId);
|
||||
}
|
||||
@@ -439,13 +539,14 @@ export class GPXLayer {
|
||||
if (!file) {
|
||||
return {
|
||||
type: 'FeatureCollection',
|
||||
features: []
|
||||
features: [],
|
||||
};
|
||||
}
|
||||
|
||||
let data = file.toGeoJSON();
|
||||
|
||||
let trackIndex = 0, segmentIndex = 0;
|
||||
let trackIndex = 0,
|
||||
segmentIndex = 0;
|
||||
for (let feature of data.features) {
|
||||
if (!feature.properties) {
|
||||
feature.properties = {};
|
||||
@@ -459,7 +560,12 @@ export class GPXLayer {
|
||||
if (!feature.properties.width) {
|
||||
feature.properties.width = get(defaultWidth);
|
||||
}
|
||||
if (get(selection).hasAnyParent(new ListTrackSegmentItem(this.fileId, trackIndex, segmentIndex)) || get(selection).hasAnyChildren(new ListWaypointsItem(this.fileId), true)) {
|
||||
if (
|
||||
get(selection).hasAnyParent(
|
||||
new ListTrackSegmentItem(this.fileId, trackIndex, segmentIndex)
|
||||
) ||
|
||||
get(selection).hasAnyChildren(new ListWaypointsItem(this.fileId), true)
|
||||
) {
|
||||
feature.properties.width = feature.properties.width + 2;
|
||||
feature.properties.opacity = Math.min(1, feature.properties.opacity + 0.1);
|
||||
}
|
||||
@@ -474,4 +580,4 @@ export class GPXLayer {
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { dbUtils } from "$lib/db";
|
||||
import { MapPopup } from "$lib/components/MapPopup";
|
||||
import { dbUtils } from '$lib/db';
|
||||
import { MapPopup } from '$lib/components/MapPopup';
|
||||
|
||||
export let waypointPopup: MapPopup | null = null;
|
||||
export let trackpointPopup: MapPopup | null = null;
|
||||
@@ -11,14 +11,14 @@ export function createPopups(map: mapboxgl.Map) {
|
||||
focusAfterOpen: false,
|
||||
maxWidth: undefined,
|
||||
offset: {
|
||||
'top': [0, 0],
|
||||
top: [0, 0],
|
||||
'top-left': [0, 0],
|
||||
'top-right': [0, 0],
|
||||
'bottom': [0, -30],
|
||||
bottom: [0, -30],
|
||||
'bottom-left': [0, -30],
|
||||
'bottom-right': [0, -30],
|
||||
'left': [10, -15],
|
||||
'right': [-10, -15],
|
||||
left: [10, -15],
|
||||
right: [-10, -15],
|
||||
},
|
||||
});
|
||||
trackpointPopup = new MapPopup(map, {
|
||||
@@ -41,4 +41,4 @@ export function removePopups() {
|
||||
|
||||
export function deleteWaypoint(fileId: string, waypointIndex: number) {
|
||||
dbUtils.applyToFile(fileId, (file) => file.replaceWaypoints(waypointIndex, waypointIndex, []));
|
||||
}
|
||||
}
|
||||
|
@@ -1,56 +1,56 @@
|
||||
<script lang="ts">
|
||||
import { map, gpxLayers } from '$lib/stores';
|
||||
import { GPXLayer } from './GPXLayer';
|
||||
import { fileObservers } from '$lib/db';
|
||||
import { DistanceMarkers } from './DistanceMarkers';
|
||||
import { StartEndMarkers } from './StartEndMarkers';
|
||||
import { onDestroy } from 'svelte';
|
||||
import { createPopups, removePopups } from './GPXLayerPopup';
|
||||
import { map, gpxLayers } from '$lib/stores';
|
||||
import { GPXLayer } from './GPXLayer';
|
||||
import { fileObservers } from '$lib/db';
|
||||
import { DistanceMarkers } from './DistanceMarkers';
|
||||
import { StartEndMarkers } from './StartEndMarkers';
|
||||
import { onDestroy } from 'svelte';
|
||||
import { createPopups, removePopups } from './GPXLayerPopup';
|
||||
|
||||
let distanceMarkers: DistanceMarkers | undefined = undefined;
|
||||
let startEndMarkers: StartEndMarkers | undefined = undefined;
|
||||
let distanceMarkers: DistanceMarkers | undefined = undefined;
|
||||
let startEndMarkers: StartEndMarkers | undefined = undefined;
|
||||
|
||||
$: if ($map && $fileObservers) {
|
||||
// remove layers for deleted files
|
||||
gpxLayers.forEach((layer, fileId) => {
|
||||
if (!$fileObservers.has(fileId)) {
|
||||
layer.remove();
|
||||
gpxLayers.delete(fileId);
|
||||
} else if ($map !== layer.map) {
|
||||
layer.updateMap($map);
|
||||
}
|
||||
});
|
||||
// add layers for new files
|
||||
$fileObservers.forEach((file, fileId) => {
|
||||
if (!gpxLayers.has(fileId)) {
|
||||
gpxLayers.set(fileId, new GPXLayer($map, fileId, file));
|
||||
}
|
||||
});
|
||||
}
|
||||
$: if ($map && $fileObservers) {
|
||||
// remove layers for deleted files
|
||||
gpxLayers.forEach((layer, fileId) => {
|
||||
if (!$fileObservers.has(fileId)) {
|
||||
layer.remove();
|
||||
gpxLayers.delete(fileId);
|
||||
} else if ($map !== layer.map) {
|
||||
layer.updateMap($map);
|
||||
}
|
||||
});
|
||||
// add layers for new files
|
||||
$fileObservers.forEach((file, fileId) => {
|
||||
if (!gpxLayers.has(fileId)) {
|
||||
gpxLayers.set(fileId, new GPXLayer($map, fileId, file));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$: if ($map) {
|
||||
if (distanceMarkers) {
|
||||
distanceMarkers.remove();
|
||||
}
|
||||
if (startEndMarkers) {
|
||||
startEndMarkers.remove();
|
||||
}
|
||||
createPopups($map);
|
||||
distanceMarkers = new DistanceMarkers($map);
|
||||
startEndMarkers = new StartEndMarkers($map);
|
||||
}
|
||||
$: if ($map) {
|
||||
if (distanceMarkers) {
|
||||
distanceMarkers.remove();
|
||||
}
|
||||
if (startEndMarkers) {
|
||||
startEndMarkers.remove();
|
||||
}
|
||||
createPopups($map);
|
||||
distanceMarkers = new DistanceMarkers($map);
|
||||
startEndMarkers = new StartEndMarkers($map);
|
||||
}
|
||||
|
||||
onDestroy(() => {
|
||||
gpxLayers.forEach((layer) => layer.remove());
|
||||
gpxLayers.clear();
|
||||
removePopups();
|
||||
if (distanceMarkers) {
|
||||
distanceMarkers.remove();
|
||||
distanceMarkers = undefined;
|
||||
}
|
||||
if (startEndMarkers) {
|
||||
startEndMarkers.remove();
|
||||
startEndMarkers = undefined;
|
||||
}
|
||||
});
|
||||
onDestroy(() => {
|
||||
gpxLayers.forEach((layer) => layer.remove());
|
||||
gpxLayers.clear();
|
||||
removePopups();
|
||||
if (distanceMarkers) {
|
||||
distanceMarkers.remove();
|
||||
distanceMarkers = undefined;
|
||||
}
|
||||
if (startEndMarkers) {
|
||||
startEndMarkers.remove();
|
||||
startEndMarkers = undefined;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { gpxStatistics, slicedGPXStatistics, currentTool, Tool } from "$lib/stores";
|
||||
import mapboxgl from "mapbox-gl";
|
||||
import { get } from "svelte/store";
|
||||
import { gpxStatistics, slicedGPXStatistics, currentTool, Tool } from '$lib/stores';
|
||||
import mapboxgl from 'mapbox-gl';
|
||||
import { get } from 'svelte/store';
|
||||
|
||||
export class StartEndMarkers {
|
||||
map: mapboxgl.Map;
|
||||
@@ -16,7 +16,8 @@ export class StartEndMarkers {
|
||||
let endElement = document.createElement('div');
|
||||
startElement.className = `h-4 w-4 rounded-full bg-green-500 border-2 border-white`;
|
||||
endElement.className = `h-4 w-4 rounded-full border-2 border-white`;
|
||||
endElement.style.background = 'repeating-conic-gradient(#fff 0 90deg, #000 0 180deg) 0 0/8px 8px round';
|
||||
endElement.style.background =
|
||||
'repeating-conic-gradient(#fff 0 90deg, #000 0 180deg) 0 0/8px 8px round';
|
||||
|
||||
this.start = new mapboxgl.Marker({ element: startElement });
|
||||
this.end = new mapboxgl.Marker({ element: endElement });
|
||||
@@ -31,7 +32,11 @@ export class StartEndMarkers {
|
||||
let statistics = get(slicedGPXStatistics)?.[0] ?? get(gpxStatistics);
|
||||
if (statistics.local.points.length > 0 && tool !== Tool.ROUTING) {
|
||||
this.start.setLngLat(statistics.local.points[0].getCoordinates()).addTo(this.map);
|
||||
this.end.setLngLat(statistics.local.points[statistics.local.points.length - 1].getCoordinates()).addTo(this.map);
|
||||
this.end
|
||||
.setLngLat(
|
||||
statistics.local.points[statistics.local.points.length - 1].getCoordinates()
|
||||
)
|
||||
.addTo(this.map);
|
||||
} else {
|
||||
this.start.remove();
|
||||
this.end.remove();
|
||||
@@ -39,9 +44,9 @@ export class StartEndMarkers {
|
||||
}
|
||||
|
||||
remove() {
|
||||
this.unsubscribes.forEach(unsubscribe => unsubscribe());
|
||||
this.unsubscribes.forEach((unsubscribe) => unsubscribe());
|
||||
|
||||
this.start.remove();
|
||||
this.end.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,51 +1,51 @@
|
||||
<script lang="ts">
|
||||
import type { TrackPoint } from 'gpx';
|
||||
import type { PopupItem } from '$lib/components/MapPopup';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import WithUnits from '$lib/components/WithUnits.svelte';
|
||||
import { Compass, Mountain, Timer, ClipboardCopy } from 'lucide-svelte';
|
||||
import { df } from '$lib/utils';
|
||||
import { _ } from 'svelte-i18n';
|
||||
import type { TrackPoint } from 'gpx';
|
||||
import type { PopupItem } from '$lib/components/MapPopup';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import WithUnits from '$lib/components/WithUnits.svelte';
|
||||
import { Compass, Mountain, Timer, ClipboardCopy } from 'lucide-svelte';
|
||||
import { df } from '$lib/utils';
|
||||
import { _ } from 'svelte-i18n';
|
||||
|
||||
export let trackpoint: PopupItem<TrackPoint>;
|
||||
export let trackpoint: PopupItem<TrackPoint>;
|
||||
</script>
|
||||
|
||||
<Card.Root class="border-none shadow-md text-base p-2">
|
||||
<Card.Header class="p-0">
|
||||
<Card.Title class="text-md"></Card.Title>
|
||||
</Card.Header>
|
||||
<Card.Content class="flex flex-col p-0 text-xs gap-1">
|
||||
<div class="flex flex-row items-center gap-1">
|
||||
<Compass size="14" />
|
||||
{trackpoint.item.getLatitude().toFixed(6)}° {trackpoint.item
|
||||
.getLongitude()
|
||||
.toFixed(6)}°
|
||||
</div>
|
||||
{#if trackpoint.item.ele !== undefined}
|
||||
<div class="flex flex-row items-center gap-1">
|
||||
<Mountain size="14" />
|
||||
<WithUnits value={trackpoint.item.ele} type="elevation" />
|
||||
</div>
|
||||
{/if}
|
||||
{#if trackpoint.item.time}
|
||||
<div class="flex flex-row items-center gap-1">
|
||||
<Timer size="14" />
|
||||
{df.format(trackpoint.item.time)}
|
||||
</div>
|
||||
{/if}
|
||||
<Button
|
||||
class="w-full px-2 py-1 h-6 justify-start mt-0.5"
|
||||
variant="secondary"
|
||||
on:click={() => {
|
||||
navigator.clipboard.writeText(
|
||||
`${trackpoint.item.getLatitude().toFixed(6)}, ${trackpoint.item.getLongitude().toFixed(6)}`
|
||||
);
|
||||
trackpoint.hide?.();
|
||||
}}
|
||||
>
|
||||
<ClipboardCopy size="16" class="mr-1" />
|
||||
{$_('menu.copy_coordinates')}
|
||||
</Button>
|
||||
</Card.Content>
|
||||
<Card.Header class="p-0">
|
||||
<Card.Title class="text-md"></Card.Title>
|
||||
</Card.Header>
|
||||
<Card.Content class="flex flex-col p-0 text-xs gap-1">
|
||||
<div class="flex flex-row items-center gap-1">
|
||||
<Compass size="14" />
|
||||
{trackpoint.item.getLatitude().toFixed(6)}° {trackpoint.item
|
||||
.getLongitude()
|
||||
.toFixed(6)}°
|
||||
</div>
|
||||
{#if trackpoint.item.ele !== undefined}
|
||||
<div class="flex flex-row items-center gap-1">
|
||||
<Mountain size="14" />
|
||||
<WithUnits value={trackpoint.item.ele} type="elevation" />
|
||||
</div>
|
||||
{/if}
|
||||
{#if trackpoint.item.time}
|
||||
<div class="flex flex-row items-center gap-1">
|
||||
<Timer size="14" />
|
||||
{df.format(trackpoint.item.time)}
|
||||
</div>
|
||||
{/if}
|
||||
<Button
|
||||
class="w-full px-2 py-1 h-6 justify-start mt-0.5"
|
||||
variant="secondary"
|
||||
on:click={() => {
|
||||
navigator.clipboard.writeText(
|
||||
`${trackpoint.item.getLatitude().toFixed(6)}, ${trackpoint.item.getLongitude().toFixed(6)}`
|
||||
);
|
||||
trackpoint.hide?.();
|
||||
}}
|
||||
>
|
||||
<ClipboardCopy size="16" class="mr-1" />
|
||||
{$_('menu.copy_coordinates')}
|
||||
</Button>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
@@ -1,102 +1,104 @@
|
||||
<script lang="ts">
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import Shortcut from '$lib/components/Shortcut.svelte';
|
||||
import { deleteWaypoint } from './GPXLayerPopup';
|
||||
import WithUnits from '$lib/components/WithUnits.svelte';
|
||||
import { Dot, ExternalLink, Trash2 } from 'lucide-svelte';
|
||||
import { Tool, currentTool } from '$lib/stores';
|
||||
import { getSymbolKey, symbols } from '$lib/assets/symbols';
|
||||
import { _ } from 'svelte-i18n';
|
||||
import sanitizeHtml from 'sanitize-html';
|
||||
import type { Waypoint } from 'gpx';
|
||||
import type { PopupItem } from '$lib/components/MapPopup';
|
||||
import { ScrollArea } from '$lib/components/ui/scroll-area/index.js';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import Shortcut from '$lib/components/Shortcut.svelte';
|
||||
import { deleteWaypoint } from './GPXLayerPopup';
|
||||
import WithUnits from '$lib/components/WithUnits.svelte';
|
||||
import { Dot, ExternalLink, Trash2 } from 'lucide-svelte';
|
||||
import { Tool, currentTool } from '$lib/stores';
|
||||
import { getSymbolKey, symbols } from '$lib/assets/symbols';
|
||||
import { _ } from 'svelte-i18n';
|
||||
import sanitizeHtml from 'sanitize-html';
|
||||
import type { Waypoint } from 'gpx';
|
||||
import type { PopupItem } from '$lib/components/MapPopup';
|
||||
import { ScrollArea } from '$lib/components/ui/scroll-area/index.js';
|
||||
|
||||
export let waypoint: PopupItem<Waypoint>;
|
||||
export let waypoint: PopupItem<Waypoint>;
|
||||
|
||||
$: symbolKey = waypoint ? getSymbolKey(waypoint.item.sym) : undefined;
|
||||
$: symbolKey = waypoint ? getSymbolKey(waypoint.item.sym) : undefined;
|
||||
|
||||
function sanitize(text: string | undefined): string {
|
||||
if (text === undefined) {
|
||||
return '';
|
||||
}
|
||||
return sanitizeHtml(text, {
|
||||
allowedTags: ['a', 'br', 'img'],
|
||||
allowedAttributes: {
|
||||
a: ['href', 'target'],
|
||||
img: ['src']
|
||||
}
|
||||
}).trim();
|
||||
}
|
||||
function sanitize(text: string | undefined): string {
|
||||
if (text === undefined) {
|
||||
return '';
|
||||
}
|
||||
return sanitizeHtml(text, {
|
||||
allowedTags: ['a', 'br', 'img'],
|
||||
allowedAttributes: {
|
||||
a: ['href', 'target'],
|
||||
img: ['src'],
|
||||
},
|
||||
}).trim();
|
||||
}
|
||||
</script>
|
||||
|
||||
<Card.Root class="border-none shadow-md text-base p-2 max-w-[50dvw]">
|
||||
<Card.Header class="p-0">
|
||||
<Card.Title class="text-md">
|
||||
{#if waypoint.item.link && waypoint.item.link.attributes && waypoint.item.link.attributes.href}
|
||||
<a href={waypoint.item.link.attributes.href} target="_blank">
|
||||
{waypoint.item.name ?? waypoint.item.link.attributes.href}
|
||||
<ExternalLink size="12" class="inline-block mb-1.5" />
|
||||
</a>
|
||||
{:else}
|
||||
{waypoint.item.name ?? $_('gpx.waypoint')}
|
||||
{/if}
|
||||
</Card.Title>
|
||||
</Card.Header>
|
||||
<Card.Content class="flex flex-col text-sm p-0">
|
||||
<div class="flex flex-row items-center text-muted-foreground text-xs whitespace-nowrap">
|
||||
{#if symbolKey}
|
||||
<span>
|
||||
{#if symbols[symbolKey].icon}
|
||||
<svelte:component
|
||||
this={symbols[symbolKey].icon}
|
||||
size="12"
|
||||
class="inline-block mb-0.5"
|
||||
/>
|
||||
{:else}
|
||||
<span class="w-4 inline-block" />
|
||||
{/if}
|
||||
{$_(`gpx.symbol.${symbolKey}`)}
|
||||
</span>
|
||||
<Dot size="16" />
|
||||
{/if}
|
||||
{waypoint.item.getLatitude().toFixed(6)}° {waypoint.item.getLongitude().toFixed(6)}°
|
||||
{#if waypoint.item.ele !== undefined}
|
||||
<Dot size="16" />
|
||||
<WithUnits value={waypoint.item.ele} type="elevation" />
|
||||
{/if}
|
||||
</div>
|
||||
<ScrollArea class="flex flex-col" viewportClasses="max-h-[30dvh]">
|
||||
{#if waypoint.item.desc}
|
||||
<span class="whitespace-pre-wrap">{@html sanitize(waypoint.item.desc)}</span>
|
||||
{/if}
|
||||
{#if waypoint.item.cmt && waypoint.item.cmt !== waypoint.item.desc}
|
||||
<span class="whitespace-pre-wrap">{@html sanitize(waypoint.item.cmt)}</span>
|
||||
{/if}
|
||||
</ScrollArea>
|
||||
{#if $currentTool === Tool.WAYPOINT}
|
||||
<Button
|
||||
class="mt-2 w-full px-2 py-1 h-8 justify-start"
|
||||
variant="outline"
|
||||
on:click={() => deleteWaypoint(waypoint.fileId, waypoint.item._data.index)}
|
||||
>
|
||||
<Trash2 size="16" class="mr-1" />
|
||||
{$_('menu.delete')}
|
||||
<Shortcut shift={true} click={true} />
|
||||
</Button>
|
||||
{/if}
|
||||
</Card.Content>
|
||||
<Card.Header class="p-0">
|
||||
<Card.Title class="text-md">
|
||||
{#if waypoint.item.link && waypoint.item.link.attributes && waypoint.item.link.attributes.href}
|
||||
<a href={waypoint.item.link.attributes.href} target="_blank">
|
||||
{waypoint.item.name ?? waypoint.item.link.attributes.href}
|
||||
<ExternalLink size="12" class="inline-block mb-1.5" />
|
||||
</a>
|
||||
{:else}
|
||||
{waypoint.item.name ?? $_('gpx.waypoint')}
|
||||
{/if}
|
||||
</Card.Title>
|
||||
</Card.Header>
|
||||
<Card.Content class="flex flex-col text-sm p-0">
|
||||
<div class="flex flex-row items-center text-muted-foreground text-xs whitespace-nowrap">
|
||||
{#if symbolKey}
|
||||
<span>
|
||||
{#if symbols[symbolKey].icon}
|
||||
<svelte:component
|
||||
this={symbols[symbolKey].icon}
|
||||
size="12"
|
||||
class="inline-block mb-0.5"
|
||||
/>
|
||||
{:else}
|
||||
<span class="w-4 inline-block" />
|
||||
{/if}
|
||||
{$_(`gpx.symbol.${symbolKey}`)}
|
||||
</span>
|
||||
<Dot size="16" />
|
||||
{/if}
|
||||
{waypoint.item.getLatitude().toFixed(6)}° {waypoint.item
|
||||
.getLongitude()
|
||||
.toFixed(6)}°
|
||||
{#if waypoint.item.ele !== undefined}
|
||||
<Dot size="16" />
|
||||
<WithUnits value={waypoint.item.ele} type="elevation" />
|
||||
{/if}
|
||||
</div>
|
||||
<ScrollArea class="flex flex-col" viewportClasses="max-h-[30dvh]">
|
||||
{#if waypoint.item.desc}
|
||||
<span class="whitespace-pre-wrap">{@html sanitize(waypoint.item.desc)}</span>
|
||||
{/if}
|
||||
{#if waypoint.item.cmt && waypoint.item.cmt !== waypoint.item.desc}
|
||||
<span class="whitespace-pre-wrap">{@html sanitize(waypoint.item.cmt)}</span>
|
||||
{/if}
|
||||
</ScrollArea>
|
||||
{#if $currentTool === Tool.WAYPOINT}
|
||||
<Button
|
||||
class="mt-2 w-full px-2 py-1 h-8 justify-start"
|
||||
variant="outline"
|
||||
on:click={() => deleteWaypoint(waypoint.fileId, waypoint.item._data.index)}
|
||||
>
|
||||
<Trash2 size="16" class="mr-1" />
|
||||
{$_('menu.delete')}
|
||||
<Shortcut shift={true} click={true} />
|
||||
</Button>
|
||||
{/if}
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
<style lang="postcss">
|
||||
div :global(a) {
|
||||
@apply text-link;
|
||||
@apply hover:underline;
|
||||
}
|
||||
div :global(a) {
|
||||
@apply text-link;
|
||||
@apply hover:underline;
|
||||
}
|
||||
|
||||
div :global(img) {
|
||||
@apply my-0;
|
||||
@apply rounded-md;
|
||||
}
|
||||
div :global(img) {
|
||||
@apply my-0;
|
||||
@apply rounded-md;
|
||||
}
|
||||
</style>
|
||||
|
Reference in New Issue
Block a user