diff --git a/gpx-rs/src/engine/command/edit/convert.rs b/gpx-rs/src/engine/command/edit/convert.rs index a954ff77f..d3f645542 100644 --- a/gpx-rs/src/engine/command/edit/convert.rs +++ b/gpx-rs/src/engine/command/edit/convert.rs @@ -107,9 +107,9 @@ impl Apply for Convert { Err(CommandError::NothingToDo) }; } - Selection::Empty - | Selection::Waypoints { .. } - | Selection::Waypoint { .. } => return Err(CommandError::NothingToDo), + Selection::Empty | Selection::Waypoints { .. } | Selection::Waypoint { .. } => { + return Err(CommandError::NothingToDo); + } }; let empty = match &next { Selection::Route { rte_ids, .. } => rte_ids.is_empty(), diff --git a/website/.eslintignore b/website/.eslintignore deleted file mode 100644 index 38972655f..000000000 --- a/website/.eslintignore +++ /dev/null @@ -1,13 +0,0 @@ -.DS_Store -node_modules -/build -/.svelte-kit -/package -.env -.env.* -!.env.example - -# Ignore files for PNPM, NPM and YARN -pnpm-lock.yaml -package-lock.json -yarn.lock diff --git a/website/.eslintrc.cjs b/website/.eslintrc.cjs deleted file mode 100644 index 7c116324d..000000000 --- a/website/.eslintrc.cjs +++ /dev/null @@ -1,31 +0,0 @@ -/** @type { import("eslint").Linter.Config } */ -module.exports = { - root: true, - extends: [ - 'eslint:recommended', - 'plugin:@typescript-eslint/recommended', - 'plugin:svelte/recommended', - 'prettier', - ], - parser: '@typescript-eslint/parser', - plugins: ['@typescript-eslint'], - parserOptions: { - sourceType: 'module', - ecmaVersion: 2020, - extraFileExtensions: ['.svelte'], - }, - env: { - browser: true, - es2017: true, - node: true, - }, - overrides: [ - { - files: ['*.svelte'], - parser: 'svelte-eslint-parser', - parserOptions: { - parser: '@typescript-eslint/parser', - }, - }, - ], -}; diff --git a/website/components.json b/website/components.json index b61120517..02562ebfd 100644 --- a/website/components.json +++ b/website/components.json @@ -1,20 +1,20 @@ { - "$schema": "https://shadcn-svelte.com/schema.json", - "tailwind": { - "css": "src/app.css", - "baseColor": "neutral" - }, - "aliases": { - "components": "$lib/components", - "utils": "$lib/utils", - "ui": "$lib/components/ui", - "hooks": "$lib/hooks", - "lib": "$lib" - }, - "typescript": true, - "registry": "https://shadcn-svelte.com/registry", - "style": "nova", - "iconLibrary": "lucide", - "menuColor": "default", - "menuAccent": "subtle" + "$schema": "https://shadcn-svelte.com/schema.json", + "tailwind": { + "css": "src/app.css", + "baseColor": "neutral" + }, + "aliases": { + "components": "$lib/components", + "utils": "$lib/utils", + "ui": "$lib/components/ui", + "hooks": "$lib/hooks", + "lib": "$lib" + }, + "typescript": true, + "registry": "https://shadcn-svelte.com/registry", + "style": "nova", + "iconLibrary": "lucide", + "menuColor": "default", + "menuAccent": "subtle" } diff --git a/website/eslint.config.js b/website/eslint.config.js new file mode 100644 index 000000000..e7d1096a1 --- /dev/null +++ b/website/eslint.config.js @@ -0,0 +1,47 @@ +import js from '@eslint/js'; +import ts from '@typescript-eslint/eslint-plugin'; +import tsParser from '@typescript-eslint/parser'; +import svelte from 'eslint-plugin-svelte'; +import prettier from 'eslint-config-prettier'; +import globals from 'globals'; + +/** @type {import('eslint').Linter.Config[]} */ +export default [ + { + ignores: ['build/', '.svelte-kit/', 'package/'], + }, + js.configs.recommended, + ...ts.configs['flat/recommended'], + ...svelte.configs['flat/recommended'], + prettier, + ...svelte.configs['flat/prettier'], + { + languageOptions: { + globals: { ...globals.browser, ...globals.node }, + }, + }, + { + rules: { + // TypeScript already reports undefined names + 'no-undef': 'off', + 'no-empty': ['error', { allowEmptyCatch: true }], + '@typescript-eslint/no-unused-vars': [ + 'error', + { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }, + ], + }, + }, + { + files: ['**/*.svelte', '**/*.svelte.ts', '**/*.svelte.js'], + languageOptions: { + parserOptions: { + parser: tsParser, + extraFileExtensions: ['.svelte'], + }, + }, + rules: { + // $effect dependencies are tracked by reading them + '@typescript-eslint/no-unused-expressions': 'off', + }, + }, +]; diff --git a/website/src/app.css b/website/src/app.css index 763088b64..adbe94cee 100644 --- a/website/src/app.css +++ b/website/src/app.css @@ -1,7 +1,7 @@ @import 'tailwindcss'; @import 'tw-animate-css'; -@import "shadcn-svelte/tailwind.css"; -@import "@fontsource-variable/inter"; +@import 'shadcn-svelte/tailwind.css'; +@import '@fontsource-variable/inter'; @custom-variant dark (&:is(.dark *)); @@ -158,7 +158,7 @@ body { @apply bg-background text-foreground; } - html { - @apply font-sans; + html { + @apply font-sans; } } diff --git a/website/src/lib/components/ButtonWithTooltip.svelte b/website/src/lib/components/ButtonWithTooltip.svelte index 7d76923cf..6bc8e0f1b 100644 --- a/website/src/lib/components/ButtonWithTooltip.svelte +++ b/website/src/lib/components/ButtonWithTooltip.svelte @@ -26,7 +26,7 @@ {#snippet child({ props })} - {/snippet} diff --git a/website/src/lib/components/docs/guides.ts b/website/src/lib/components/docs/guides.ts index dbc3e6609..bc36e8283 100644 --- a/website/src/lib/components/docs/guides.ts +++ b/website/src/lib/components/docs/guides.ts @@ -39,7 +39,7 @@ export function getPreviousGuide(currentGuide: string): string | undefined { return `${previousGuide}/${guides[previousGuide][guides[previousGuide].length - 1]}`; } } else { - if (guides.hasOwnProperty(subguides[0])) { + if (Object.hasOwn(guides, subguides[0])) { const subguideIndex = guides[subguides[0]].indexOf(subguides[1]); if (subguideIndex > 0) { return `${subguides[0]}/${guides[subguides[0]][subguideIndex - 1]}`; @@ -56,7 +56,7 @@ export function getNextGuide(currentGuide: string): string | undefined { const subguides = currentGuide.split('/'); if (subguides.length === 1) { - if (guides.hasOwnProperty(currentGuide)) { + if (Object.hasOwn(guides, currentGuide)) { if (guides[currentGuide].length === 0) { const keys = Object.keys(guides); const index = keys.indexOf(currentGuide); @@ -68,7 +68,7 @@ export function getNextGuide(currentGuide: string): string | undefined { return undefined; } } else { - if (guides.hasOwnProperty(subguides[0])) { + if (Object.hasOwn(guides, subguides[0])) { const subguideIndex = guides[subguides[0]].indexOf(subguides[1]); if (subguideIndex < guides[subguides[0]].length - 1) { return `${subguides[0]}/${guides[subguides[0]][subguideIndex + 1]}`; diff --git a/website/src/lib/components/elevation-profile/elevation-profile.ts b/website/src/lib/components/elevation-profile/elevation-profile.ts index e21356798..afe753d9a 100644 --- a/website/src/lib/components/elevation-profile/elevation-profile.ts +++ b/website/src/lib/components/elevation-profile/elevation-profile.ts @@ -148,7 +148,7 @@ export class ElevationProfile { } initialize() { - let options: ChartOptions<'line'> = { + const options: ChartOptions<'line'> = { animation: false, parsing: false, maintainAspectRatio: false, @@ -199,7 +199,7 @@ export class ElevationProfile { return ''; }, label: (context: TooltipItem<'line'>) => { - let point = context.raw as ElevationProfilePoint; + const point = context.raw as ElevationProfilePoint; if (context.datasetIndex === 0) { this._hoveredPoint.set( this._dragging @@ -223,16 +223,18 @@ export class ElevationProfile { } }, afterBody: (contexts: TooltipItem<'line'>[]) => { - let context = contexts.filter((context) => context.datasetIndex === 0); + const context = contexts.filter( + (context) => context.datasetIndex === 0 + ); if (context.length === 0) return; - let point = context[0].raw as ElevationProfilePoint; + const point = context[0].raw as ElevationProfilePoint; // the rest of the data is there once it is read (see `hover`) const hovered = this._hovered; const details = hovered?.index === point.index ? hovered.point : undefined; const elevationFill = get(this._elevationFill); - let labels = [ + const labels = [ ` ${i18n._('quantities.distance')}: ${getDistanceWithUnits(point.x, false)}`, ]; @@ -250,10 +252,10 @@ export class ElevationProfile { } labels.push(` ${i18n._('quantities.slope')}: ${slope}`); } - let surface = details.surface ?? 'unknown'; - let highway = details.highway ?? 'unknown'; - let sacScale = details.sacScale; - let mtbScale = details.mtbScale; + const surface = details.surface ?? 'unknown'; + const highway = details.highway ?? 'unknown'; + const sacScale = details.sacScale; + const mtbScale = details.mtbScale; if (elevationFill === 'surface') { labels.push( @@ -305,7 +307,7 @@ export class ElevationProfile { enabled: true, }, mode: 'x', - onZoomStart: ({ chart, event }: { chart: Chart; event: any }) => { + onZoomStart: ({ event }: { event: any }) => { if (!this._chart) { return false; } @@ -335,7 +337,7 @@ export class ElevationProfile { }, }; - let datasets: string[] = ['speed', 'hr', 'cad', 'atemp', 'power']; + const datasets: string[] = ['speed', 'hr', 'cad', 'atemp', 'power']; datasets.forEach((id) => { options.scales![`y${id}`] = { type: 'linear', @@ -580,11 +582,11 @@ export class ElevationProfile { } const additionalDatasets = get(this._additionalDatasets); - let includeSpeed = additionalDatasets.includes('speed'); - let includeHeartRate = additionalDatasets.includes('hr'); - let includeCadence = additionalDatasets.includes('cad'); - let includeTemperature = additionalDatasets.includes('atemp'); - let includePower = additionalDatasets.includes('power'); + const includeSpeed = additionalDatasets.includes('speed'); + const includeHeartRate = additionalDatasets.includes('hr'); + const includeCadence = additionalDatasets.includes('cad'); + const includeTemperature = additionalDatasets.includes('atemp'); + const includePower = additionalDatasets.includes('power'); if (this._chart.data.datasets.length == 6) { this._chart.data.datasets[1].hidden = !includeSpeed; this._chart.data.datasets[2].hidden = !includeHeartRate; @@ -639,21 +641,21 @@ export class ElevationProfile { const slicedStatistics = get(this._slicedStatistics); if (slicedStatistics) { - let startIndex = slicedStatistics.start; - let endIndex = slicedStatistics.end; + const startIndex = slicedStatistics.start; + const endIndex = slicedStatistics.end; // Draw selection rectangle - let selectionContext = this._overlay.getContext('2d'); + const selectionContext = this._overlay.getContext('2d'); if (selectionContext) { selectionContext.fillStyle = mode.current === 'dark' ? 'white' : 'black'; selectionContext.globalAlpha = mode.current === 'dark' ? 0.2 : 0.1; selectionContext.clearRect(0, 0, this._overlay.width, this._overlay.height); const statistics = get(this._statistics); - let startPixel = this._chart.scales.x.getPixelForValue( + const startPixel = this._chart.scales.x.getPixelForValue( getConvertedDistance(statistics.totalDistance[startIndex] ?? 0) ); - let endPixel = this._chart.scales.x.getPixelForValue( + const endPixel = this._chart.scales.x.getPixelForValue( getConvertedDistance(statistics.totalDistance[endIndex] ?? 0) ); @@ -665,7 +667,7 @@ export class ElevationProfile { ); } } else if (this._overlay) { - let selectionContext = this._overlay.getContext('2d'); + const selectionContext = this._overlay.getContext('2d'); if (selectionContext) { selectionContext.clearRect(0, 0, this._overlay.width, this._overlay.height); } diff --git a/website/src/lib/components/embedding/embedding.ts b/website/src/lib/components/embedding/embedding.ts index ed783266a..110242230 100644 --- a/website/src/lib/components/embedding/embedding.ts +++ b/website/src/lib/components/embedding/embedding.ts @@ -109,7 +109,7 @@ export function getURLForGoogleDriveFile(fileId: string): string { } export function convertOldEmbeddingOptions(options: URLSearchParams): any { - let newOptions: any = { + const newOptions: any = { key: PUBLIC_MAPTILER_KEY, files: [], ids: [], @@ -128,7 +128,7 @@ export function convertOldEmbeddingOptions(options: URLSearchParams): any { } } if (options.has('source')) { - let basemap = options.get('source')!; + const basemap = options.get('source')!; if (basemap === 'satellite') { newOptions.basemap = 'libertySatellite'; } else if (basemap === 'otm') { diff --git a/website/src/lib/components/explorer/Explorer.svelte b/website/src/lib/components/explorer/Explorer.svelte index 608cf6eac..1d84bd1f7 100644 --- a/website/src/lib/components/explorer/Explorer.svelte +++ b/website/src/lib/components/explorer/Explorer.svelte @@ -132,7 +132,6 @@
{i18n._('menu.explorer')}
-
- { diff --git a/website/src/lib/components/file-list/sortable-file-list.ts b/website/src/lib/components/file-list/sortable-file-list.ts index 2635de619..045d57b63 100644 --- a/website/src/lib/components/file-list/sortable-file-list.ts +++ b/website/src/lib/components/file-list/sortable-file-list.ts @@ -241,9 +241,9 @@ export class SortableFileList { return; } const selection_ = get(engine.selection); - for (let id of changed) { - let element = this._elements[id]; - let node = this.childNode(id); + for (const id of changed) { + const element = this._elements[id]; + const node = this.childNode(id); if (element && node) { if (isSelected(selection_, node)) { Sortable.utils.select(element); @@ -344,7 +344,7 @@ export class SortableFileList { const files = get(engine.files); this._container.childNodes.forEach((element) => { if (element instanceof HTMLElement) { - let attr = element.getAttribute('data-id'); + const attr = element.getAttribute('data-id'); if (attr) { if (this._sortableLevel === 'file' && !files.has(attr)) { this.forget(element); @@ -392,12 +392,12 @@ export class SortableFileList { } getChangedIds() { - let changed: string[] = []; + const changed: string[] = []; const selection_ = get(engine.selection); Object.entries(this._elements).forEach(([id, element]) => { const node = this.childNode(id); - let inSelection = node !== undefined && isSelected(selection_, node); - let isSelectedInList = element.classList.contains('sortable-selected'); + const inSelection = node !== undefined && isSelected(selection_, node); + const isSelectedInList = element.classList.contains('sortable-selected'); if (inSelection !== isSelectedInList) { changed.push(id); } diff --git a/website/src/lib/components/map/gpx-layer/WaypointPopup.svelte b/website/src/lib/components/map/gpx-layer/WaypointPopup.svelte index 015c5d016..dbe78556b 100644 --- a/website/src/lib/components/map/gpx-layer/WaypointPopup.svelte +++ b/website/src/lib/components/map/gpx-layer/WaypointPopup.svelte @@ -77,9 +77,11 @@
{#if waypoint.item.desc} + {@html sanitize(waypoint.item.desc)} {/if} {#if waypoint.item.cmt && waypoint.item.cmt !== waypoint.item.desc} + {@html sanitize(waypoint.item.cmt)} {/if} diff --git a/website/src/lib/components/map/gpx-layer/distance-markers.ts b/website/src/lib/components/map/gpx-layer/distance-markers.ts index 619e7574f..31309f690 100644 --- a/website/src/lib/components/map/gpx-layer/distance-markers.ts +++ b/website/src/lib/components/map/gpx-layer/distance-markers.ts @@ -35,7 +35,8 @@ export class DistanceMarkers { try { if (get(distanceMarkers) && !get(allHidden)) { - let distanceSource: GeoJSONSource | undefined = map_.getSource('distance-markers'); + const distanceSource: GeoJSONSource | undefined = + map_.getSource('distance-markers'); if (distanceSource) { distanceSource.setData(this.getDistanceMarkersGeoJSON()); } else { @@ -90,7 +91,7 @@ export class DistanceMarkers { map_.removeLayer('distance-markers'); } } - } catch (e) { + } catch { // No reliable way to check if the map is ready to add sources and layers return; } @@ -104,12 +105,12 @@ export class DistanceMarkers { // the distance from the start of the selection, and the position, of each trackpoint const { length, totalDistance, lng, lat } = get(engine.statistics); - let features: GeoJSON.Feature[] = []; + const features: GeoJSON.Feature[] = []; let currentTargetDistance = 1; for (let i = 0; i < length; i++) { if (totalDistance[i] >= getConvertedDistanceToKilometers(currentTargetDistance)) { - let distance = currentTargetDistance.toFixed(0); - let level = levels.find((level) => currentTargetDistance % level === 0) || 1; + const distance = currentTargetDistance.toFixed(0); + const level = levels.find((level) => currentTargetDistance % level === 0) || 1; features.push({ type: 'Feature', geometry: { diff --git a/website/src/lib/components/map/gpx-layer/gpx-layer.ts b/website/src/lib/components/map/gpx-layer/gpx-layer.ts index 9a4260d03..5c143a925 100644 --- a/website/src/lib/components/map/gpx-layer/gpx-layer.ts +++ b/website/src/lib/components/map/gpx-layer/gpx-layer.ts @@ -23,7 +23,7 @@ import { mapCursor, MapCursorState } from '$lib/state/map-cursor'; import { ANCHOR_LAYER_KEY } from '$lib/components/map/style'; export function getSvgForSymbol(symbol?: string | undefined, layerColor?: string | undefined) { - let symbolSvg = symbol ? symbols[symbol]?.iconSvg : undefined; + const symbolSvg = symbol ? symbols[symbol]?.iconSvg : undefined; return ` ${ layerColor @@ -63,7 +63,7 @@ export class GPXLayer { currentWaypointData: GeoJSON.FeatureCollection | null = null; draggedWaypointIndex: number | null = null; draggingStartingPosition: Point = new Point(0, 0); - unsubscribe: Function[] = []; + unsubscribe: (() => void)[] = []; updateBinded: () => void = this.update.bind(this); layerOnMouseEnterBinded: (e: any) => void = this.layerOnMouseEnter.bind(this); @@ -101,7 +101,7 @@ export class GPXLayer { this.unsubscribe.push( engine.selection.subscribe(($selection) => { this.selectionState = $selection; - let newSelected = hasSelectionWithin($selection, this.fileNode()); + const newSelected = hasSelectionWithin($selection, this.fileNode()); if (this.selected || newSelected) { this.selected = newSelected; this.update(); @@ -146,7 +146,7 @@ export class GPXLayer { this.loadIcons(); try { - let source = _map.getSource(this.fileId) as GeoJSONSource | undefined; + const source = _map.getSource(this.fileId) as GeoJSONSource | undefined; if (source) { source.setData(this.getGeoJSON()); } else { @@ -234,7 +234,7 @@ export class GPXLayer { } } - let waypointSource = _map.getSource(this.fileId + '-waypoints') as + const waypointSource = _map.getSource(this.fileId + '-waypoints') as | GeoJSONSource | undefined; this.currentWaypointData = this.getWaypointsGeoJSON(); @@ -301,7 +301,7 @@ export class GPXLayer { ['in', ['get', 'waypointIndex'], ['literal', visibleWaypoints]], { validate: false } ); - } catch (e) { + } catch { // No reliable way to check if the map is ready to add sources and layers return; } @@ -584,7 +584,7 @@ export class GPXLayer { this.currentWaypointData!.features[this.draggedWaypointIndex].geometry as GeoJSON.Point ).coordinates = [e.lngLat.lng, e.lngLat.lat]; - let waypointSource = get(map)?.getSource(this.fileId + '-waypoints') as + const waypointSource = get(map)?.getSource(this.fileId + '-waypoints') as | GeoJSONSource | undefined; if (waypointSource) { @@ -698,7 +698,7 @@ export class GPXLayer { return; } - let symbols = new Set(); + const symbols = new Set(); get(this.file).waypoints.features.forEach((feature) => { symbols.add(getSymbolKey(feature.properties.sym)); }); diff --git a/website/src/lib/components/map/gpx-layer/start-end-markers.ts b/website/src/lib/components/map/gpx-layer/start-end-markers.ts index 7700daf37..4a0bb6acf 100644 --- a/website/src/lib/components/map/gpx-layer/start-end-markers.ts +++ b/website/src/lib/components/map/gpx-layer/start-end-markers.ts @@ -100,7 +100,7 @@ export class StartEndMarkers { }); } - let source = map_.getSource('start-end-markers') as GeoJSONSource | undefined; + const source = map_.getSource('start-end-markers') as GeoJSONSource | undefined; if (source) { source.setData(data); } else { diff --git a/website/src/lib/components/map/layer-control/CustomLayers.svelte b/website/src/lib/components/map/layer-control/CustomLayers.svelte index 5d557c6a9..3eec6927b 100644 --- a/website/src/lib/components/map/layer-control/CustomLayers.svelte +++ b/website/src/lib/components/map/layer-control/CustomLayers.svelte @@ -136,7 +136,7 @@ function getLayerId() { for (let id = 0; ; id++) { - if (!$customLayers.hasOwnProperty(`custom-${id}`)) { + if (!Object.hasOwn($customLayers, `custom-${id}`)) { return `custom-${id}`; } } @@ -145,7 +145,7 @@ function addLayer(layerId: string) { if (layerType === 'basemap') { selectedBasemapTree.update(($tree) => { - if (!$tree.basemaps.hasOwnProperty('custom')) { + if (!Object.hasOwn($tree.basemaps, 'custom')) { $tree.basemaps['custom'] = {}; } $tree.basemaps['custom'][layerId] = true; @@ -161,7 +161,7 @@ } } else { selectedOverlayTree.update(($tree) => { - if (!$tree.overlays.hasOwnProperty('custom')) { + if (!Object.hasOwn($tree.overlays, 'custom')) { $tree.overlays['custom'] = {}; } $tree.overlays['custom'][layerId] = true; @@ -169,7 +169,7 @@ }); currentOverlays.update(($overlays) => { - if (!$overlays.overlays.hasOwnProperty('custom')) { + if (!Object.hasOwn($overlays.overlays, 'custom')) { $overlays.overlays['custom'] = {}; } $overlays.overlays['custom'][layerId] = true; @@ -183,7 +183,7 @@ } function tryDeleteLayer(node: any, id: string): any { - if (node.hasOwnProperty(id)) { + if (Object.hasOwn(node, id)) { delete node[id]; } return node; @@ -365,7 +365,7 @@ - {#each tileUrls as url, i} + {#each tileUrls as _, i}
{ if (checked !== undefined) { Object.keys(node).forEach((id) => { - if (!checked.hasOwnProperty(id)) { + if (!Object.hasOwn(checked, id)) { if (typeof node[id] == 'boolean') { checked[id] = false; } else { @@ -72,7 +72,7 @@ /> {/if}
- +
diff --git a/website/src/routes/[[language]]/help/[...guide]/+page.server.ts b/website/src/routes/[[language]]/help/[...guide]/+page.server.ts index b153927b3..f25c99baf 100644 --- a/website/src/routes/[[language]]/help/[...guide]/+page.server.ts +++ b/website/src/routes/[[language]]/help/[...guide]/+page.server.ts @@ -3,14 +3,14 @@ import { guides } from '$lib/components/docs/docs'; import type { EntryGenerator } from './$types'; export const entries: EntryGenerator = () => { - let entries = []; - for (let lang of Object.keys(languages)) { - for (let guide of Object.keys(guides)) { + const entries = []; + for (const lang of Object.keys(languages)) { + for (const guide of Object.keys(guides)) { entries.push({ language: lang == 'en' ? '' : lang, guide, }); - for (let subguide of guides[guide]) { + for (const subguide of guides[guide]) { entries.push({ language: lang == 'en' ? '' : lang, guide: `${guide}/${subguide}`,