mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2025-08-31 15:43:25 +00:00
export options
This commit is contained in:
@@ -1,198 +0,0 @@
|
||||
/**
|
||||
* For a detailed explanation regarding each configuration property, visit:
|
||||
* https://jestjs.io/docs/configuration
|
||||
*/
|
||||
|
||||
/** @type {import('jest').Config} */
|
||||
const config = {
|
||||
// All imported modules in your tests should be mocked automatically
|
||||
// automock: false,
|
||||
|
||||
// Stop running tests after `n` failures
|
||||
// bail: 0,
|
||||
|
||||
// The directory where Jest should store its cached dependency information
|
||||
// cacheDirectory: "/private/var/folders/hf/0lj0fwd15m55qqlzd3d29mtw0000gp/T/jest_dy",
|
||||
|
||||
// Automatically clear mock calls, instances, contexts and results before every test
|
||||
// clearMocks: false,
|
||||
|
||||
// Indicates whether the coverage information should be collected while executing the test
|
||||
// collectCoverage: false,
|
||||
|
||||
// An array of glob patterns indicating a set of files for which coverage information should be collected
|
||||
// collectCoverageFrom: undefined,
|
||||
|
||||
// The directory where Jest should output its coverage files
|
||||
// coverageDirectory: undefined,
|
||||
|
||||
// An array of regexp pattern strings used to skip coverage collection
|
||||
// coveragePathIgnorePatterns: [
|
||||
// "/node_modules/"
|
||||
// ],
|
||||
|
||||
// Indicates which provider should be used to instrument code for coverage
|
||||
coverageProvider: "v8",
|
||||
|
||||
// A list of reporter names that Jest uses when writing coverage reports
|
||||
// coverageReporters: [
|
||||
// "json",
|
||||
// "text",
|
||||
// "lcov",
|
||||
// "clover"
|
||||
// ],
|
||||
|
||||
// An object that configures minimum threshold enforcement for coverage results
|
||||
// coverageThreshold: undefined,
|
||||
|
||||
// A path to a custom dependency extractor
|
||||
// dependencyExtractor: undefined,
|
||||
|
||||
// Make calling deprecated APIs throw helpful error messages
|
||||
// errorOnDeprecated: false,
|
||||
|
||||
// The default configuration for fake timers
|
||||
// fakeTimers: {
|
||||
// "enableGlobally": false
|
||||
// },
|
||||
|
||||
// Force coverage collection from ignored files using an array of glob patterns
|
||||
// forceCoverageMatch: [],
|
||||
|
||||
// A path to a module which exports an async function that is triggered once before all test suites
|
||||
// globalSetup: undefined,
|
||||
|
||||
// A path to a module which exports an async function that is triggered once after all test suites
|
||||
// globalTeardown: undefined,
|
||||
|
||||
// A set of global variables that need to be available in all test environments
|
||||
// globals: {},
|
||||
|
||||
// The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
|
||||
// maxWorkers: "50%",
|
||||
|
||||
// An array of directory names to be searched recursively up from the requiring module's location
|
||||
// moduleDirectories: [
|
||||
// "node_modules"
|
||||
// ],
|
||||
|
||||
// An array of file extensions your modules use
|
||||
// moduleFileExtensions: [
|
||||
// "js",
|
||||
// "mjs",
|
||||
// "cjs",
|
||||
// "jsx",
|
||||
// "ts",
|
||||
// "tsx",
|
||||
// "json",
|
||||
// "node"
|
||||
// ],
|
||||
|
||||
// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
|
||||
// moduleNameMapper: {},
|
||||
|
||||
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
|
||||
// modulePathIgnorePatterns: [],
|
||||
|
||||
// Activates notifications for test results
|
||||
// notify: false,
|
||||
|
||||
// An enum that specifies notification mode. Requires { notify: true }
|
||||
// notifyMode: "failure-change",
|
||||
|
||||
// A preset that is used as a base for Jest's configuration
|
||||
preset: "ts-jest",
|
||||
|
||||
// Run tests from one or more projects
|
||||
// projects: undefined,
|
||||
|
||||
// Use this configuration option to add custom reporters to Jest
|
||||
// reporters: undefined,
|
||||
|
||||
// Automatically reset mock state before every test
|
||||
// resetMocks: false,
|
||||
|
||||
// Reset the module registry before running each individual test
|
||||
// resetModules: false,
|
||||
|
||||
// A path to a custom resolver
|
||||
// resolver: undefined,
|
||||
|
||||
// Automatically restore mock state and implementation before every test
|
||||
// restoreMocks: false,
|
||||
|
||||
// The root directory that Jest should scan for tests and modules within
|
||||
rootDir: "test",
|
||||
|
||||
// A list of paths to directories that Jest should use to search for files in
|
||||
// roots: [
|
||||
// "<rootDir>"
|
||||
// ],
|
||||
|
||||
// Allows you to use a custom runner instead of Jest's default test runner
|
||||
// runner: "jest-runner",
|
||||
|
||||
// The paths to modules that run some code to configure or set up the testing environment before each test
|
||||
// setupFiles: [],
|
||||
|
||||
// A list of paths to modules that run some code to configure or set up the testing framework before each test
|
||||
// setupFilesAfterEnv: [],
|
||||
|
||||
// The number of seconds after which a test is considered as slow and reported as such in the results.
|
||||
// slowTestThreshold: 5,
|
||||
|
||||
// A list of paths to snapshot serializer modules Jest should use for snapshot testing
|
||||
// snapshotSerializers: [],
|
||||
|
||||
// The test environment that will be used for testing
|
||||
// testEnvironment: "jest-environment-node",
|
||||
|
||||
// Options that will be passed to the testEnvironment
|
||||
// testEnvironmentOptions: {},
|
||||
|
||||
// Adds a location field to test results
|
||||
// testLocationInResults: false,
|
||||
|
||||
// The glob patterns Jest uses to detect test files
|
||||
// testMatch: [
|
||||
// "**/__tests__/**/*.[jt]s?(x)",
|
||||
// "**/?(*.)+(spec|test).[tj]s?(x)"
|
||||
// ],
|
||||
|
||||
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
|
||||
// testPathIgnorePatterns: [
|
||||
// "/node_modules/"
|
||||
// ],
|
||||
|
||||
// The regexp pattern or array of patterns that Jest uses to detect test files
|
||||
// testRegex: [],
|
||||
|
||||
// This option allows the use of a custom results processor
|
||||
// testResultsProcessor: undefined,
|
||||
|
||||
// This option allows use of a custom test runner
|
||||
// testRunner: "jest-circus/runner",
|
||||
|
||||
// A map from regular expressions to paths to transformers
|
||||
// transform: undefined,
|
||||
|
||||
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
|
||||
// transformIgnorePatterns: [
|
||||
// "/node_modules/",
|
||||
// "\\.pnp\\.[^\\/]+$"
|
||||
// ],
|
||||
|
||||
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
|
||||
// unmockedModulePathPatterns: undefined,
|
||||
|
||||
// Indicates whether each individual test should be reported during the run
|
||||
// verbose: undefined,
|
||||
|
||||
// An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
|
||||
// watchPathIgnorePatterns: [],
|
||||
|
||||
// Whether to use watchman for file crawling
|
||||
// watchman: true,
|
||||
};
|
||||
|
||||
export default config;
|
3615
gpx/package-lock.json
generated
3615
gpx/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -2,8 +2,8 @@
|
||||
"name": "gpx",
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"exports": "./dist/src/index.js",
|
||||
"types": "dist/src/index.d.ts",
|
||||
"exports": "./dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/gpxstudio/gpx.studio.git",
|
||||
@@ -16,15 +16,11 @@
|
||||
"ts-node": "^10.9.2"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"test": "jest"
|
||||
"build": "tsc"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/geojson": "^7946.0.14",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/node": "^20.14.6",
|
||||
"jest": "^29.7.0",
|
||||
"ts-jest": "^29.1.5",
|
||||
"typescript": "^5.4.5"
|
||||
}
|
||||
}
|
||||
}
|
137
gpx/src/gpx.ts
137
gpx/src/gpx.ts
@@ -212,14 +212,32 @@ export class GPXFile extends GPXTreeNode<Track>{
|
||||
};
|
||||
}
|
||||
|
||||
toGPXFileType(): GPXFileType {
|
||||
return {
|
||||
toGPXFileType(exclude: string[] = []): GPXFileType {
|
||||
let file: GPXFileType = {
|
||||
attributes: cloneJSON(this.attributes),
|
||||
metadata: cloneJSON(this.metadata),
|
||||
metadata: {},
|
||||
wpt: this.wpt,
|
||||
trk: this.trk.map((track) => track.toTrackType()),
|
||||
trk: this.trk.map((track) => track.toTrackType(exclude)),
|
||||
rte: [],
|
||||
};
|
||||
if (this.metadata) {
|
||||
if (this.metadata.name) {
|
||||
file.metadata.name = this.metadata.name;
|
||||
}
|
||||
if (this.metadata.desc) {
|
||||
file.metadata.desc = this.metadata.desc;
|
||||
}
|
||||
if (this.metadata.author) {
|
||||
file.metadata.author = cloneJSON(this.metadata.author);
|
||||
}
|
||||
if (this.metadata.link) {
|
||||
file.metadata.link = cloneJSON(this.metadata.link);
|
||||
}
|
||||
if (this.metadata.time && !exclude.includes('time')) {
|
||||
file.metadata.time = this.metadata.time;
|
||||
}
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
// Producers
|
||||
@@ -475,7 +493,7 @@ export class Track extends GPXTreeNode<TrackSegment> {
|
||||
});
|
||||
}
|
||||
|
||||
toTrackType(): TrackType {
|
||||
toTrackType(exclude: string[] = []): TrackType {
|
||||
return {
|
||||
name: this.name,
|
||||
cmt: this.cmt,
|
||||
@@ -483,7 +501,7 @@ export class Track extends GPXTreeNode<TrackSegment> {
|
||||
src: this.src,
|
||||
link: this.link,
|
||||
type: this.type,
|
||||
trkseg: this.trkseg.map((seg) => seg.toTrackSegmentType()),
|
||||
trkseg: this.trkseg.map((seg) => seg.toTrackSegmentType(exclude)),
|
||||
extensions: this.extensions,
|
||||
};
|
||||
}
|
||||
@@ -678,6 +696,30 @@ export class TrackSegment extends GPXTreeLeaf {
|
||||
statistics.global.bounds.southWest.lon = Math.min(statistics.global.bounds.southWest.lon, points[i].attributes.lon);
|
||||
statistics.global.bounds.northEast.lat = Math.max(statistics.global.bounds.northEast.lat, points[i].attributes.lat);
|
||||
statistics.global.bounds.northEast.lon = Math.max(statistics.global.bounds.northEast.lon, points[i].attributes.lon);
|
||||
|
||||
// extensions
|
||||
if (points[i].extensions) {
|
||||
if (points[i].extensions["gpxtpx:TrackPointExtension"] && points[i].extensions["gpxtpx:TrackPointExtension"]["gpxtpx:hr"]) {
|
||||
let hr = points[i].extensions["gpxtpx:TrackPointExtension"]["gpxtpx:hr"];
|
||||
statistics.global.hr.avg = (statistics.global.hr.count * statistics.global.hr.avg + hr) / (statistics.global.hr.count + 1);
|
||||
statistics.global.hr.count++;
|
||||
}
|
||||
if (points[i].extensions["gpxtpx:TrackPointExtension"] && points[i].extensions["gpxtpx:TrackPointExtension"]["gpxtpx:cad"]) {
|
||||
let cad = points[i].extensions["gpxtpx:TrackPointExtension"]["gpxtpx:cad"];
|
||||
statistics.global.cad.avg = (statistics.global.cad.count * statistics.global.cad.avg + cad) / (statistics.global.cad.count + 1);
|
||||
statistics.global.cad.count++;
|
||||
}
|
||||
if (points[i].extensions["gpxtpx:TrackPointExtension"] && points[i].extensions["gpxtpx:TrackPointExtension"]["gpxtpx:atemp"]) {
|
||||
let atemp = points[i].extensions["gpxtpx:TrackPointExtension"]["gpxtpx:atemp"];
|
||||
statistics.global.atemp.avg = (statistics.global.atemp.count * statistics.global.atemp.avg + atemp) / (statistics.global.atemp.count + 1);
|
||||
statistics.global.atemp.count++;
|
||||
}
|
||||
if (points[i].extensions["gpxpx:PowerExtension"] && points[i].extensions["gpxpx:PowerExtension"]["gpxpx:PowerInWatts"]) {
|
||||
let power = points[i].extensions["gpxpx:PowerExtension"]["gpxpx:PowerInWatts"];
|
||||
statistics.global.power.avg = (statistics.global.power.count * statistics.global.power.avg + power) / (statistics.global.power.count + 1);
|
||||
statistics.global.power.count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[statistics.local.slope.segment, statistics.local.slope.length] = this._computeSlopeSegments(statistics);
|
||||
@@ -790,9 +832,9 @@ export class TrackSegment extends GPXTreeLeaf {
|
||||
};
|
||||
}
|
||||
|
||||
toTrackSegmentType(): TrackSegmentType {
|
||||
toTrackSegmentType(exclude: string[] = []): TrackSegmentType {
|
||||
return {
|
||||
trkpt: this.trkpt.map((point) => point.toTrackPointType())
|
||||
trkpt: this.trkpt.map((point) => point.toTrackPointType(exclude))
|
||||
};
|
||||
}
|
||||
|
||||
@@ -975,13 +1017,38 @@ export class TrackPoint {
|
||||
this.extensions["gpxtpx:TrackPointExtension"]["gpxtpx:Extensions"]["surface"] = surface;
|
||||
}
|
||||
|
||||
toTrackPointType(): TrackPointType {
|
||||
return {
|
||||
toTrackPointType(exclude: string[] = []): TrackPointType {
|
||||
let trkpt: TrackPointType = {
|
||||
attributes: this.attributes,
|
||||
ele: this.ele,
|
||||
time: this.time,
|
||||
extensions: this.extensions,
|
||||
};
|
||||
if (!exclude.includes('time')) {
|
||||
trkpt = { ...trkpt, time: this.time };
|
||||
}
|
||||
if (this.extensions) {
|
||||
trkpt = {
|
||||
...trkpt, extensions: {
|
||||
"gpxtpx:TrackPointExtension": {},
|
||||
"gpxpx:PowerExtension": {},
|
||||
}
|
||||
};
|
||||
if (this.extensions["gpxtpx:TrackPointExtension"] && this.extensions["gpxtpx:TrackPointExtension"]["gpxtpx:hr"] && !exclude.includes('hr')) {
|
||||
trkpt.extensions["gpxtpx:TrackPointExtension"]["gpxtpx:hr"] = this.extensions["gpxtpx:TrackPointExtension"]["gpxtpx:hr"];
|
||||
}
|
||||
if (this.extensions["gpxtpx:TrackPointExtension"] && this.extensions["gpxtpx:TrackPointExtension"]["gpxtpx:cad"] && !exclude.includes('cad')) {
|
||||
trkpt.extensions["gpxtpx:TrackPointExtension"]["gpxtpx:cad"] = this.extensions["gpxtpx:TrackPointExtension"]["gpxtpx:cad"];
|
||||
}
|
||||
if (this.extensions["gpxtpx:TrackPointExtension"] && this.extensions["gpxtpx:TrackPointExtension"]["gpxtpx:atemp"] && !exclude.includes('atemp')) {
|
||||
trkpt.extensions["gpxtpx:TrackPointExtension"]["gpxtpx:atemp"] = this.extensions["gpxtpx:TrackPointExtension"]["gpxtpx:atemp"];
|
||||
}
|
||||
if (this.extensions["gpxpx:PowerExtension"] && this.extensions["gpxpx:PowerExtension"]["gpxpx:PowerInWatts"] && !exclude.includes('power')) {
|
||||
trkpt.extensions["gpxpx:PowerExtension"]["gpxpx:PowerInWatts"] = this.extensions["gpxpx:PowerExtension"]["gpxpx:PowerInWatts"];
|
||||
}
|
||||
if (this.extensions["gpxtpx:TrackPointExtension"] && this.extensions["gpxtpx:TrackPointExtension"]["gpxtpx:Extensions"] && this.extensions["gpxtpx:TrackPointExtension"]["gpxtpx:Extensions"].surface && !exclude.includes('surface')) {
|
||||
trkpt.extensions["gpxtpx:TrackPointExtension"]["gpxtpx:Extensions"] = { surface: this.extensions["gpxtpx:TrackPointExtension"]["gpxtpx:Extensions"].surface };
|
||||
}
|
||||
}
|
||||
return trkpt;
|
||||
}
|
||||
|
||||
clone(): TrackPoint {
|
||||
@@ -1084,6 +1151,22 @@ export class GPXStatistics {
|
||||
southWest: Coordinates,
|
||||
northEast: Coordinates,
|
||||
},
|
||||
hr: {
|
||||
avg: number,
|
||||
count: number,
|
||||
},
|
||||
cad: {
|
||||
avg: number,
|
||||
count: number,
|
||||
},
|
||||
atemp: {
|
||||
avg: number,
|
||||
count: number,
|
||||
},
|
||||
power: {
|
||||
avg: number,
|
||||
count: number,
|
||||
}
|
||||
};
|
||||
local: {
|
||||
points: TrackPoint[],
|
||||
@@ -1138,6 +1221,22 @@ export class GPXStatistics {
|
||||
lon: -180,
|
||||
},
|
||||
},
|
||||
hr: {
|
||||
avg: 0,
|
||||
count: 0,
|
||||
},
|
||||
cad: {
|
||||
avg: 0,
|
||||
count: 0,
|
||||
},
|
||||
atemp: {
|
||||
avg: 0,
|
||||
count: 0,
|
||||
},
|
||||
power: {
|
||||
avg: 0,
|
||||
count: 0,
|
||||
}
|
||||
};
|
||||
this.local = {
|
||||
points: [],
|
||||
@@ -1198,6 +1297,15 @@ export class GPXStatistics {
|
||||
this.global.bounds.southWest.lon = Math.min(this.global.bounds.southWest.lon, other.global.bounds.southWest.lon);
|
||||
this.global.bounds.northEast.lat = Math.max(this.global.bounds.northEast.lat, other.global.bounds.northEast.lat);
|
||||
this.global.bounds.northEast.lon = Math.max(this.global.bounds.northEast.lon, other.global.bounds.northEast.lon);
|
||||
|
||||
this.global.hr.avg = (this.global.hr.count * this.global.hr.avg + other.global.hr.count * other.global.hr.avg) / Math.max(1, this.global.hr.count + other.global.hr.count);
|
||||
this.global.hr.count += other.global.hr.count;
|
||||
this.global.cad.avg = (this.global.cad.count * this.global.cad.avg + other.global.cad.count * other.global.cad.avg) / Math.max(1, this.global.cad.count + other.global.cad.count);
|
||||
this.global.cad.count += other.global.cad.count;
|
||||
this.global.atemp.avg = (this.global.atemp.count * this.global.atemp.avg + other.global.atemp.count * other.global.atemp.avg) / Math.max(1, this.global.atemp.count + other.global.atemp.count);
|
||||
this.global.atemp.count += other.global.atemp.count;
|
||||
this.global.power.avg = (this.global.power.count * this.global.power.avg + other.global.power.count * other.global.power.avg) / Math.max(1, this.global.power.count + other.global.power.count);
|
||||
this.global.power.count += other.global.power.count;
|
||||
}
|
||||
|
||||
slice(start: number, end: number): GPXStatistics {
|
||||
@@ -1225,6 +1333,11 @@ export class GPXStatistics {
|
||||
statistics.global.bounds.northEast.lat = this.global.bounds.northEast.lat;
|
||||
statistics.global.bounds.northEast.lon = this.global.bounds.northEast.lon;
|
||||
|
||||
statistics.global.hr = this.global.hr;
|
||||
statistics.global.cad = this.global.cad;
|
||||
statistics.global.atemp = this.global.atemp;
|
||||
statistics.global.power = this.global.power;
|
||||
|
||||
return statistics;
|
||||
}
|
||||
}
|
||||
|
@@ -61,9 +61,8 @@ export function parseGPX(gpxData: string): GPXFile {
|
||||
return new GPXFile(parsed);
|
||||
}
|
||||
|
||||
|
||||
export function buildGPX(file: GPXFile): string {
|
||||
const gpx = file.toGPXFileType();
|
||||
export function buildGPX(file: GPXFile, exclude: string[]): string {
|
||||
const gpx = file.toGPXFileType(exclude);
|
||||
|
||||
const builder = new XMLBuilder({
|
||||
format: true,
|
||||
|
@@ -1,59 +0,0 @@
|
||||
import * as fs from 'fs';
|
||||
|
||||
import { parseGPX, buildGPX } from '../src/io';
|
||||
|
||||
describe('GPX operations', () => {
|
||||
it('Clone', () => {
|
||||
const path = "test-data/with_tracks_and_segments.gpx";
|
||||
const data = fs.readFileSync(path, 'utf8');
|
||||
const original = parseGPX(data);
|
||||
|
||||
const cloned = original.clone();
|
||||
|
||||
expect(cloned).not.toBe(original);
|
||||
|
||||
const originalString = buildGPX(original);
|
||||
const clonedString = buildGPX(cloned);
|
||||
|
||||
expect(clonedString).toBe(originalString);
|
||||
});
|
||||
|
||||
it('Reverse', () => {
|
||||
const path = "test-data/with_tracks_and_segments.gpx";
|
||||
const data = fs.readFileSync(path, 'utf8');
|
||||
const original = parseGPX(data);
|
||||
|
||||
let reversed = original.clone();
|
||||
reversed.reverse();
|
||||
|
||||
expect(original.getStartTimestamp().getTime()).toBe(reversed.getStartTimestamp().getTime());
|
||||
expect(original.getEndTimestamp().getTime()).toBe(reversed.getEndTimestamp().getTime());
|
||||
|
||||
expect(reversed.trk.length).toBe(original.trk.length);
|
||||
|
||||
for (let i = 0; i < original.trk.length; i++) {
|
||||
const originalTrack = original.trk[i];
|
||||
const reversedTrack = reversed.trk[original.trk.length - i - 1];
|
||||
|
||||
expect(reversedTrack.trkseg.length).toBe(originalTrack.trkseg.length);
|
||||
|
||||
for (let j = 0; j < originalTrack.trkseg.length; j++) {
|
||||
const originalSegment = originalTrack.trkseg[j];
|
||||
const reversedSegment = reversedTrack.trkseg[originalTrack.trkseg.length - j - 1];
|
||||
|
||||
expect(reversedSegment.trkpt.length).toBe(originalSegment.trkpt.length);
|
||||
|
||||
for (let k = 0; k < originalSegment.trkpt.length; k++) {
|
||||
const originalPoint = originalSegment.trkpt[k];
|
||||
const reversedPoint = reversedSegment.trkpt[originalSegment.trkpt.length - k - 1];
|
||||
|
||||
expect(reversedPoint.attributes.lat).toBe(originalPoint.attributes.lat);
|
||||
expect(reversedPoint.attributes.lon).toBe(originalPoint.attributes.lon);
|
||||
expect(reversedPoint.ele).toBe(originalPoint.ele);
|
||||
|
||||
expect(reversed.getEndTimestamp().getTime() - reversedPoint.time.getTime()).toBe(originalPoint.time.getTime() - original.getStartTimestamp().getTime());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
@@ -1,364 +0,0 @@
|
||||
import * as fs from 'fs';
|
||||
|
||||
import { parseGPX, buildGPX } from '../src/io';
|
||||
|
||||
describe("Parsing", () => {
|
||||
it("Simple", () => {
|
||||
const path = "test-data/simple.gpx";
|
||||
const data = fs.readFileSync(path, 'utf8');
|
||||
const result = parseGPX(data);
|
||||
|
||||
expect(result.attributes.creator).toBe("https://gpx.studio");
|
||||
expect(result.metadata.name).toBe("simple");
|
||||
expect(result.metadata.author.name).toBe("gpx.studio");
|
||||
expect(result.metadata.author.link.attributes.href).toBe("https://gpx.studio");
|
||||
|
||||
expect(result.trk.length).toBe(1);
|
||||
|
||||
const track = result.trk[0];
|
||||
expect(track.name).toBe("simple");
|
||||
expect(track.type).toBe("Cycling");
|
||||
expect(track.trkseg.length).toBe(1);
|
||||
|
||||
const segment = track.trkseg[0];
|
||||
expect(segment.trkpt.length).toBe(80);
|
||||
|
||||
for (let i = 0; i < segment.trkpt.length; i++) {
|
||||
const point = segment.trkpt[i];
|
||||
expect(point).toHaveProperty('attributes');
|
||||
expect(point.attributes).toHaveProperty('lat');
|
||||
expect(point.attributes).toHaveProperty('lon');
|
||||
expect(point).toHaveProperty('ele');
|
||||
}
|
||||
|
||||
expect(segment.trkpt[0].attributes.lat).toBe(50.790867);
|
||||
expect(segment.trkpt[0].attributes.lon).toBe(4.404968);
|
||||
expect(segment.trkpt[0].ele).toBe(109.0);
|
||||
});
|
||||
|
||||
it("Multiple tracks", () => {
|
||||
const path = "test-data/with_tracks.gpx";
|
||||
const data = fs.readFileSync(path, 'utf8');
|
||||
const result = parseGPX(data);
|
||||
|
||||
expect(result.trk.length).toBe(2);
|
||||
|
||||
const track_1 = result.trk[0];
|
||||
expect(track_1.name).toBe("track 1");
|
||||
expect(track_1.trkseg.length).toBe(1);
|
||||
expect(track_1.trkseg[0].trkpt.length).toBe(49);
|
||||
|
||||
const track_2 = result.trk[1];
|
||||
expect(track_2.name).toBe("track 2");
|
||||
expect(track_2.trkseg.length).toBe(1);
|
||||
expect(track_2.trkseg[0].trkpt.length).toBe(28);
|
||||
});
|
||||
|
||||
it("Multiple segments", () => {
|
||||
const path = "test-data/with_segments.gpx";
|
||||
const data = fs.readFileSync(path, 'utf8');
|
||||
const result = parseGPX(data);
|
||||
|
||||
expect(result.trk.length).toBe(1);
|
||||
|
||||
const track = result.trk[0];
|
||||
expect(track.trkseg.length).toBe(2);
|
||||
expect(track.trkseg[0].trkpt.length).toBe(49);
|
||||
expect(track.trkseg[1].trkpt.length).toBe(28);
|
||||
});
|
||||
|
||||
it("Waypoint", () => {
|
||||
const path = "test-data/with_waypoint.gpx";
|
||||
const data = fs.readFileSync(path, 'utf8');
|
||||
const result = parseGPX(data);
|
||||
|
||||
expect(result.wpt.length).toBe(1);
|
||||
|
||||
const waypoint = result.wpt[0];
|
||||
expect(waypoint.attributes.lat).toBe(50.7836710064975);
|
||||
expect(waypoint.attributes.lon).toBe(4.410764082658738);
|
||||
expect(waypoint.ele).toBe(122.0);
|
||||
expect(waypoint.name).toBe("Waypoint");
|
||||
expect(waypoint.cmt).toBe("Comment");
|
||||
expect(waypoint.desc).toBe("Description");
|
||||
expect(waypoint.sym).toBe("Bike Trail");
|
||||
});
|
||||
|
||||
it("Time", () => {
|
||||
const path = "test-data/with_time.gpx";
|
||||
const data = fs.readFileSync(path, 'utf8');
|
||||
const result = parseGPX(data);
|
||||
|
||||
const track = result.trk[0];
|
||||
const segment = track.trkseg[0];
|
||||
|
||||
for (let i = 0; i < segment.trkpt.length; i++) {
|
||||
expect(segment.trkpt[i].time).toBeInstanceOf(Date);
|
||||
}
|
||||
|
||||
expect(segment.trkpt[0].time).toEqual(new Date("2023-12-31T23:00:00.000Z"));
|
||||
expect(segment.trkpt[segment.trkpt.length - 1].time).toEqual(new Date("2023-12-31T23:06:40.567Z"));
|
||||
});
|
||||
|
||||
it("Heart rate", () => {
|
||||
const path = "test-data/with_hr.gpx";
|
||||
const data = fs.readFileSync(path, 'utf8');
|
||||
const result = parseGPX(data);
|
||||
|
||||
const track = result.trk[0];
|
||||
const segment = track.trkseg[0];
|
||||
|
||||
for (let i = 0; i < segment.trkpt.length; i++) {
|
||||
expect(segment.trkpt[i]).toHaveProperty('extensions');
|
||||
expect(segment.trkpt[i].extensions).toHaveProperty('gpxtpx:TrackPointExtension');
|
||||
expect(segment.trkpt[i].extensions['gpxtpx:TrackPointExtension']).toHaveProperty('gpxtpx:hr');
|
||||
}
|
||||
|
||||
expect(segment.trkpt[0].extensions['gpxtpx:TrackPointExtension']['gpxtpx:hr']).toBe(150);
|
||||
expect(segment.trkpt[segment.trkpt.length - 1].extensions['gpxtpx:TrackPointExtension']['gpxtpx:hr']).toBe(160);
|
||||
});
|
||||
|
||||
it("Cadence", () => {
|
||||
const path = "test-data/with_cad.gpx";
|
||||
const data = fs.readFileSync(path, 'utf8');
|
||||
const result = parseGPX(data);
|
||||
|
||||
const track = result.trk[0];
|
||||
const segment = track.trkseg[0];
|
||||
|
||||
for (let i = 0; i < segment.trkpt.length; i++) {
|
||||
expect(segment.trkpt[i]).toHaveProperty('extensions');
|
||||
expect(segment.trkpt[i].extensions).toHaveProperty('gpxtpx:TrackPointExtension');
|
||||
expect(segment.trkpt[i].extensions['gpxtpx:TrackPointExtension']).toHaveProperty('gpxtpx:cad');
|
||||
}
|
||||
|
||||
expect(segment.trkpt[0].extensions['gpxtpx:TrackPointExtension']['gpxtpx:cad']).toBe(80);
|
||||
expect(segment.trkpt[segment.trkpt.length - 1].extensions['gpxtpx:TrackPointExtension']['gpxtpx:cad']).toBe(90);
|
||||
});
|
||||
|
||||
it("Temperature", () => {
|
||||
const path = "test-data/with_temp.gpx";
|
||||
const data = fs.readFileSync(path, 'utf8');
|
||||
const result = parseGPX(data);
|
||||
|
||||
const track = result.trk[0];
|
||||
const segment = track.trkseg[0];
|
||||
|
||||
for (let i = 0; i < segment.trkpt.length; i++) {
|
||||
expect(segment.trkpt[i]).toHaveProperty('extensions');
|
||||
expect(segment.trkpt[i].extensions).toHaveProperty('gpxtpx:TrackPointExtension');
|
||||
expect(segment.trkpt[i].extensions['gpxtpx:TrackPointExtension']).toHaveProperty('gpxtpx:atemp');
|
||||
}
|
||||
|
||||
expect(segment.trkpt[0].extensions['gpxtpx:TrackPointExtension']['gpxtpx:atemp']).toBe(21);
|
||||
expect(segment.trkpt[segment.trkpt.length - 1].extensions['gpxtpx:TrackPointExtension']['gpxtpx:atemp']).toBe(22);
|
||||
});
|
||||
|
||||
it("Power 1", () => {
|
||||
const path = "test-data/with_power_1.gpx";
|
||||
const data = fs.readFileSync(path, 'utf8');
|
||||
const result = parseGPX(data);
|
||||
|
||||
const track = result.trk[0];
|
||||
const segment = track.trkseg[0];
|
||||
|
||||
for (let i = 0; i < segment.trkpt.length; i++) {
|
||||
expect(segment.trkpt[i]).toHaveProperty('extensions');
|
||||
expect(segment.trkpt[i].extensions).toHaveProperty('gpxpx:PowerExtension');
|
||||
expect(segment.trkpt[i].extensions['gpxpx:PowerExtension']).toHaveProperty('gpxpx:PowerInWatts');
|
||||
}
|
||||
|
||||
expect(segment.trkpt[0].extensions['gpxpx:PowerExtension']['gpxpx:PowerInWatts']).toBe(200);
|
||||
expect(segment.trkpt[segment.trkpt.length - 1].extensions['gpxpx:PowerExtension']['gpxpx:PowerInWatts']).toBe(210);
|
||||
});
|
||||
|
||||
it("Power 2", () => {
|
||||
const path = "test-data/with_power_2.gpx";
|
||||
const data = fs.readFileSync(path, 'utf8');
|
||||
const result = parseGPX(data);
|
||||
|
||||
const track = result.trk[0];
|
||||
const segment = track.trkseg[0];
|
||||
|
||||
for (let i = 0; i < segment.trkpt.length; i++) {
|
||||
expect(segment.trkpt[i]).toHaveProperty('extensions');
|
||||
expect(segment.trkpt[i].extensions).toHaveProperty('gpxpx:PowerExtension');
|
||||
expect(segment.trkpt[i].extensions['gpxpx:PowerExtension']).toHaveProperty('gpxpx:PowerInWatts');
|
||||
}
|
||||
|
||||
expect(segment.trkpt[0].extensions['gpxpx:PowerExtension']['gpxpx:PowerInWatts']).toBe(200);
|
||||
expect(segment.trkpt[segment.trkpt.length - 1].extensions['gpxpx:PowerExtension']['gpxpx:PowerInWatts']).toBe(210);
|
||||
});
|
||||
|
||||
it("Surface", () => {
|
||||
const path = "test-data/with_surface.gpx";
|
||||
const data = fs.readFileSync(path, 'utf8');
|
||||
const result = parseGPX(data);
|
||||
|
||||
const track = result.trk[0];
|
||||
const segment = track.trkseg[0];
|
||||
|
||||
for (let i = 0; i < segment.trkpt.length; i++) {
|
||||
expect(segment.trkpt[i]).toHaveProperty('extensions');
|
||||
expect(segment.trkpt[i].extensions).toHaveProperty('gpxtpx:TrackPointExtension');
|
||||
expect(segment.trkpt[i].extensions['gpxtpx:TrackPointExtension']).toHaveProperty('gpxtpx:Extensions');
|
||||
expect(segment.trkpt[i].extensions['gpxtpx:TrackPointExtension']['gpxtpx:Extensions']).toHaveProperty('surface');
|
||||
}
|
||||
|
||||
expect(segment.trkpt[0].extensions['gpxtpx:TrackPointExtension']['gpxtpx:Extensions'].surface).toBe("asphalt");
|
||||
expect(segment.trkpt[segment.trkpt.length - 1].extensions['gpxtpx:TrackPointExtension']['gpxtpx:Extensions'].surface).toBe("cobblestone");
|
||||
});
|
||||
|
||||
it("Track style", () => {
|
||||
const path = "test-data/with_style.gpx";
|
||||
const data = fs.readFileSync(path, 'utf8');
|
||||
const result = parseGPX(data);
|
||||
|
||||
const track = result.trk[0];
|
||||
|
||||
expect(track).toHaveProperty('extensions');
|
||||
expect(track.extensions).toHaveProperty('gpx_style:line');
|
||||
|
||||
expect(track.extensions['gpx_style:line']).toHaveProperty('color');
|
||||
expect(track.extensions['gpx_style:line']).toHaveProperty('opacity');
|
||||
expect(track.extensions['gpx_style:line']).toHaveProperty('weight');
|
||||
|
||||
expect(track.extensions['gpx_style:line'].color).toBe("#2d3ee9");
|
||||
expect(track.extensions['gpx_style:line'].opacity).toBe(0.5);
|
||||
expect(track.extensions['gpx_style:line'].weight).toBe(6);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Building", () => {
|
||||
it("Simple", () => {
|
||||
const path = "test-data/simple.gpx";
|
||||
const data = fs.readFileSync(path, 'utf8');
|
||||
const original = parseGPX(data);
|
||||
|
||||
const built = buildGPX(original);
|
||||
const rebuilt = parseGPX(built);
|
||||
|
||||
expect(rebuilt).toEqual(original);
|
||||
});
|
||||
|
||||
it("Multiple tracks", () => {
|
||||
const path = "test-data/with_tracks.gpx";
|
||||
const data = fs.readFileSync(path, 'utf8');
|
||||
const original = parseGPX(data);
|
||||
|
||||
const built = buildGPX(original);
|
||||
const rebuilt = parseGPX(built);
|
||||
|
||||
expect(rebuilt).toEqual(original);
|
||||
});
|
||||
|
||||
it("Multiple segments", () => {
|
||||
const path = "test-data/with_segments.gpx";
|
||||
const data = fs.readFileSync(path, 'utf8');
|
||||
const original = parseGPX(data);
|
||||
|
||||
const built = buildGPX(original);
|
||||
const rebuilt = parseGPX(built);
|
||||
|
||||
expect(rebuilt).toEqual(original);
|
||||
});
|
||||
|
||||
it("Waypoint", () => {
|
||||
const path = "test-data/with_waypoint.gpx";
|
||||
const data = fs.readFileSync(path, 'utf8');
|
||||
const original = parseGPX(data);
|
||||
|
||||
const built = buildGPX(original);
|
||||
const rebuilt = parseGPX(built);
|
||||
|
||||
expect(rebuilt).toEqual(original);
|
||||
});
|
||||
|
||||
it("Time", () => {
|
||||
const path = "test-data/with_time.gpx";
|
||||
const data = fs.readFileSync(path, 'utf8');
|
||||
const original = parseGPX(data);
|
||||
|
||||
const built = buildGPX(original);
|
||||
const rebuilt = parseGPX(built);
|
||||
|
||||
expect(rebuilt).toEqual(original);
|
||||
});
|
||||
|
||||
it("Heart rate", () => {
|
||||
const path = "test-data/with_hr.gpx";
|
||||
const data = fs.readFileSync(path, 'utf8');
|
||||
const original = parseGPX(data);
|
||||
|
||||
const built = buildGPX(original);
|
||||
const rebuilt = parseGPX(built);
|
||||
|
||||
expect(rebuilt).toEqual(original);
|
||||
});
|
||||
|
||||
it("Cadence", () => {
|
||||
const path = "test-data/with_cad.gpx";
|
||||
const data = fs.readFileSync(path, 'utf8');
|
||||
const original = parseGPX(data);
|
||||
|
||||
const built = buildGPX(original);
|
||||
const rebuilt = parseGPX(built);
|
||||
|
||||
expect(rebuilt).toEqual(original);
|
||||
});
|
||||
|
||||
it("Temperature", () => {
|
||||
const path = "test-data/with_temp.gpx";
|
||||
const data = fs.readFileSync(path, 'utf8');
|
||||
const original = parseGPX(data);
|
||||
|
||||
const built = buildGPX(original);
|
||||
const rebuilt = parseGPX(built);
|
||||
|
||||
expect(rebuilt).toEqual(original);
|
||||
});
|
||||
|
||||
it("Power 1", () => {
|
||||
const path = "test-data/with_power_1.gpx";
|
||||
const data = fs.readFileSync(path, 'utf8');
|
||||
const original = parseGPX(data);
|
||||
|
||||
const built = buildGPX(original);
|
||||
const rebuilt = parseGPX(built);
|
||||
|
||||
expect(rebuilt).toEqual(original);
|
||||
});
|
||||
|
||||
it("Power 2", () => {
|
||||
const path = "test-data/with_power_2.gpx";
|
||||
const data = fs.readFileSync(path, 'utf8');
|
||||
const original = parseGPX(data);
|
||||
|
||||
const built = buildGPX(original);
|
||||
const rebuilt = parseGPX(built);
|
||||
|
||||
expect(rebuilt).toEqual(original);
|
||||
});
|
||||
|
||||
it("Surface", () => {
|
||||
const path = "test-data/with_surface.gpx";
|
||||
const data = fs.readFileSync(path, 'utf8');
|
||||
const original = parseGPX(data);
|
||||
|
||||
const built = buildGPX(original);
|
||||
const rebuilt = parseGPX(built);
|
||||
|
||||
expect(rebuilt).toEqual(original);
|
||||
});
|
||||
|
||||
it("Track style", () => {
|
||||
const path = "test-data/with_style.gpx";
|
||||
const data = fs.readFileSync(path, 'utf8');
|
||||
const original = parseGPX(data);
|
||||
|
||||
const built = buildGPX(original);
|
||||
const rebuilt = parseGPX(built);
|
||||
|
||||
expect(rebuilt).toEqual(original);
|
||||
});
|
||||
});
|
@@ -7,7 +7,6 @@
|
||||
"moduleResolution": "node",
|
||||
},
|
||||
"include": [
|
||||
"src",
|
||||
"test"
|
||||
"src"
|
||||
],
|
||||
}
|
3
website/package-lock.json
generated
3
website/package-lock.json
generated
@@ -71,10 +71,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/geojson": "^7946.0.14",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/node": "^20.14.6",
|
||||
"jest": "^29.7.0",
|
||||
"ts-jest": "^29.1.5",
|
||||
"typescript": "^5.4.5"
|
||||
}
|
||||
},
|
||||
|
@@ -67,4 +67,4 @@
|
||||
"tailwind-merge": "^2.3.0",
|
||||
"tailwind-variants": "^0.2.1"
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,24 +1,75 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import { Checkbox } from '$lib/components/ui/checkbox';
|
||||
import { Separator } from '$lib/components/ui/separator';
|
||||
import { Dialog } from 'bits-ui';
|
||||
import {
|
||||
currentTool,
|
||||
exportAllFiles,
|
||||
exportSelectedFiles,
|
||||
ExportState,
|
||||
exportState
|
||||
exportState,
|
||||
gpxStatistics
|
||||
} from '$lib/stores';
|
||||
import { fileObservers } from '$lib/db';
|
||||
import { Download } from 'lucide-svelte';
|
||||
import {
|
||||
Download,
|
||||
Zap,
|
||||
BrickWall,
|
||||
HeartPulse,
|
||||
Orbit,
|
||||
Thermometer,
|
||||
SquareActivity
|
||||
} from 'lucide-svelte';
|
||||
import { _ } from 'svelte-i18n';
|
||||
import { selection } from './file-list/Selection';
|
||||
import { get } from 'svelte/store';
|
||||
import { GPXStatistics } from 'gpx';
|
||||
import { ListRootItem } from './file-list/FileList';
|
||||
|
||||
let open = false;
|
||||
let exportOptions: Record<string, boolean> = {
|
||||
time: true,
|
||||
surface: true,
|
||||
hr: true,
|
||||
cad: true,
|
||||
atemp: true,
|
||||
power: true
|
||||
};
|
||||
let hide: Record<string, boolean> = {
|
||||
time: false,
|
||||
surface: false,
|
||||
hr: false,
|
||||
cad: false,
|
||||
atemp: false,
|
||||
power: false
|
||||
};
|
||||
|
||||
$: if ($exportState !== ExportState.NONE) {
|
||||
open = true;
|
||||
$currentTool = null;
|
||||
|
||||
let statistics = $gpxStatistics;
|
||||
if ($exportState === ExportState.ALL) {
|
||||
statistics = Array.from($fileObservers.values())
|
||||
.map((file) => get(file)?.statistics)
|
||||
.reduce((acc, cur) => {
|
||||
if (cur !== undefined) {
|
||||
acc.mergeWith(cur.getStatisticsFor(new ListRootItem()));
|
||||
}
|
||||
return acc;
|
||||
}, new GPXStatistics());
|
||||
}
|
||||
|
||||
hide.time = statistics.global.time.total === 0;
|
||||
hide.hr = statistics.global.hr.count === 0;
|
||||
hide.cad = statistics.global.cad.count === 0;
|
||||
hide.atemp = statistics.global.atemp.count === 0;
|
||||
hide.power = statistics.global.power.count === 0;
|
||||
}
|
||||
|
||||
$: exclude = Object.keys(exportOptions).filter((key) => !exportOptions[key]);
|
||||
</script>
|
||||
|
||||
<Dialog.Root
|
||||
@@ -32,9 +83,11 @@
|
||||
<Dialog.Trigger class="hidden" />
|
||||
<Dialog.Portal>
|
||||
<Dialog.Content
|
||||
class="fixed left-[50%] top-[50%] z-50 w-fit max-w-full translate-x-[-50%] translate-y-[-50%] flex flex-col items-center gap-2 border bg-background p-3 shadow-lg rounded-md"
|
||||
class="fixed left-[50%] top-[50%] z-50 w-fit max-w-full translate-x-[-50%] translate-y-[-50%] flex flex-col items-center gap-3 border bg-background p-3 shadow-lg rounded-md"
|
||||
>
|
||||
<div class="flex flex-row items-center gap-4 border rounded-md p-2">
|
||||
<div
|
||||
class="w-full flex flex-row items-center justify-center gap-4 border rounded-md p-2 bg-accent"
|
||||
>
|
||||
<span>⚠️</span>
|
||||
<span class="max-w-96 text-sm">
|
||||
{$_('menu.support_message')}
|
||||
@@ -50,9 +103,9 @@
|
||||
class="grow"
|
||||
on:click={() => {
|
||||
if ($exportState === ExportState.SELECTION) {
|
||||
exportSelectedFiles();
|
||||
exportSelectedFiles(exclude);
|
||||
} else if ($exportState === ExportState.ALL) {
|
||||
exportAllFiles();
|
||||
exportAllFiles(exclude);
|
||||
}
|
||||
open = false;
|
||||
$exportState = ExportState.NONE;
|
||||
@@ -66,6 +119,63 @@
|
||||
{/if}
|
||||
</Button>
|
||||
</div>
|
||||
<div class="w-full max-w-xl flex flex-col items-center gap-2">
|
||||
<div class="w-full flex flex-row items-center gap-3">
|
||||
<div class="grow">
|
||||
<Separator />
|
||||
</div>
|
||||
<Label class="shrink-0">
|
||||
{$_('menu.export_options')}
|
||||
</Label>
|
||||
<div class="grow">
|
||||
<Separator />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-row flex-wrap justify-center gap-x-6 gap-y-2">
|
||||
<div class="flex flex-row items-center gap-1.5 {hide.time ? 'hidden' : ''}">
|
||||
<Checkbox id="export-time" bind:checked={exportOptions.time} />
|
||||
<Label for="export-time" class="flex flex-row items-center gap-1">
|
||||
<Zap size="16" />
|
||||
{$_('quantities.time')}
|
||||
</Label>
|
||||
</div>
|
||||
<div class="flex flex-row items-center gap-1.5">
|
||||
<Checkbox id="export-surface" bind:checked={exportOptions.surface} />
|
||||
<Label for="export-surface" class="flex flex-row items-center gap-1">
|
||||
<BrickWall size="16" />
|
||||
{$_('quantities.surface')}
|
||||
</Label>
|
||||
</div>
|
||||
<div class="flex flex-row items-center gap-1.5 {hide.hr ? 'hidden' : ''}">
|
||||
<Checkbox id="export-heartrate" bind:checked={exportOptions.hr} />
|
||||
<Label for="export-heartrate" class="flex flex-row items-center gap-1">
|
||||
<HeartPulse size="16" />
|
||||
{$_('quantities.heartrate')}
|
||||
</Label>
|
||||
</div>
|
||||
<div class="flex flex-row items-center gap-1.5 {hide.cad ? 'hidden' : ''}">
|
||||
<Checkbox id="export-cadence" bind:checked={exportOptions.cad} />
|
||||
<Label for="export-cadence" class="flex flex-row items-center gap-1">
|
||||
<Orbit size="16" />
|
||||
{$_('quantities.cadence')}
|
||||
</Label>
|
||||
</div>
|
||||
<div class="flex flex-row items-center gap-1.5 {hide.atemp ? 'hidden' : ''}">
|
||||
<Checkbox id="export-temperature" bind:checked={exportOptions.atemp} />
|
||||
<Label for="export-temperature" class="flex flex-row items-center gap-1">
|
||||
<Thermometer size="16" />
|
||||
{$_('quantities.temperature')}
|
||||
</Label>
|
||||
</div>
|
||||
<div class="flex flex-row items-center gap-1.5 {hide.power ? 'hidden' : ''}">
|
||||
<Checkbox id="export-power" bind:checked={exportOptions.power} />
|
||||
<Label for="export-power" class="flex flex-row items-center gap-1">
|
||||
<SquareActivity size="16" />
|
||||
{$_('quantities.power')}
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog.Content>
|
||||
</Dialog.Portal>
|
||||
</Dialog.Root>
|
||||
|
@@ -320,30 +320,30 @@ export function updateSelectionFromKey(down: boolean, shift: boolean) {
|
||||
}
|
||||
}
|
||||
|
||||
async function exportFiles(fileIds: string[]) {
|
||||
async function exportFiles(fileIds: string[], exclude: string[]) {
|
||||
for (let fileId of fileIds) {
|
||||
let file = getFile(fileId);
|
||||
if (file) {
|
||||
exportFile(file);
|
||||
exportFile(file, exclude);
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function exportSelectedFiles() {
|
||||
export function exportSelectedFiles(exclude: string[]) {
|
||||
let fileIds: string[] = [];
|
||||
applyToOrderedSelectedItemsFromFile(async (fileId, level, items) => {
|
||||
fileIds.push(fileId);
|
||||
});
|
||||
exportFiles(fileIds);
|
||||
exportFiles(fileIds, exclude);
|
||||
}
|
||||
|
||||
export function exportAllFiles() {
|
||||
exportFiles(get(fileOrder));
|
||||
export function exportAllFiles(exclude: string[]) {
|
||||
exportFiles(get(fileOrder), exclude);
|
||||
}
|
||||
|
||||
export function exportFile(file: GPXFile) {
|
||||
let blob = new Blob([buildGPX(file)], { type: 'application/gpx+xml' });
|
||||
export function exportFile(file: GPXFile, exclude: string[]) {
|
||||
let blob = new Blob([buildGPX(file, exclude)], { type: 'application/gpx+xml' });
|
||||
let url = URL.createObjectURL(blob);
|
||||
let a = document.createElement('a');
|
||||
a.href = url;
|
||||
|
@@ -20,6 +20,7 @@
|
||||
"cut": "Cut",
|
||||
"export": "Export...",
|
||||
"export_all": "Export all...",
|
||||
"export_options": "Export options",
|
||||
"support_message": "The tool is free to use, but not free to run. Please consider supporting the website if you use it frequently. Thank you!",
|
||||
"support_button": "Help keep the website free",
|
||||
"download_file": "Download file",
|
||||
|
Reference in New Issue
Block a user