hammerhead integration added (#628)

* hammerhead integration added

* typo corrected

* start date added, optimize some translations

* new option to send trail to hammerhead

* fix elevation information (was in cm)

* skip empty tracks on import

* update docs

* remove manual user-id input

* fix merge issues

* remove user-id from documentation

* fix hammerhead logo for light theme

* fix hammerhead logo for light theme

---------

Co-authored-by: Christian Beutel <>
Co-authored-by: Flomp <Flomp@users.noreply.github.com>
This commit is contained in:
slothful-vassal
2026-03-20 13:58:34 +01:00
committed by GitHub
parent 5a05573e83
commit 369e71fcec
20 changed files with 1839 additions and 51 deletions

View File

@@ -3,7 +3,11 @@
import { page } from "$app/state";
import type { List } from "$lib/models/list";
import type { Trail } from "$lib/models/trail";
import { categories } from "$lib/stores/category_store.js";
import {
integrations,
integrations_index,
uploadGpx,
} from "$lib/stores/integration_store";
import {
lists_add_trail,
lists_index,
@@ -17,12 +21,14 @@
import { trail2gpx } from "$lib/util/gpx_util";
import { gpx } from "$lib/vendor/toGeoJSON/toGeoJSON";
import JSZip from "jszip";
import type { Snippet } from "svelte";
import { onMount, type Snippet } from "svelte";
import { _ } from "svelte-i18n";
import { get } from "svelte/store";
import Dropdown, { type DropdownItem } from "../base/dropdown.svelte";
import ConfirmModal from "../confirm_modal.svelte";
import ListSearchModal from "../list/list_search_modal.svelte";
import TrailExportModal from "./trail_export_modal.svelte";
import TrailSendModal from "./trail_send_modal.svelte";
import TrailShareModal from "./trail_share_modal.svelte";
interface Props {
@@ -40,8 +46,56 @@
let listSelectModal: ListSearchModal;
let trailExportModal: TrailExportModal;
let trailShareModal: TrailShareModal;
let trailSendModal: TrailSendModal;
const hammerheadIntegration = $derived(
$integrations.find((integration) =>
Boolean(integration.hammerhead?.active)
)
);
let lists: List[] = $state([]);
let integrationsLoading = false;
let integrationsLoadedForUser: string | undefined;
onMount(() => {
const unsubscribe = currentUser.subscribe(async (user) => {
if (!user) {
integrationsLoadedForUser = undefined;
return;
}
const existing = get(integrations);
if (
existing.length &&
existing[0]?.user === user.id
) {
integrationsLoadedForUser = user.id;
return;
}
if (
integrationsLoading ||
integrationsLoadedForUser === user.id
) {
return;
}
integrationsLoading = true;
try {
await integrations_index();
integrationsLoadedForUser = user.id;
} catch (error) {
console.error("Failed to load integrations", error);
} finally {
integrationsLoading = false;
}
});
return () => {
unsubscribe();
};
});
let loading: boolean = $state(false);
@@ -212,6 +266,15 @@
},
]
: []),
...(!isMultiselectMode() && hammerheadIntegration && canExport()
? [
{
text: $_("send-to"),
value: "send-to",
icon: "upload",
},
]
: []),
];
}
@@ -336,6 +399,51 @@
updateTrailsVisibility();
} else if (ddVal == "delete") {
confirmModal.openModal();
} else if (item.value == "send-to") {
trailSendModal.openModal();
}
}
async function uploadToHammerhead() {
if (!hammerheadIntegration || !hasTrail()) {
console.error("No Hammerhead integration found.");
return;
}
for (const uTrail of trails!) {
try {
if (uTrail.gpx) {
const gpxData = await trail2gpx(uTrail, $currentUser);
const formData = new FormData();
const gpxFile = new File(
[gpxData],
`${uTrail.name || "trail"}.gpx`,
{ type: "application/gpx+xml" },
);
formData.append("file", gpxFile);
await uploadGpx("hammerhead", gpxFile);
show_toast({
type: "success",
icon: "check",
text: $_("uploaded-trail-to-hammerhead"),
});
} else {
show_toast({
type: "error",
icon: "close",
text: $_("trail-has-no-gpx"),
});
}
} catch (e) {
console.error(e);
show_toast({
type: "error",
icon: "close",
text: $_("error-uploading-trail-to-hammerhead"),
});
}
}
}
@@ -617,3 +725,11 @@
onsave={handleShareUpdate}
bind:this={trailShareModal}
></TrailShareModal>
<TrailSendModal
bind:this={trailSendModal}
onsend={async (settings) => {
if (settings.integrationName === "hammerhead") {
await uploadToHammerhead();
}
}}
></TrailSendModal>