Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

debug client: support stop ids in from / to inputs #6133

Open
wants to merge 2 commits into
base: dev-2.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions client/src/components/MapView/NavigationMarkers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { TripQueryVariables } from '../../gql/graphql.ts';
import { Marker } from 'react-map-gl';
import markerFlagStart from '../../static/img/marker-flag-start-shadowed.png';
import markerFlagEnd from '../../static/img/marker-flag-end-shadowed.png';
import { useCoordinateResolver } from './useCoordinateResolver.ts';

export function NavigationMarkers({
setCursor,
Expand All @@ -14,13 +15,16 @@ export function NavigationMarkers({
setTripQueryVariables: (variables: TripQueryVariables) => void;
loading: boolean;
}) {
const fromCoordinates = useCoordinateResolver(tripQueryVariables.from);
const toCoordinates = useCoordinateResolver(tripQueryVariables.to);

return (
<>
{tripQueryVariables.from.coordinates && (
{fromCoordinates && (
<Marker
draggable
latitude={tripQueryVariables.from.coordinates?.latitude}
longitude={tripQueryVariables.from.coordinates?.longitude}
latitude={fromCoordinates.latitude}
longitude={fromCoordinates.longitude}
onDragStart={() => setCursor('grabbing')}
onDragEnd={(e) => {
setCursor('auto');
Expand All @@ -36,11 +40,11 @@ export function NavigationMarkers({
<img alt="" src={markerFlagStart} height={48} width={49} />
</Marker>
)}
{tripQueryVariables.to.coordinates && (
{toCoordinates && (
<Marker
draggable
latitude={tripQueryVariables.to.coordinates?.latitude}
longitude={tripQueryVariables.to.coordinates?.longitude}
latitude={toCoordinates.latitude}
longitude={toCoordinates.longitude}
onDragStart={() => setCursor('grabbing')}
onDragEnd={(e) => {
setCursor('auto');
Expand Down
30 changes: 30 additions & 0 deletions client/src/components/MapView/useCoordinateResolver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Location } from '../../gql/graphql.ts';
import { useQuayCoordinateQuery } from '../../hooks/useQuayCoordinateQuery.ts';

interface Coordinates {
latitude: number;
longitude: number;
}

export function useCoordinateResolver(location: Location): Coordinates | undefined {
const quay = useQuayCoordinateQuery(location);

if (quay) {
const { longitude, latitude } = quay;

if (longitude && latitude) {
return {
longitude,
latitude,
Comment on lines +17 to +18
Copy link
Member

@t2gran t2gran Oct 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My knowledge of JS is limited - but it looks like the arguments order is swapped according to the interface definition above. As a practice to avoid errors I like to always do lat, long never long, lat (doc, params, args, execution order and so on).

};
}
}

if (location.coordinates) {
return {
...location.coordinates,
};
}

return undefined;
}
50 changes: 39 additions & 11 deletions client/src/components/SearchBar/LocationInputField.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,37 @@
import { Form } from 'react-bootstrap';
import { COORDINATE_PRECISION } from './constants.ts';
import { Location } from '../../gql/graphql.ts';
import { toString, parseLocation } from '../../util/locationConverter.ts';
import { Location, TripQueryVariables } from '../../gql/graphql.ts';
import { useCallback, useEffect, useState } from 'react';

interface Props {
id: string;
label: string;
tripQueryVariables: TripQueryVariables;
setTripQueryVariables: (tripQueryVariables: TripQueryVariables) => void;
locationFieldKey: 'from' | 'to';
}

export function LocationInputField({ id, label, tripQueryVariables, setTripQueryVariables, locationFieldKey }: Props) {
const [value, setValue] = useState('');

useEffect(() => {
const initialLocation: Location = tripQueryVariables[locationFieldKey];

setValue(toString(initialLocation) || '');
}, [tripQueryVariables, locationFieldKey]);

const onLocationChange = useCallback(
(value: string) => {
const newLocation = parseLocation(value) || {};

setTripQueryVariables({
...tripQueryVariables,
[locationFieldKey]: newLocation,
});
},
[tripQueryVariables, setTripQueryVariables, locationFieldKey],
);

export function LocationInputField({ location, id, label }: { location: Location; id: string; label: string }) {
return (
<Form.Group>
<Form.Label column="sm" htmlFor={id}>
Expand All @@ -16,14 +45,13 @@ export function LocationInputField({ location, id, label }: { location: Location
className="input-medium"
// Intentionally empty for now, but needed because of
// https://react.dev/reference/react-dom/components/input#controlling-an-input-with-a-state-variable
onChange={() => {}}
value={
location.coordinates
? `${location.coordinates?.latitude.toPrecision(
COORDINATE_PRECISION,
)} ${location.coordinates?.longitude.toPrecision(COORDINATE_PRECISION)}`
: ''
}
onChange={(e) => {
setValue(e.target.value);
}}
onBlur={(event) => {
onLocationChange(event.target.value);
}}
value={value}
/>
</Form.Group>
);
Expand Down
16 changes: 14 additions & 2 deletions client/src/components/SearchBar/SearchBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,21 @@ export function SearchBar({ onRoute, tripQueryVariables, setTripQueryVariables,
{showServerInfo && <ServerInfoTooltip serverInfo={serverInfo} target={target} />}
</div>
</Navbar.Brand>
<LocationInputField location={tripQueryVariables.from} label="From" id="fromInputField" />
<LocationInputField
label="From"
id="fromInputField"
locationFieldKey="from"
tripQueryVariables={tripQueryVariables}
setTripQueryVariables={setTripQueryVariables}
/>
<SwapLocationsButton tripQueryVariables={tripQueryVariables} setTripQueryVariables={setTripQueryVariables} />
<LocationInputField location={tripQueryVariables.to} label="To" id="toInputField" />
<LocationInputField
label="To"
id="toInputField"
locationFieldKey="to"
tripQueryVariables={tripQueryVariables}
setTripQueryVariables={setTripQueryVariables}
/>
<DepartureArrivalSelect tripQueryVariables={tripQueryVariables} setTripQueryVariables={setTripQueryVariables} />
<DateTimeInputField tripQueryVariables={tripQueryVariables} setTripQueryVariables={setTripQueryVariables} />
<NumTripPatternsInput tripQueryVariables={tripQueryVariables} setTripQueryVariables={setTripQueryVariables} />
Expand Down
32 changes: 32 additions & 0 deletions client/src/hooks/useQuayCoordinateQuery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { useEffect, useState } from 'react';
import { request } from 'graphql-request'; // eslint-disable-line import/no-unresolved
import { Location, QueryType } from '../gql/graphql.ts';
import { getApiUrl } from '../util/getApiUrl.ts';
import { graphql } from '../gql';

const query = graphql(`
query quayCoordinate($id: String!) {
quay(id: $id) {
longitude
latitude
}
}
`);

export const useQuayCoordinateQuery = (location: Location) => {
const [data, setData] = useState<QueryType | null>(null);

useEffect(() => {
const fetchData = async () => {
if (location.place) {
const variables = { id: location.place };
setData((await request(getApiUrl(), query, variables)) as QueryType);
} else {
setData(null);
}
};
fetchData();
}, [location]);

return data?.quay;
};
8 changes: 6 additions & 2 deletions client/src/hooks/useTripQuery.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useCallback, useEffect, useState } from 'react';
import { request } from 'graphql-request'; // eslint-disable-line import/no-unresolved
import { QueryType, TripQueryVariables } from '../gql/graphql.ts';
import { Location, QueryType, TripQueryVariables } from '../gql/graphql.ts';
import { getApiUrl } from '../util/getApiUrl.ts';
import { query } from '../static/query/tripQuery.tsx';

Expand Down Expand Up @@ -37,10 +37,14 @@ export const useTripQuery: TripQueryHook = (variables) => {
);

useEffect(() => {
if (variables?.from.coordinates && variables?.to.coordinates) {
if (validLocation(variables?.from) && validLocation(variables?.to)) {
callback();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [variables?.from, variables?.to]);
return [data, loading, callback];
};

function validLocation(location: Location | undefined) {
return location && (location.coordinates || location.place);
}
47 changes: 47 additions & 0 deletions client/src/util/locationConverter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { COORDINATE_PRECISION } from '../components/SearchBar/constants.ts';
import { Location } from '../gql/graphql.ts';

const DOUBLE_PATTERN = '-{0,1}\\d+(\\.\\d+){0,1}';

const LAT_LON_PATTERN = '(' + DOUBLE_PATTERN + ')(\\s*,\\s*|\\s+)(' + DOUBLE_PATTERN + ')';

const ID_SEPARATOR = ':';

export function parseLocation(value: string): Location | null {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand what this function does. In particular, there seems to be something going on with "name" which seems out of scope.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These function is the typescript version of the LocationStringParser.java class.

I removed the "name" parsing part.

const latLonMatch = value.match(LAT_LON_PATTERN);

if (latLonMatch) {
return {
coordinates: {
latitude: +latLonMatch[1],
longitude: +latLonMatch[4],
},
};
}

if (validFeedScopeIdString(value)) {
return {
place: value,
};
}

return null;
}

function validFeedScopeIdString(value: string): boolean {
return value.indexOf(ID_SEPARATOR) > -1;
}

export function toString(location: Location): string | null {
if (location.coordinates) {
return `${location.coordinates?.latitude.toPrecision(
COORDINATE_PRECISION,
)} ${location.coordinates?.longitude.toPrecision(COORDINATE_PRECISION)}`;
}

if (location.place) {
return location.place;
}

return null;
}
Loading