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

UI: Advanced options etc. #730

Merged
merged 4 commits into from
Feb 9, 2025
Merged
Show file tree
Hide file tree
Changes from 3 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
649 changes: 317 additions & 332 deletions ui/package-lock.json

Large diffs are not rendered by default.

64 changes: 32 additions & 32 deletions ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,48 +15,48 @@
"test:unit": "vitest"
},
"devDependencies": {
"@hey-api/openapi-ts": "^0.53.11",
"@playwright/test": "^1.28.1",
"@sveltejs/adapter-static": "^3.0.5",
"@sveltejs/kit": "^2.12.0",
"@sveltejs/vite-plugin-svelte": "^4.0.0-next.6",
"@types/eslint": "^8.56.0",
"@hey-api/openapi-ts": "^0.53.12",
"@playwright/test": "^1.50.1",
"@sveltejs/adapter-static": "^3.0.8",
"@sveltejs/kit": "^2.17.1",
"@sveltejs/vite-plugin-svelte": "^4.0.4",
"@types/eslint": "^8.56.12",
"@types/polyline": "^0.1.32",
"@typescript-eslint/eslint-plugin": "^7.0.0",
"@typescript-eslint/parser": "^7.0.0",
"autoprefixer": "^10.4.19",
"bits-ui": "^1.0.0-next.63",
"@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^7.18.0",
"autoprefixer": "^10.4.20",
"bits-ui": "^1.0.0-next.87",
"clsx": "^2.1.1",
"eslint": "^8.56.0",
"eslint": "^8.57.1",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-svelte": "^2.44.0",
"eslint-plugin-svelte": "^2.46.1",
"lucide-svelte": "^0.460.1",
"postcss": "^8.4.38",
"prettier": "^3.1.1",
"prettier-plugin-svelte": "^3.2.7",
"svelte": "^5.1.0",
"svelte-check": "^3.6.0",
"tailwind-merge": "^2.5.4",
"tailwind-variants": "^0.3.0",
"tailwindcss": "^3.4.3",
"postcss": "^8.5.1",
"prettier": "^3.4.2",
"prettier-plugin-svelte": "^3.3.3",
"svelte": "^5.19.9",
"svelte-check": "^3.8.6",
"tailwind-merge": "^2.6.0",
"tailwind-variants": "^0.3.1",
"tailwindcss": "^3.4.17",
"tailwindcss-animate": "^1.0.7",
"tslib": "^2.4.1",
"typescript": "^5.0.0",
"vite": "^5.0.3",
"vitest": "^1.2.0"
"tslib": "^2.8.1",
"typescript": "^5.7.3",
"vite": "^5.4.14",
"vitest": "^1.6.1"
},
"type": "module",
"dependencies": {
"@deck.gl/core": "^9.0.33",
"@deck.gl/layers": "^9.0.33",
"@deck.gl/mapbox": "^9.0.33",
"@hey-api/client-fetch": "^0.4.2",
"@turf/rhumb-bearing": "^7.1.0",
"@turf/rhumb-distance": "^7.1.0",
"@deck.gl/core": "^9.1.0",
"@deck.gl/layers": "^9.1.0",
"@deck.gl/mapbox": "^9.1.0",
"@hey-api/client-fetch": "^0.4.4",
"@turf/rhumb-bearing": "^7.2.0",
"@turf/rhumb-distance": "^7.2.0",
"colord": "^2.9.3",
"geojson": "^0.5.0",
"maplibre-gl": "^4.3.2",
"maplibre-gl": "^4.7.1",
"polyline": "^0.2.0",
"svelte-radix": "^1.1.0"
"svelte-radix": "^1.1.1"
}
}
4 changes: 4 additions & 0 deletions ui/src/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,7 @@
flex-direction: column;
z-index: 3;
}

.maplibregl-ctrl-group .maplibregl-ctrl-geolocate {
display: none;
}
15 changes: 0 additions & 15 deletions ui/src/lib/AddressTypeahead.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@
shown.add(entry);
return true;
});
preventClickthrough(!!items.length && comboOpen);
};

const deserialize = (s: string): Location => {
Expand Down Expand Up @@ -119,16 +118,6 @@
}, 150);
}
});

let comboOpen = false;
const preventClickthrough = (prevent: boolean) => {
const ctr = document.getElementById('searchmask-container')!;
if (prevent) {
ctr.style.pointerEvents = 'none';
} else {
window.setTimeout(() => (ctr.style.pointerEvents = 'auto'), 1);
}
};
</script>

<Combobox.Root
Expand All @@ -141,10 +130,6 @@
inputValue = selected.label!;
}
}}
onOpenChange={(open) => {
comboOpen = open;
preventClickthrough(open);
}}
>
<Combobox.Input
{placeholder}
Expand Down
120 changes: 120 additions & 0 deletions ui/src/lib/AdvancedOptions.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<script lang="ts">
import Button from '$lib/components/ui/button/button.svelte';
import { t } from '$lib/i18n/translation';
import { Select } from 'bits-ui';
import BusFront from 'lucide-svelte/icons/bus-front';
import ChevronsUpDown from 'lucide-svelte/icons/chevrons-up-down';
import ChevronUp from 'lucide-svelte/icons/chevron-up';
import ChevronsUp from 'lucide-svelte/icons/chevrons-up';
import ChevronDown from 'lucide-svelte/icons/chevron-down';
import ChevronsDown from 'lucide-svelte/icons/chevrons-down';
import Check from 'lucide-svelte/icons/check';
import { Switch } from './components/ui/switch';

let {
selectedModes = $bindable(),
wheelchair = $bindable(),
bikeRental = $bindable(),
bikeCarriage = $bindable()
}: {
selectedModes: string[];
wheelchair: boolean;
bikeRental: boolean;
bikeCarriage: boolean;
} = $props();

const possibleModes = [
'AIRPLANE',
'HIGHSPEED_RAIL',
'LONG_DISTANCE',
'NIGHT_RAIL',
'COACH',
'REGIONAL_FAST_RAIL',
'REGIONAL_RAIL',
'METRO',
'SUBWAY',
'TRAM',
'BUS',
'FERRY',
'OTHER'
];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const modes = possibleModes.map((m) => ({ value: m, label: (t as any)[m] }));

const selectedModeLabel = $derived(
selectedModes.length && selectedModes.length != possibleModes.length
? modes
.filter((m) => selectedModes.includes(m.value))
.map((m) => m.label)
.join(', ')
: t.defaultSelectedModes
);

let expanded = $state<boolean>(false);
</script>

<Button variant="ghost" onclick={() => (expanded = !expanded)}>
{t.advancedSearchOptions}
{#if expanded}
<ChevronUp class="size-[18px]" />
{:else}
<ChevronDown class="size-[18px]" />
{/if}
</Button>

{#if expanded}
<div class="w-full">
<Select.Root
type="multiple"
bind:value={selectedModes}
onOpenChange={(o: boolean) => {
if (o && !selectedModes.length) selectedModes = [...possibleModes];
}}
>
<Select.Trigger
class="flex items-center h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
aria-label={t.selectModes}
>
<BusFront class="mr-[9px] size-6 text-muted-foreground shrink-0" />
<div class="grow text-ellipsis overflow-hidden text-nowrap">{selectedModeLabel}</div>
<ChevronsUpDown class="ml-auto size-6 text-muted-foreground shrink-0" />
</Select.Trigger>
<Select.Portal>
<Select.Content
class="z-10 max-h-96 w-[var(--bits-select-anchor-width)] min-w-[var(--bits-select-anchor-width)] rounded-xl border border-muted bg-background px-1 py-3 shadow-popover outline-none"
sideOffset={10}
>
<Select.ScrollUpButton class="flex w-full items-center justify-center">
<ChevronsUp class="size-3" />
</Select.ScrollUpButton>
<Select.Viewport class="p-1">
{#each modes as mode, i (i + mode.value)}
<Select.Item
class="flex h-10 w-full select-none items-center rounded-button py-3 pl-5 pr-1.5 text-sm outline-none duration-75 data-[highlighted]:bg-muted"
value={mode.value}
label={mode.label}
>
{#snippet children({ selected })}
{mode.label}
{#if selected}
<div class="ml-auto">
<Check />
</div>
{/if}
{/snippet}
</Select.Item>
{/each}
</Select.Viewport>
<Select.ScrollDownButton class="flex w-full items-center justify-center">
<ChevronsDown class="size-3" />
</Select.ScrollDownButton>
</Select.Content>
</Select.Portal>
</Select.Root>

<Switch bind:checked={wheelchair} label={t.wheelchair} id="wheelchair" />
<Switch bind:checked={bikeRental} label={t.bikeRental} id="bikeRental" />
<Switch bind:checked={bikeCarriage} label={t.bikeCarriage} id="bikeCarriage" />
<div class="text-muted-foreground leading-tight">{t.unreliableOptions}</div>
</div>
{/if}
16 changes: 16 additions & 0 deletions ui/src/lib/RailViz.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import { Button } from '$lib/components/ui/button';
import Palette from 'lucide-svelte/icons/palette';
import Rss from 'lucide-svelte/icons/rss';
import LocateFixed from 'lucide-svelte/icons/locate-fixed';
import { browser } from '$app/environment';

let {
Expand Down Expand Up @@ -248,6 +249,17 @@
}, 60000);
};

const geolocate = new maplibregl.GeolocateControl({
positionOptions: {
enableHighAccuracy: true
},
showAccuracyCircle: false
});

const getLocation = () => {
geolocate.trigger();
};

$effect(() => {
if (map && !overlay) {
overlay = new MapboxOverlay({
Expand All @@ -270,6 +282,7 @@
onClickTrip(object.trips[0].tripId);
}
});
map.addControl(geolocate);
map.addControl(overlay);

console.log('updateRailviz: init');
Expand Down Expand Up @@ -311,6 +324,9 @@
<Palette class="h-[1.2rem] w-[1.2rem]" />
{/if}
</Button>
<Button size="icon" onclick={() => getLocation()}>
<LocateFixed class="w-5 h-5" />
</Button>
</Control>

{#if railvizError}
Expand Down
53 changes: 37 additions & 16 deletions ui/src/lib/SearchMask.svelte
Original file line number Diff line number Diff line change
@@ -1,41 +1,64 @@
<script lang="ts">
import ArrowUpDown from 'lucide-svelte/icons/arrow-up-down';
import Accessibility from 'lucide-svelte/icons/accessibility';
import Bike from 'lucide-svelte/icons/bike';
import LocateFixed from 'lucide-svelte/icons/locate-fixed';
import AddressTypeahead from '$lib/AddressTypeahead.svelte';
import Button from '$lib/components/ui/button/button.svelte';
import { Label } from '$lib/components/ui/label';
import * as RadioGroup from '$lib/components/ui/radio-group';
import DateInput from '$lib/DateInput.svelte';
import { type Location } from '$lib/Location';
import { Toggle } from '$lib/components/ui/toggle';
import { posToLocation, type Location } from '$lib/Location';
import { t } from '$lib/i18n/translation';
import AdvancedOptions from './AdvancedOptions.svelte';

let {
from = $bindable(),
to = $bindable(),
time = $bindable(),
timeType = $bindable(),
wheelchair = $bindable(),
bikeRental = $bindable()
bikeRental = $bindable(),
bikeCarriage = $bindable(),
selectedModes = $bindable()
}: {
from: Location;
to: Location;
time: Date;
timeType: string;
wheelchair: boolean;
bikeRental: boolean;
bikeCarriage: boolean;
selectedModes: string[];
} = $props();

let fromItems = $state<Array<Location>>([]);
let toItems = $state<Array<Location>>([]);

const getLocation = () => {
if (navigator && navigator.geolocation) {
navigator.geolocation.getCurrentPosition(applyPosition, (e) => console.log(e), {
enableHighAccuracy: true
});
}
};

const applyPosition = (position: { coords: { latitude: number; longitude: number } }) => {
from = posToLocation({ lat: position.coords.latitude, lon: position.coords.longitude }, 0);
};
</script>

<div id="searchmask-container" class="flex flex-col space-y-4 p-4 relative">
<AddressTypeahead name="from" placeholder={t.from} bind:selected={from} bind:items={fromItems} />
<AddressTypeahead name="to" placeholder={t.to} bind:selected={to} bind:items={toItems} />
<Button
class="absolute z-10 right-12 top-6"
variant="ghost"
class="absolute z-10 right-4 top-0"
size="icon"
onclick={() => getLocation()}
>
<LocateFixed class="w-5 h-5" />
</Button>
<Button
class="absolute z-10 right-14 top-6"
variant="outline"
size="icon"
onclick={() => {
Expand All @@ -57,24 +80,22 @@
for="departure"
class="flex items-center rounded-md border-2 border-muted bg-popover p-1 px-2 hover:bg-accent hover:text-accent-foreground [&:has([data-state=checked])]:border-blue-600 hover:cursor-pointer"
>
<RadioGroup.Item value="departure" id="departure" class="sr-only" aria-label="Abfahrt" />
<RadioGroup.Item
value="departure"
id="departure"
class="sr-only"
aria-label={t.departure}
/>
<span>{t.departure}</span>
</Label>
<Label
for="arrival"
class="flex items-center rounded-md border-2 border-muted bg-popover p-1 px-2 hover:bg-accent hover:text-accent-foreground [&:has([data-state=checked])]:border-blue-600 hover:cursor-pointer"
>
<RadioGroup.Item value="arrival" id="arrival" class="sr-only" aria-label="Ankunft" />
<RadioGroup.Item value="arrival" id="arrival" class="sr-only" aria-label={t.arrival} />
<span>{t.arrival}</span>
</Label>
</RadioGroup.Root>
<div>
<Toggle aria-label="toggle bold" bind:pressed={wheelchair}>
<Accessibility class="h-6 w-6" />
</Toggle>
<Toggle aria-label="toggle bold" bind:pressed={bikeRental}>
<Bike class="h-6 w-6" />
</Toggle>
</div>
<AdvancedOptions bind:wheelchair bind:bikeRental bind:bikeCarriage bind:selectedModes />
</div>
</div>
Loading
Loading