mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2025-12-02 10:02:12 +00:00
Compare commits
6 Commits
88c9abb78e
...
0f7f64fb2f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0f7f64fb2f | ||
|
|
b09a1fdcb7 | ||
|
|
e5d45dee3a | ||
|
|
8c3365ef24 | ||
|
|
db5cbffb70 | ||
|
|
683ac4e118 |
@@ -152,11 +152,12 @@ export const basemaps: { [key: string]: string | StyleSpecification } = {
|
|||||||
linzTopo: {
|
linzTopo: {
|
||||||
type: 'raster',
|
type: 'raster',
|
||||||
tiles: [
|
tiles: [
|
||||||
'https://tiles-cdn.koordinates.com/services;key=39a8b989633a4bef98bc0e065380454a/tiles/v4/layer=50767/EPSG:3857/{z}/{x}/{y}.png',
|
'https://basemaps.linz.govt.nz/v1/tiles/topo-raster/WebMercatorQuad/{z}/{x}/{y}.webp?api=d01fbtg0ar23gctac5m0jgyy2ds',
|
||||||
],
|
],
|
||||||
tileSize: 256,
|
tileSize: 256,
|
||||||
maxzoom: 18,
|
maxzoom: 16,
|
||||||
attribution: '© <a href="https://www.linz.govt.nz/" target="_blank">LINZ</a>',
|
attribution:
|
||||||
|
'© <a href="//www.linz.govt.nz/linz-copyright">LINZ CC BY 4.0</a> © <a href="//www.linz.govt.nz/data/linz-data/linz-basemaps/data-attribution">Imagery Basemap contributors</a>',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
layers: [
|
layers: [
|
||||||
|
|||||||
@@ -1,16 +1,23 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { CircleQuestionMark } from '@lucide/svelte';
|
import { CircleQuestionMark } from '@lucide/svelte';
|
||||||
import { i18n } from '$lib/i18n.svelte';
|
import { i18n } from '$lib/i18n.svelte';
|
||||||
|
import type { Snippet } from 'svelte';
|
||||||
|
|
||||||
export let link: string | undefined = undefined;
|
let {
|
||||||
|
link,
|
||||||
|
class: className = '',
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
link: string;
|
||||||
|
class?: string;
|
||||||
|
children: Snippet;
|
||||||
|
} = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div class="text-sm bg-secondary rounded border flex flex-row items-center p-2 {className}">
|
||||||
class="text-sm bg-secondary rounded border flex flex-row items-center p-2 {$$props.class || ''}"
|
|
||||||
>
|
|
||||||
<CircleQuestionMark size="16" class="w-4 mr-2 shrink-0 grow-0" />
|
<CircleQuestionMark size="16" class="w-4 mr-2 shrink-0 grow-0" />
|
||||||
<div>
|
<div>
|
||||||
<slot />
|
{@render children()}
|
||||||
{#if link}
|
{#if link}
|
||||||
<a href={link} target="_blank" class="text-sm text-link hover:underline">
|
<a href={link} target="_blank" class="text-sm text-link hover:underline">
|
||||||
{i18n._('menu.more')}
|
{i18n._('menu.more')}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
import { i18n } from '$lib/i18n.svelte';
|
import { i18n } from '$lib/i18n.svelte';
|
||||||
import { defaultBasemap, type CustomLayer } from '$lib/assets/layers';
|
import { defaultBasemap, type CustomLayer } from '$lib/assets/layers';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { customBasemapUpdate } from './utils';
|
import { customBasemapUpdate, isSelected, remove } from './utils';
|
||||||
import { settings } from '$lib/logic/settings';
|
import { settings } from '$lib/logic/settings';
|
||||||
import { map } from '$lib/components/map/map';
|
import { map } from '$lib/components/map/map';
|
||||||
import { dndzone } from 'svelte-dnd-action';
|
import { dndzone } from 'svelte-dnd-action';
|
||||||
@@ -176,11 +176,7 @@
|
|||||||
return $tree;
|
return $tree;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (
|
if ($map && $currentOverlays && isSelected($currentOverlays, layerId)) {
|
||||||
$currentOverlays.overlays['custom'] &&
|
|
||||||
$currentOverlays.overlays['custom'][layerId] &&
|
|
||||||
$map
|
|
||||||
) {
|
|
||||||
try {
|
try {
|
||||||
$map.removeImport(layerId);
|
$map.removeImport(layerId);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -188,10 +184,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$currentOverlays.overlays.hasOwnProperty('custom')) {
|
currentOverlays.update(($overlays) => {
|
||||||
$currentOverlays.overlays['custom'] = {};
|
if (!$overlays.overlays.hasOwnProperty('custom')) {
|
||||||
|
$overlays.overlays['custom'] = {};
|
||||||
}
|
}
|
||||||
$currentOverlays.overlays['custom'][layerId] = true;
|
$overlays.overlays['custom'][layerId] = true;
|
||||||
|
return $overlays;
|
||||||
|
});
|
||||||
|
|
||||||
if (!$customOverlayOrder.includes(layerId)) {
|
if (!$customOverlayOrder.includes(layerId)) {
|
||||||
$customOverlayOrder = [...$customOverlayOrder, layerId];
|
$customOverlayOrder = [...$customOverlayOrder, layerId];
|
||||||
@@ -216,49 +215,15 @@
|
|||||||
$previousBasemap = defaultBasemap;
|
$previousBasemap = defaultBasemap;
|
||||||
}
|
}
|
||||||
|
|
||||||
$selectedBasemapTree.basemaps['custom'] = tryDeleteLayer(
|
$selectedBasemapTree = remove($selectedBasemapTree, layerId);
|
||||||
$selectedBasemapTree.basemaps['custom'],
|
|
||||||
layerId
|
|
||||||
);
|
|
||||||
if (Object.keys($selectedBasemapTree.basemaps['custom']).length === 0) {
|
|
||||||
$selectedBasemapTree.basemaps = tryDeleteLayer(
|
|
||||||
$selectedBasemapTree.basemaps,
|
|
||||||
'custom'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
$customBasemapOrder = $customBasemapOrder.filter((id) => id !== layerId);
|
$customBasemapOrder = $customBasemapOrder.filter((id) => id !== layerId);
|
||||||
} else {
|
} else {
|
||||||
$currentOverlays.overlays['custom'][layerId] = false;
|
if ($currentOverlays) {
|
||||||
if ($previousOverlays.overlays['custom']) {
|
$currentOverlays = remove($currentOverlays, layerId);
|
||||||
$previousOverlays.overlays['custom'] = tryDeleteLayer(
|
|
||||||
$previousOverlays.overlays['custom'],
|
|
||||||
layerId
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
$selectedOverlayTree.overlays['custom'] = tryDeleteLayer(
|
|
||||||
$selectedOverlayTree.overlays['custom'],
|
|
||||||
layerId
|
|
||||||
);
|
|
||||||
if (Object.keys($selectedOverlayTree.overlays['custom']).length === 0) {
|
|
||||||
$selectedOverlayTree.overlays = tryDeleteLayer(
|
|
||||||
$selectedOverlayTree.overlays,
|
|
||||||
'custom'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
$previousOverlays = remove($previousOverlays, layerId);
|
||||||
|
$selectedOverlayTree = remove($selectedOverlayTree, layerId);
|
||||||
$customOverlayOrder = $customOverlayOrder.filter((id) => id !== layerId);
|
$customOverlayOrder = $customOverlayOrder.filter((id) => id !== layerId);
|
||||||
|
|
||||||
if (
|
|
||||||
$currentOverlays.overlays['custom'] &&
|
|
||||||
$currentOverlays.overlays['custom'][layerId] &&
|
|
||||||
$map
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
$map.removeImport(layerId);
|
|
||||||
} catch (e) {
|
|
||||||
// No reliable way to check if the map is ready to remove sources and layers
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
$customLayers = tryDeleteLayer($customLayers, layerId);
|
$customLayers = tryDeleteLayer($customLayers, layerId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
import CustomLayers from './CustomLayers.svelte';
|
import CustomLayers from './CustomLayers.svelte';
|
||||||
import { settings } from '$lib/logic/settings';
|
import { settings } from '$lib/logic/settings';
|
||||||
import { untrack } from 'svelte';
|
import { untrack } from 'svelte';
|
||||||
|
import { extensionAPI } from './extension-api';
|
||||||
|
|
||||||
const {
|
const {
|
||||||
selectedBasemapTree,
|
selectedBasemapTree,
|
||||||
@@ -160,7 +161,11 @@
|
|||||||
<Select.Trigger class="h-8 mr-1 w-full">
|
<Select.Trigger class="h-8 mr-1 w-full">
|
||||||
{#if selectedOverlay}
|
{#if selectedOverlay}
|
||||||
{#if isSelected($selectedOverlayTree, selectedOverlay)}
|
{#if isSelected($selectedOverlayTree, selectedOverlay)}
|
||||||
|
{#if extensionAPI.isLayerFromExtension(selectedOverlay)}
|
||||||
|
{extensionAPI.getLayerName(selectedOverlay)}
|
||||||
|
{:else}
|
||||||
{i18n._(`layers.label.${selectedOverlay}`)}
|
{i18n._(`layers.label.${selectedOverlay}`)}
|
||||||
|
{/if}
|
||||||
{:else if $customLayers.hasOwnProperty(selectedOverlay)}
|
{:else if $customLayers.hasOwnProperty(selectedOverlay)}
|
||||||
{$customLayers[selectedOverlay].name}
|
{$customLayers[selectedOverlay].name}
|
||||||
{/if}
|
{/if}
|
||||||
@@ -169,9 +174,13 @@
|
|||||||
<Select.Content class="h-fit max-h-[40dvh] overflow-y-auto">
|
<Select.Content class="h-fit max-h-[40dvh] overflow-y-auto">
|
||||||
{#each Object.keys(overlays) as id}
|
{#each Object.keys(overlays) as id}
|
||||||
{#if isSelected($selectedOverlayTree, id)}
|
{#if isSelected($selectedOverlayTree, id)}
|
||||||
<Select.Item value={id}
|
<Select.Item value={id}>
|
||||||
>{i18n._(`layers.label.${id}`)}</Select.Item
|
{#if extensionAPI.isLayerFromExtension(id)}
|
||||||
>
|
{extensionAPI.getLayerName(id)}
|
||||||
|
{:else}
|
||||||
|
{i18n._(`layers.label.${id}`)}
|
||||||
|
{/if}
|
||||||
|
</Select.Item>
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
{#each Object.entries($customLayers) as [id, layer]}
|
{#each Object.entries($customLayers) as [id, layer]}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
import { anySelectedLayer } from './utils';
|
import { anySelectedLayer } from './utils';
|
||||||
import { i18n } from '$lib/i18n.svelte';
|
import { i18n } from '$lib/i18n.svelte';
|
||||||
import { settings } from '$lib/logic/settings';
|
import { settings } from '$lib/logic/settings';
|
||||||
|
import { extensionAPI } from '$lib/components/map/layer-control/extension-api';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
name,
|
name,
|
||||||
@@ -72,6 +73,8 @@
|
|||||||
<Label for="{name}-{id}" class="flex flex-row items-center gap-1">
|
<Label for="{name}-{id}" class="flex flex-row items-center gap-1">
|
||||||
{#if $customLayers.hasOwnProperty(id)}
|
{#if $customLayers.hasOwnProperty(id)}
|
||||||
{$customLayers[id].name}
|
{$customLayers[id].name}
|
||||||
|
{:else if extensionAPI.isLayerFromExtension(id)}
|
||||||
|
{extensionAPI.getLayerName(id)}
|
||||||
{:else}
|
{:else}
|
||||||
{i18n._(`layers.label.${id}`)}
|
{i18n._(`layers.label.${id}`)}
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -9,18 +9,23 @@
|
|||||||
import { fileActions } from '$lib/logic/file-actions';
|
import { fileActions } from '$lib/logic/file-actions';
|
||||||
import { selection } from '$lib/logic/selection';
|
import { selection } from '$lib/logic/selection';
|
||||||
|
|
||||||
export let poi: PopupItem<any>;
|
let {
|
||||||
|
poi,
|
||||||
|
}: {
|
||||||
|
poi: PopupItem<any>;
|
||||||
|
} = $props();
|
||||||
|
|
||||||
let tags: { [key: string]: string } = {};
|
let tags: Record<string, string> = $derived(poi ? JSON.parse(poi.item.tags) : {});
|
||||||
let name = '';
|
let name = $derived.by(() => {
|
||||||
$: if (poi) {
|
if (poi) {
|
||||||
tags = JSON.parse(poi.item.tags);
|
|
||||||
if (tags.name !== undefined && tags.name !== '') {
|
if (tags.name !== undefined && tags.name !== '') {
|
||||||
name = tags.name;
|
return tags.name;
|
||||||
} else {
|
} else {
|
||||||
name = i18n._(`layers.label.${poi.item.query}`);
|
return i18n._(`layers.label.${poi.item.query}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return '';
|
||||||
|
});
|
||||||
|
|
||||||
function addToFile() {
|
function addToFile() {
|
||||||
const desc = Object.entries(tags)
|
const desc = Object.entries(tags)
|
||||||
@@ -74,7 +79,7 @@
|
|||||||
<ScrollArea class="flex flex-col max-h-[30dvh]">
|
<ScrollArea class="flex flex-col max-h-[30dvh]">
|
||||||
{#if tags.image || tags['image:0']}
|
{#if tags.image || tags['image:0']}
|
||||||
<div class="w-full rounded-md overflow-clip my-2 max-w-96 mx-auto">
|
<div class="w-full rounded-md overflow-clip my-2 max-w-96 mx-auto">
|
||||||
<!-- svelte-ignore a11y-missing-attribute -->
|
<!-- svelte-ignore a11y_missing_attribute -->
|
||||||
<img src={tags.image ?? tags['image:0']} />
|
<img src={tags.image ?? tags['image:0']} />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
167
website/src/lib/components/map/layer-control/extension-api.ts
Normal file
167
website/src/lib/components/map/layer-control/extension-api.ts
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
import { map, type MapboxGLMap } from '$lib/components/map/map';
|
||||||
|
import { settings } from '$lib/logic/settings';
|
||||||
|
import { get } from 'svelte/store';
|
||||||
|
import { isSelected, remove, removeByPrefix, toggle } from './utils';
|
||||||
|
import { overlays, overlayTree } from '$lib/assets/layers';
|
||||||
|
import { browser } from '$app/environment';
|
||||||
|
|
||||||
|
const { currentOverlays, previousOverlays, selectedOverlayTree } = settings;
|
||||||
|
|
||||||
|
export type CustomOverlay = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
tileUrls: string[];
|
||||||
|
maxZoom?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class ExtensionAPI {
|
||||||
|
private _map: MapboxGLMap;
|
||||||
|
private _overlays: Map<string, CustomOverlay> = new Map();
|
||||||
|
|
||||||
|
constructor(map: MapboxGLMap) {
|
||||||
|
this._map = map;
|
||||||
|
if (browser && !window.hasOwnProperty('gpxstudio')) {
|
||||||
|
Object.defineProperty(window, 'gpxstudio', {
|
||||||
|
value: this,
|
||||||
|
});
|
||||||
|
addEventListener('beforeunload', () => {
|
||||||
|
this.destroy();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async ensureLoaded(): Promise<void> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this._map.onLoad(() => {
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addOrUpdateOverlay(overlay: CustomOverlay) {
|
||||||
|
if (!overlay.id || !overlay.tileUrls || overlay.tileUrls.length === 0) {
|
||||||
|
throw new Error('Overlay must have an id and at least one tile URL.');
|
||||||
|
}
|
||||||
|
overlay.id = this.getOverlayId(overlay.id);
|
||||||
|
|
||||||
|
this._overlays.set(overlay.id, overlay);
|
||||||
|
|
||||||
|
overlays[overlay.id] = {
|
||||||
|
version: 8,
|
||||||
|
sources: {
|
||||||
|
[overlay.id]: {
|
||||||
|
type: 'raster',
|
||||||
|
tiles: overlay.tileUrls,
|
||||||
|
tileSize: overlay.tileUrls.some((url) => url.includes('512')) ? 512 : 256,
|
||||||
|
maxzoom: overlay.maxZoom ?? 22,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
layers: [
|
||||||
|
{
|
||||||
|
id: overlay.id,
|
||||||
|
type: 'raster',
|
||||||
|
source: overlay.id,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
overlayTree.overlays.world[overlay.id] = true;
|
||||||
|
|
||||||
|
selectedOverlayTree.update((selected) => {
|
||||||
|
selected.overlays.world[overlay.id] = true;
|
||||||
|
return selected;
|
||||||
|
});
|
||||||
|
|
||||||
|
const current = get(currentOverlays);
|
||||||
|
if (current && isSelected(current, overlay.id)) {
|
||||||
|
try {
|
||||||
|
get(this._map)?.removeImport(overlay.id);
|
||||||
|
} catch (e) {
|
||||||
|
// No reliable way to check if the map is ready to remove sources and layers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
currentOverlays.update((current) => {
|
||||||
|
current.overlays.world[overlay.id] = true;
|
||||||
|
return current;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
removeOverlaysWithPrefix(prefix: string) {
|
||||||
|
prefix = this.getOverlayId(prefix);
|
||||||
|
|
||||||
|
currentOverlays.update((overlays) => {
|
||||||
|
removeByPrefix(overlays, prefix);
|
||||||
|
return overlays;
|
||||||
|
});
|
||||||
|
previousOverlays.update((overlays) => {
|
||||||
|
removeByPrefix(overlays, prefix);
|
||||||
|
return overlays;
|
||||||
|
});
|
||||||
|
selectedOverlayTree.update((overlays) => {
|
||||||
|
removeByPrefix(overlays, prefix);
|
||||||
|
return overlays;
|
||||||
|
});
|
||||||
|
Object.keys(overlays).forEach((id) => {
|
||||||
|
if (id.startsWith(prefix)) {
|
||||||
|
delete overlays[id];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.keys(overlayTree.overlays.world).forEach((id) => {
|
||||||
|
if (id.startsWith(prefix)) {
|
||||||
|
delete overlayTree.overlays.world[id];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleOverlay(id: string) {
|
||||||
|
id = this.getOverlayId(id);
|
||||||
|
|
||||||
|
currentOverlays.update((overlays) => {
|
||||||
|
toggle(overlays, id);
|
||||||
|
return overlays;
|
||||||
|
});
|
||||||
|
if (!isSelected(get(selectedOverlayTree), id)) {
|
||||||
|
selectedOverlayTree.update((overlays) => {
|
||||||
|
toggle(overlays, id);
|
||||||
|
return overlays;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isLayerFromExtension(id: string): boolean {
|
||||||
|
return this._overlays.has(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
getLayerName(id: string): string {
|
||||||
|
const overlay = this._overlays.get(id);
|
||||||
|
return overlay ? overlay.name : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
private getOverlayId(id: string): string {
|
||||||
|
return `extension-${id}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private destroy() {
|
||||||
|
currentOverlays.update((overlays) => {
|
||||||
|
this._overlays.forEach((_, id) => {
|
||||||
|
remove(overlays, id);
|
||||||
|
});
|
||||||
|
return overlays;
|
||||||
|
});
|
||||||
|
previousOverlays.update((overlays) => {
|
||||||
|
this._overlays.forEach((_, id) => {
|
||||||
|
remove(overlays, id);
|
||||||
|
});
|
||||||
|
return overlays;
|
||||||
|
});
|
||||||
|
selectedOverlayTree.update((overlays) => {
|
||||||
|
this._overlays.forEach((_, id) => {
|
||||||
|
remove(overlays, id);
|
||||||
|
});
|
||||||
|
return overlays;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const extensionAPI = new ExtensionAPI(map);
|
||||||
@@ -55,4 +55,26 @@ export function toggle(node: LayerTreeType, id: string) {
|
|||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function remove(node: LayerTreeType, id: string) {
|
||||||
|
Object.keys(node).forEach((key) => {
|
||||||
|
if (key === id) {
|
||||||
|
delete node[key];
|
||||||
|
} else if (typeof node[key] !== 'boolean') {
|
||||||
|
remove(node[key], id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeByPrefix(node: LayerTreeType, prefix: string) {
|
||||||
|
Object.keys(node).forEach((key) => {
|
||||||
|
if (key.startsWith(prefix)) {
|
||||||
|
delete node[key];
|
||||||
|
} else if (typeof node[key] !== 'boolean') {
|
||||||
|
remove(node[key], prefix);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
export const customBasemapUpdate = writable(0);
|
export const customBasemapUpdate = writable(0);
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export class AllHidden {
|
|||||||
|
|
||||||
update() {
|
update() {
|
||||||
let hidden = true;
|
let hidden = true;
|
||||||
selection.applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
|
selection.applyToSelectedItemsFromFile((fileId, level, items) => {
|
||||||
let file = fileStateCollection.getFile(fileId);
|
let file = fileStateCollection.getFile(fileId);
|
||||||
if (file) {
|
if (file) {
|
||||||
for (let item of items) {
|
for (let item of items) {
|
||||||
|
|||||||
@@ -187,6 +187,33 @@ export class Selection {
|
|||||||
return selected;
|
return selected;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
applyToSelectedItemsFromFile(
|
||||||
|
callback: (fileId: string, level: ListLevel | undefined, items: ListItem[]) => void
|
||||||
|
) {
|
||||||
|
let selectedItems = get(this._selection).getSelected();
|
||||||
|
get(fileStateCollection).forEach((_, fileId) => {
|
||||||
|
let level: ListLevel | undefined = undefined;
|
||||||
|
let items: ListItem[] = [];
|
||||||
|
selectedItems.forEach((item) => {
|
||||||
|
if (item.getFileId() === fileId) {
|
||||||
|
level = item.level;
|
||||||
|
if (
|
||||||
|
item instanceof ListFileItem ||
|
||||||
|
item instanceof ListTrackItem ||
|
||||||
|
item instanceof ListTrackSegmentItem ||
|
||||||
|
item instanceof ListWaypointsItem ||
|
||||||
|
item instanceof ListWaypointItem
|
||||||
|
) {
|
||||||
|
items.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (items.length > 0) {
|
||||||
|
callback(fileId, level, items);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
applyToOrderedSelectedItemsFromFile(
|
applyToOrderedSelectedItemsFromFile(
|
||||||
callback: (fileId: string, level: ListLevel | undefined, items: ListItem[]) => void,
|
callback: (fileId: string, level: ListLevel | undefined, items: ListItem[]) => void,
|
||||||
reverse: boolean = true
|
reverse: boolean = true
|
||||||
|
|||||||
Reference in New Issue
Block a user