mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-20 22:16:26 +00:00
fix(tours): import the walk icon, keep one stop menu open and count only stage ends a change lost
The activity section used Footprints without importing it. Opening one stop's menu now closes any other. lostStageEnds no longer counts a stop the change kept without its mark as lost twice over. The wanderer watch switch keeps the same tree once the connection is known, so it is not remounted and keeps its focus.
This commit is contained in:
@@ -12,24 +12,25 @@ import { useWandererWatch, type WandererWatchController } from './useWandererWat
|
||||
/** The switch, dimmed with a word on why while there is no connection to watch with. */
|
||||
function WatchSwitch({ W }: { W: WandererWatchController }) {
|
||||
const { t } = useTranslation();
|
||||
const toggle = (
|
||||
<ToggleSwitch
|
||||
on={W.enabled}
|
||||
onToggle={() => void W.toggle()}
|
||||
label={t('wanderer.watch.label')}
|
||||
disabled={!W.available}
|
||||
/>
|
||||
);
|
||||
if (W.available) return toggle;
|
||||
const off = !W.available;
|
||||
// A disabled button takes no focus and, in some browsers, no hover either, so
|
||||
// the reason sits on a focusable wrapper the tooltip listens to.
|
||||
// the reason sits on a focusable wrapper the tooltip listens to. The wrapper
|
||||
// stays once the switch can be used, so the button is never mounted anew and
|
||||
// keeps its focus when the connection is known.
|
||||
return (
|
||||
<Tooltip label={t('wanderer.watch.notConnected')}>
|
||||
<Tooltip label={t('wanderer.watch.notConnected')} disabled={!off}>
|
||||
<span
|
||||
tabIndex={0}
|
||||
className="inline-flex cursor-not-allowed rounded-full opacity-50 outline-none focus-visible:ring-2 focus-visible:ring-[color:var(--text-primary)] focus-visible:ring-offset-2 focus-visible:ring-offset-[color:var(--bg-card)]"
|
||||
tabIndex={off ? 0 : undefined}
|
||||
className={`inline-flex rounded-full outline-none ${off ? 'cursor-not-allowed opacity-50 focus-visible:ring-2 focus-visible:ring-[color:var(--text-primary)] focus-visible:ring-offset-2 focus-visible:ring-offset-[color:var(--bg-card)]' : ''}`}
|
||||
>
|
||||
<span className="pointer-events-none inline-flex">{toggle}</span>
|
||||
<span className={`inline-flex ${off ? 'pointer-events-none' : ''}`}>
|
||||
<ToggleSwitch
|
||||
on={W.enabled}
|
||||
onToggle={() => void W.toggle()}
|
||||
label={t('wanderer.watch.label')}
|
||||
disabled={off}
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { TourActivity, TourListItem } from '@trek/shared';
|
||||
import { CalendarDays, CheckCheck, Clock, CloudOff, FileDown, Plus, RefreshCw, Unlink } from 'lucide-react';
|
||||
import { CalendarDays, CheckCheck, Clock, CloudOff, FileDown, Footprints, Plus, RefreshCw, Unlink } from 'lucide-react';
|
||||
import { useMemo } from 'react';
|
||||
import { useTranslation } from '../../../i18n';
|
||||
import { useSettingsStore } from '../../../store/settingsStore';
|
||||
|
||||
@@ -39,7 +39,8 @@ import { PaddleRolePill } from '../paddle/PaddleWaypointParts';
|
||||
import { usePaddleRegionLoad } from '../paddle/usePaddleRegionLoad';
|
||||
import { usePaddleWaypointActions } from '../paddle/usePaddleWaypointActions';
|
||||
import { usePortageRouting } from '../paddle/usePortageRouting';
|
||||
import { StageDivider, StageEndCorner, stageMenuItems } from '../stages/StageParts';
|
||||
import { stageMenuItems } from '../stages/stageActions';
|
||||
import { StageDivider, StageEndCorner } from '../stages/StageParts';
|
||||
import { RailCard } from '../tourParts';
|
||||
import { StationScheduleRow } from '../walk/StationSchedule';
|
||||
import { usePlannerWalkSchedule } from '../walk/useWalkSchedule';
|
||||
@@ -465,6 +466,8 @@ function WaypointRowActions({
|
||||
isSaving,
|
||||
onUnlink,
|
||||
stationName,
|
||||
menuOwner,
|
||||
onMenuOpen,
|
||||
}: {
|
||||
planner: TourPlannerController;
|
||||
point: WaypointPoint;
|
||||
@@ -476,9 +479,13 @@ function WaypointRowActions({
|
||||
onUnlink?: () => void;
|
||||
/** CW-01: the name the row shows, for a station without one of its own. */
|
||||
stationName?: string;
|
||||
/** The row whose menu is open; opening one row's menu closes the menu of any other. */
|
||||
menuOwner: string | null;
|
||||
onMenuOpen: (id: string) => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const menu = useContextMenu();
|
||||
const menuOpen = !!menu.menu && menuOwner === point.id;
|
||||
const paddle = usePaddleWaypointActions(planner);
|
||||
const [stationOpen, setStationOpen] = useState(false);
|
||||
const station = planner.tourType === 'walk' && !point.shaping && !(planner.isLoop && index === lastIndex);
|
||||
@@ -511,14 +518,21 @@ function WaypointRowActions({
|
||||
];
|
||||
return (
|
||||
<span
|
||||
className={`flex-none items-center gap-0.5 group-focus-within:flex group-hover:flex ${selected || menu.menu ? 'flex' : 'hidden'}`}
|
||||
className={`flex-none items-center gap-0.5 group-focus-within:flex group-hover:flex ${selected || menuOpen ? 'flex' : 'hidden'}`}
|
||||
>
|
||||
<WaypointAction
|
||||
label={t('tours.planner.moreActions')}
|
||||
disabled={isSaving}
|
||||
active={!!menu.menu}
|
||||
active={menuOpen}
|
||||
onAct={() => undefined}
|
||||
onPress={(event) => (menu.menu ? menu.close() : menu.open(event, items, true))}
|
||||
onPress={(event) => {
|
||||
if (menuOpen) {
|
||||
menu.close();
|
||||
return;
|
||||
}
|
||||
onMenuOpen(point.id);
|
||||
menu.open(event, items, true);
|
||||
}}
|
||||
>
|
||||
<MoreHorizontal size={14} strokeWidth={2.2} />
|
||||
</WaypointAction>
|
||||
@@ -530,7 +544,7 @@ function WaypointRowActions({
|
||||
>
|
||||
<Trash2 size={13} strokeWidth={2} />
|
||||
</WaypointAction>
|
||||
<ContextMenu menu={menu.menu} onClose={menu.close} />
|
||||
<ContextMenu menu={menuOpen ? menu.menu : null} onClose={menu.close} />
|
||||
{stationOpen && (
|
||||
<StationDialog
|
||||
point={point}
|
||||
@@ -683,6 +697,7 @@ export default function TourWaypointList({
|
||||
// CW-02, CW-04: the arrivals and opening hours of a city walk on one of its days; hints only, nothing saved.
|
||||
const walk = usePlannerWalkSchedule(planner, tripPlaces.linkedPlaceOf);
|
||||
const [placesOpen, setPlacesOpen] = useState(false);
|
||||
const [menuOwner, setMenuOwner] = useState<string | null>(null);
|
||||
const addRows = canEdit && (
|
||||
<>
|
||||
<AddPointRow disabled={isSaving} placesOpen={placesOpen} onTogglePlaces={() => setPlacesOpen((open) => !open)} />
|
||||
@@ -719,6 +734,10 @@ export default function TourWaypointList({
|
||||
const stage = stageStarts.get(index);
|
||||
return stage ? <StageDivider stage={stage} /> : null;
|
||||
};
|
||||
// A row's name: the trip place it stands for, else the name looked up for its spot.
|
||||
const placeNameOf = (point: WaypointPoint, linked: ReturnType<typeof tripPlaces.linkedPlaceOf>) =>
|
||||
linked?.name ?? placeNames[point.id];
|
||||
const ferrataOf = (point: WaypointPoint) => ferrataEnds[point.id] ?? null;
|
||||
const stageMarkOf = (stage: number | undefined) =>
|
||||
stage ? { stageNote: t('tours.stages.endOf', { number: stage }), corner: <StageEndCorner /> } : {};
|
||||
const endDrag = () => {
|
||||
@@ -768,7 +787,7 @@ export default function TourWaypointList({
|
||||
const isDropTarget = dropTarget?.id === point.id && draggedWaypointId !== point.id;
|
||||
const loopEnd = planner.isLoop && index === lastIndex;
|
||||
const linkedPlace = tripPlaces.linkedPlaceOf(point);
|
||||
const placeName = linkedPlace?.name ?? placeNames[point.id];
|
||||
const placeName = placeNameOf(point, linkedPlace);
|
||||
const stop = stopNumbers.get(point.id) ?? index + 1;
|
||||
return (
|
||||
<Fragment key={point.id}>
|
||||
@@ -829,7 +848,7 @@ export default function TourWaypointList({
|
||||
<WaypointNumber
|
||||
stop={stop}
|
||||
selected={selected}
|
||||
ferrata={ferrataEnds[point.id] ?? null}
|
||||
ferrata={ferrataOf(point)}
|
||||
poiCategory={point.poiCategory ?? null}
|
||||
station={point}
|
||||
loopEnd={loopEnd}
|
||||
@@ -861,6 +880,8 @@ export default function TourWaypointList({
|
||||
isSaving={isSaving}
|
||||
onUnlink={point.linkedPlaceId ? () => tripPlaces.unlink(point.id) : undefined}
|
||||
stationName={placeName}
|
||||
menuOwner={menuOwner}
|
||||
onMenuOpen={setMenuOwner}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -94,9 +94,12 @@ export function lostStageEnds(
|
||||
): number {
|
||||
const kept = new Set(next.filter((point) => point.stageEnd).map((point) => point.id));
|
||||
const asChanged = new Map(changed.map((point) => [point.id, point]));
|
||||
return current.filter(
|
||||
(point) => point.stageEnd && !kept.has(point.id) && (asChanged.get(point.id)?.stageEnd ?? true)
|
||||
).length;
|
||||
return current.filter((point) => {
|
||||
if (!point.stageEnd || kept.has(point.id)) return false;
|
||||
const asChange = asChanged.get(point.id);
|
||||
// A point the change removed lost its mark; one the change kept lost it only if the change still had it.
|
||||
return !asChange || !!asChange.stageEnd;
|
||||
}).length;
|
||||
}
|
||||
|
||||
/** What a stage measures; null where the route does not tell (no heights on the water, a route not planned yet). */
|
||||
|
||||
Reference in New Issue
Block a user