Files
TREK/client/src/components/Tours/TourGpxExportButton.tsx
T
Maurice 73237ffedb feat(tours): bike tours in the planner, CyclOSM, and the planned cards' last points (BIK-01 to BIK-06, RTE-01, IO-01, LIB-02)
The tour type is a dropdown; a bike tour picks its profile, routes for it,
says when a point lies off every way, and shows what it runs on: surfaces,
kinds of way and, for a mountain bike, the trail grades, each pointed at on
the map. A type's own map (OpenHikingMap for a hike, CyclOSM for a bike tour)
is offered while a tour of that type is open, with an admin switch each.

From the planned cards: a hike avoids steep climbs, steps or ferries, nothing
else; a saved tour can be shared where the platform takes GPX files; and the
tours rail adds a tour from one menu: plan new, import GPX, or take one over
from another trip.
2026-10-19 21:33:49 +02:00

79 lines
2.9 KiB
TypeScript

import { Download, Loader2, Share } from 'lucide-react';
import { useTranslation } from '../../i18n';
import { BarButton } from '../Planner/planParts';
import { DialogButton } from '../shared/DialogShell';
import { Tooltip } from '../shared/Tooltip';
import { useTourGpxExport, type TourGpxExportTarget } from './useTourGpxExport';
/**
* PR1-06A, IO-01 in a footer: the saved tour as a GPX file, its own action
* beside the navigation, which only leads to the start. "GPX" says the format,
* the tooltip what the file holds. Where the platform takes GPX files, a share
* button beside it hands the same file to the system's share sheet.
*/
export function TourGpxExportButton({ target }: { target: TourGpxExportTarget }) {
const { t } = useTranslation();
const { exportGpx, shareGpx, canShare, exporting } = useTourGpxExport();
return (
<>
<Tooltip label={t('tours.gpxExport.hint')}>
<span className="inline-flex">
<DialogButton
onClick={() => void exportGpx(target)}
disabled={exporting}
aria-label={t('tours.gpxExport.action')}
data-testid="tour-gpx-export"
icon={exporting ? <Loader2 size={14} className="animate-spin" /> : <Download size={14} strokeWidth={2} />}
>
<span className="max-sm:hidden">GPX</span>
</DialogButton>
</span>
</Tooltip>
{canShare && (
<Tooltip label={t('tours.gpxExport.share')}>
<span className="inline-flex">
<DialogButton
onClick={() => void shareGpx(target)}
disabled={exporting}
aria-label={t('tours.gpxExport.share')}
data-testid="tour-gpx-share"
icon={<Share size={14} strokeWidth={2} />}
>
<span className="max-sm:hidden">{t('tours.gpxExport.shareShort')}</span>
</DialogButton>
</span>
</Tooltip>
)}
</>
);
}
/**
* The same export in the planner's head. While the tour has unsaved changes
* the file would not hold them, so the buttons wait for the save and say so.
*/
export function TourGpxExportBarButton({ target, unsaved }: { target: TourGpxExportTarget; unsaved: boolean }) {
const { t } = useTranslation();
const { exportGpx, shareGpx, canShare, exporting } = useTourGpxExport();
return (
<>
<BarButton
label={t(unsaved ? 'tours.gpxExport.saveFirst' : 'tours.gpxExport.action')}
onClick={() => void exportGpx(target)}
disabled={unsaved || exporting}
>
{exporting ? <Loader2 size={15} className="animate-spin" /> : <Download size={15} strokeWidth={2} />}
</BarButton>
{canShare && (
<BarButton
label={t(unsaved ? 'tours.gpxExport.shareSaveFirst' : 'tours.gpxExport.share')}
onClick={() => void shareGpx(target)}
disabled={unsaved || exporting}
>
<Share size={15} strokeWidth={2} />
</BarButton>
)}
</>
);
}