mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2025-09-02 08:42:31 +00:00
wrap file collection in single object
This commit is contained in:
@@ -1,9 +1,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import GPX from './GPX.svelte';
|
import GPX from './GPX.svelte';
|
||||||
|
|
||||||
import { files } from '$lib/stores';
|
import { fileCollection } from '$lib/stores';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#each $files as file}
|
{#each $fileCollection.files as file}
|
||||||
<GPX {file} />
|
<GPX {file} />
|
||||||
{/each}
|
{/each}
|
||||||
|
@@ -6,7 +6,7 @@
|
|||||||
import Chart from 'chart.js/auto';
|
import Chart from 'chart.js/auto';
|
||||||
import mapboxgl from 'mapbox-gl';
|
import mapboxgl from 'mapbox-gl';
|
||||||
|
|
||||||
import { map, files, fileOrder, selectedFiles } from '$lib/stores';
|
import { map, fileCollection, fileOrder, selectedFiles } from '$lib/stores';
|
||||||
|
|
||||||
import { onDestroy, onMount } from 'svelte';
|
import { onDestroy, onMount } from 'svelte';
|
||||||
import {
|
import {
|
||||||
@@ -75,10 +75,10 @@
|
|||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
callbacks: {
|
callbacks: {
|
||||||
title: function (context) {
|
title: function () {
|
||||||
return '';
|
return '';
|
||||||
},
|
},
|
||||||
label: function (context) {
|
label: function (context: Chart.TooltipContext) {
|
||||||
let point = context.raw;
|
let point = context.raw;
|
||||||
if (context.datasetIndex === 0) {
|
if (context.datasetIndex === 0) {
|
||||||
let elevation = point.y.toFixed(0);
|
let elevation = point.y.toFixed(0);
|
||||||
@@ -104,7 +104,7 @@
|
|||||||
return `Power: ${power} W`;
|
return `Power: ${power} W`;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
afterBody: function (contexts) {
|
afterBody: function (contexts: Chart.TooltipContext[]) {
|
||||||
let context = contexts.filter((context) => context.datasetIndex === 0);
|
let context = contexts.filter((context) => context.datasetIndex === 0);
|
||||||
if (context.length === 0) return;
|
if (context.length === 0) return;
|
||||||
let point = context[0].raw;
|
let point = context[0].raw;
|
||||||
@@ -124,7 +124,13 @@
|
|||||||
stacked: false
|
stacked: false
|
||||||
};
|
};
|
||||||
|
|
||||||
let datasets = {
|
let datasets: {
|
||||||
|
[key: string]: {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
units: string;
|
||||||
|
};
|
||||||
|
} = {
|
||||||
speed: {
|
speed: {
|
||||||
id: 'speed',
|
id: 'speed',
|
||||||
label: 'Speed',
|
label: 'Speed',
|
||||||
@@ -179,7 +185,7 @@
|
|||||||
{
|
{
|
||||||
id: 'toggleMarker',
|
id: 'toggleMarker',
|
||||||
events: ['mouseout'],
|
events: ['mouseout'],
|
||||||
afterEvent: function (chart, args) {
|
afterEvent: function (chart: Chart, args: { event: Chart.ChartEvent }) {
|
||||||
if (args.event.type === 'mouseout') {
|
if (args.event.type === 'mouseout') {
|
||||||
if ($map && marker) {
|
if ($map && marker) {
|
||||||
marker.remove();
|
marker.remove();
|
||||||
@@ -194,7 +200,7 @@
|
|||||||
|
|
||||||
$: if (chart) {
|
$: if (chart) {
|
||||||
let gpxFiles = new GPXFiles(Array.from($selectedFiles));
|
let gpxFiles = new GPXFiles(Array.from($selectedFiles));
|
||||||
let order = $fileOrder.length == 0 ? $files : $fileOrder;
|
let order = $fileOrder.length == 0 ? $fileCollection.files : $fileOrder;
|
||||||
gpxFiles.files.sort(function (a, b) {
|
gpxFiles.files.sort(function (a, b) {
|
||||||
return order.indexOf(a) - order.indexOf(b);
|
return order.indexOf(a) - order.indexOf(b);
|
||||||
});
|
});
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { fileOrder, files, selectedFiles, selectFiles } from '$lib/stores';
|
import { fileOrder, fileCollection, selectedFiles, selectFiles } from '$lib/stores';
|
||||||
|
|
||||||
import { ScrollArea } from '$lib/components/ui/scroll-area/index';
|
import { ScrollArea } from '$lib/components/ui/scroll-area/index';
|
||||||
import Sortable from 'sortablejs/Sortable';
|
import Sortable from 'sortablejs/Sortable';
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
|
|
||||||
function selectAllFiles() {
|
function selectAllFiles() {
|
||||||
selectedFiles.update((selectedFiles) => {
|
selectedFiles.update((selectedFiles) => {
|
||||||
get(files).forEach((file) => {
|
get(fileCollection).files.forEach((file) => {
|
||||||
selectedFiles.add(file);
|
selectedFiles.add(file);
|
||||||
});
|
});
|
||||||
return selectedFiles;
|
return selectedFiles;
|
||||||
@@ -53,12 +53,12 @@
|
|||||||
avoidImplicitDeselect: true,
|
avoidImplicitDeselect: true,
|
||||||
onSelect: (e) => {
|
onSelect: (e) => {
|
||||||
const index = parseInt(e.item.getAttribute('data-id'));
|
const index = parseInt(e.item.getAttribute('data-id'));
|
||||||
addSelectFile($files[index]);
|
addSelectFile($fileCollection.files[index]);
|
||||||
if (!e.originalEvent.shiftKey && $selectedFiles.size > 1) {
|
if (!e.originalEvent.shiftKey && $selectedFiles.size > 1) {
|
||||||
$selectedFiles.forEach((file) => {
|
$selectedFiles.forEach((file) => {
|
||||||
if (file !== $files[index]) {
|
if (file !== $fileCollection.files[index]) {
|
||||||
deselectFile(file);
|
deselectFile(file);
|
||||||
const index = $files.indexOf(file);
|
const index = $fileCollection.files.indexOf(file);
|
||||||
Sortable.utils.deselect(buttons[index]);
|
Sortable.utils.deselect(buttons[index]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -66,10 +66,12 @@
|
|||||||
},
|
},
|
||||||
onDeselect: (e) => {
|
onDeselect: (e) => {
|
||||||
const index = parseInt(e.item.getAttribute('data-id'));
|
const index = parseInt(e.item.getAttribute('data-id'));
|
||||||
deselectFile($files[index]);
|
deselectFile($fileCollection.files[index]);
|
||||||
},
|
},
|
||||||
onSort: () => {
|
onSort: () => {
|
||||||
$fileOrder = sortable.toArray().map((index) => $files[parseInt(index)]);
|
$fileOrder = sortable
|
||||||
|
.toArray()
|
||||||
|
.map((index: string) => $fileCollection.files[parseInt(index)]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -82,23 +84,23 @@
|
|||||||
Sortable.utils.deselect(button);
|
Sortable.utils.deselect(button);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const index = $files.indexOf(file);
|
const index = $fileCollection.files.indexOf(file);
|
||||||
Sortable.utils.select(buttons[index]);
|
Sortable.utils.select(buttons[index]);
|
||||||
selectFile(file);
|
selectFile(file);
|
||||||
},
|
},
|
||||||
addSelect: (file: GPXFile) => {
|
addSelect: (file: GPXFile) => {
|
||||||
const index = $files.indexOf(file);
|
const index = $fileCollection.files.indexOf(file);
|
||||||
Sortable.utils.select(buttons[index]);
|
Sortable.utils.select(buttons[index]);
|
||||||
addSelectFile(file);
|
addSelectFile(file);
|
||||||
},
|
},
|
||||||
selectAllFiles: () => {
|
selectAllFiles: () => {
|
||||||
$files.forEach((file, index) => {
|
$fileCollection.files.forEach((file, index) => {
|
||||||
Sortable.utils.select(buttons[index]);
|
Sortable.utils.select(buttons[index]);
|
||||||
});
|
});
|
||||||
selectAllFiles();
|
selectAllFiles();
|
||||||
},
|
},
|
||||||
removeSelect: (file: GPXFile) => {
|
removeSelect: (file: GPXFile) => {
|
||||||
const index = $files.indexOf(file);
|
const index = $fileCollection.files.indexOf(file);
|
||||||
Sortable.utils.deselect(buttons[index]);
|
Sortable.utils.deselect(buttons[index]);
|
||||||
deselectFile(file);
|
deselectFile(file);
|
||||||
}
|
}
|
||||||
@@ -109,7 +111,7 @@
|
|||||||
<div class="absolute h-10 -translate-y-10 w-fit max-w-full bg-secondary rounded-t">
|
<div class="absolute h-10 -translate-y-10 w-fit max-w-full bg-secondary rounded-t">
|
||||||
<ScrollArea orientation="horizontal" class="w-full h-full" scrollbarXClasses="h-2">
|
<ScrollArea orientation="horizontal" class="w-full h-full" scrollbarXClasses="h-2">
|
||||||
<div bind:this={tabs} class="flex flex-row gap-1">
|
<div bind:this={tabs} class="flex flex-row gap-1">
|
||||||
{#each $files as file, index}
|
{#each $fileCollection.files as file, index}
|
||||||
<button
|
<button
|
||||||
bind:this={buttons[index]}
|
bind:this={buttons[index]}
|
||||||
data-id={index}
|
data-id={index}
|
||||||
|
@@ -41,7 +41,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onDestroy, onMount } from 'svelte';
|
import { onDestroy, onMount } from 'svelte';
|
||||||
import { GPXFile } from 'gpx';
|
import { GPXFile } from 'gpx';
|
||||||
import { map, selectedFiles, selectFiles, files } from '$lib/stores';
|
import { map, selectedFiles, selectFiles, fileCollection } from '$lib/stores';
|
||||||
import { get } from 'svelte/store';
|
import { get } from 'svelte/store';
|
||||||
|
|
||||||
export let file: GPXFile;
|
export let file: GPXFile;
|
||||||
@@ -127,7 +127,7 @@
|
|||||||
onMount(() => {
|
onMount(() => {
|
||||||
addGPXLayer();
|
addGPXLayer();
|
||||||
if ($map) {
|
if ($map) {
|
||||||
if ($files.length == 1) {
|
if ($fileCollection.files.length == 1) {
|
||||||
$map.fitBounds([file.statistics.bounds.southWest, file.statistics.bounds.northEast], {
|
$map.fitBounds([file.statistics.bounds.southWest, file.statistics.bounds.northEast], {
|
||||||
padding: 60,
|
padding: 60,
|
||||||
linear: true,
|
linear: true,
|
||||||
|
@@ -15,7 +15,7 @@
|
|||||||
} from 'lucide-svelte';
|
} from 'lucide-svelte';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
files,
|
fileCollection,
|
||||||
selectedFiles,
|
selectedFiles,
|
||||||
duplicateSelectedFiles,
|
duplicateSelectedFiles,
|
||||||
exportAllFiles,
|
exportAllFiles,
|
||||||
@@ -62,7 +62,7 @@
|
|||||||
<Menubar.Item on:click={exportSelectedFiles} disabled={$selectedFiles.size == 0}>
|
<Menubar.Item on:click={exportSelectedFiles} disabled={$selectedFiles.size == 0}>
|
||||||
<Download size="16" class="mr-1" /> Export... <Menubar.Shortcut>⌘S</Menubar.Shortcut>
|
<Download size="16" class="mr-1" /> Export... <Menubar.Shortcut>⌘S</Menubar.Shortcut>
|
||||||
</Menubar.Item>
|
</Menubar.Item>
|
||||||
<Menubar.Item on:click={exportAllFiles} disabled={$files.length == 0}>
|
<Menubar.Item on:click={exportAllFiles} disabled={$fileCollection.files.length == 0}>
|
||||||
<Download size="16" class="mr-1" /> Export all... <Menubar.Shortcut
|
<Download size="16" class="mr-1" /> Export all... <Menubar.Shortcut
|
||||||
>⇧⌘S</Menubar.Shortcut
|
>⇧⌘S</Menubar.Shortcut
|
||||||
>
|
>
|
||||||
@@ -86,7 +86,7 @@
|
|||||||
<Menubar.Item
|
<Menubar.Item
|
||||||
class="text-destructive data-[highlighted]:text-destructive"
|
class="text-destructive data-[highlighted]:text-destructive"
|
||||||
on:click={removeAllFiles}
|
on:click={removeAllFiles}
|
||||||
disabled={$files.length == 0}
|
disabled={$fileCollection.files.length == 0}
|
||||||
>
|
>
|
||||||
<Trash2 size="16" class="mr-1" /> Delete all<Menubar.Shortcut>⇧⌘⌫</Menubar.Shortcut
|
<Trash2 size="16" class="mr-1" /> Delete all<Menubar.Shortcut>⇧⌘⌫</Menubar.Shortcut
|
||||||
></Menubar.Item
|
></Menubar.Item
|
||||||
|
@@ -1,14 +1,21 @@
|
|||||||
import { writable, get } from 'svelte/store';
|
import { writable, get } from 'svelte/store';
|
||||||
|
|
||||||
import mapboxgl from 'mapbox-gl';
|
import mapboxgl from 'mapbox-gl';
|
||||||
import { GPXFile, buildGPX, parseGPX } from 'gpx';
|
import { GPXFile, GPXFiles, buildGPX, parseGPX } from 'gpx';
|
||||||
|
|
||||||
export const map = writable<mapboxgl.Map | null>(null);
|
export const map = writable<mapboxgl.Map | null>(null);
|
||||||
export const files = writable<GPXFile[]>([]);
|
export const fileCollection = writable<GPXFiles>(new GPXFiles([]));
|
||||||
export const fileOrder = writable<GPXFile[]>([]);
|
export const fileOrder = writable<GPXFile[]>([]);
|
||||||
export const selectedFiles = writable<Set<GPXFile>>(new Set());
|
export const selectedFiles = writable<Set<GPXFile>>(new Set());
|
||||||
export const selectFiles = writable<{ [key: string]: (file?: GPXFile) => void }>({});
|
export const selectFiles = writable<{ [key: string]: (file?: GPXFile) => void }>({});
|
||||||
|
|
||||||
|
export function addFile(file: GPXFile) {
|
||||||
|
fileCollection.update($files => {
|
||||||
|
$files.files.push(file);
|
||||||
|
return $files;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function triggerFileInput() {
|
export function triggerFileInput() {
|
||||||
const input = document.createElement('input');
|
const input = document.createElement('input');
|
||||||
input.type = 'file';
|
input.type = 'file';
|
||||||
@@ -25,15 +32,15 @@ export function triggerFileInput() {
|
|||||||
|
|
||||||
export async function loadFiles(list: FileList) {
|
export async function loadFiles(list: FileList) {
|
||||||
for (let i = 0; i < list.length; i++) {
|
for (let i = 0; i < list.length; i++) {
|
||||||
await loadFile(list[i]);
|
let file = await loadFile(list[i]);
|
||||||
if (i == 0) {
|
if (i == 0 && file) {
|
||||||
get(selectFiles).select(get(files)[get(files).length - 1]);
|
get(selectFiles).select(file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loadFile(file: File) {
|
export async function loadFile(file: File) {
|
||||||
let result = await new Promise<void>((resolve) => {
|
let result = await new Promise<GPXFile | null>((resolve) => {
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = () => {
|
reader.onload = () => {
|
||||||
let data = reader.result?.toString() ?? null;
|
let data = reader.result?.toString() ?? null;
|
||||||
@@ -42,9 +49,11 @@ export async function loadFile(file: File) {
|
|||||||
if (gpx.metadata.name === undefined) {
|
if (gpx.metadata.name === undefined) {
|
||||||
gpx.metadata['name'] = file.name.split('.').slice(0, -1).join('.');
|
gpx.metadata['name'] = file.name.split('.').slice(0, -1).join('.');
|
||||||
}
|
}
|
||||||
files.update($files => [...$files, gpx]);
|
addFile(gpx);
|
||||||
|
resolve(gpx);
|
||||||
|
} else {
|
||||||
|
resolve(null);
|
||||||
}
|
}
|
||||||
resolve();
|
|
||||||
};
|
};
|
||||||
reader.readAsText(file);
|
reader.readAsText(file);
|
||||||
});
|
});
|
||||||
@@ -59,15 +68,15 @@ export function duplicateSelectedFiles() {
|
|||||||
|
|
||||||
export function duplicateFile(file: GPXFile) {
|
export function duplicateFile(file: GPXFile) {
|
||||||
let clone = file.clone();
|
let clone = file.clone();
|
||||||
files.update($files => [...$files, clone]);
|
addFile(clone);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeSelectedFiles() {
|
export function removeSelectedFiles() {
|
||||||
let index = 0;
|
let index = 0;
|
||||||
while (index < get(files).length) {
|
while (index < get(fileCollection).files.length) {
|
||||||
if (get(selectedFiles).has(get(files)[index])) {
|
if (get(selectedFiles).has(get(fileCollection).files[index])) {
|
||||||
files.update($files => {
|
fileCollection.update($files => {
|
||||||
$files.splice(index, 1);
|
$files.files.splice(index, 1);
|
||||||
return $files;
|
return $files;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -78,8 +87,8 @@ export function removeSelectedFiles() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function removeAllFiles() {
|
export function removeAllFiles() {
|
||||||
files.update($files => {
|
fileCollection.update($files => {
|
||||||
$files.splice(0, $files.length);
|
$files.files.splice(0, $files.files.length);
|
||||||
return $files;
|
return $files;
|
||||||
});
|
});
|
||||||
get(selectedFiles).clear();
|
get(selectedFiles).clear();
|
||||||
@@ -90,8 +99,8 @@ export function exportSelectedFiles() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function exportAllFiles() {
|
export async function exportAllFiles() {
|
||||||
for (let i = 0; i < get(files).length; i++) {
|
for (let file of get(fileCollection).files) {
|
||||||
exportFile(get(files)[i]);
|
exportFile(file);
|
||||||
await new Promise(resolve => setTimeout(resolve, 200));
|
await new Promise(resolve => setTimeout(resolve, 200));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user