diff --git a/etc/configs/fake.json b/etc/configs/fake.json index d7a76f20091..80284443af8 100644 --- a/etc/configs/fake.json +++ b/etc/configs/fake.json @@ -140,6 +140,28 @@ "movement_sensor": "movement_sensor1", "base": "base1", "obstacles": [ + { + "location": { + "latitude": 40.6705209, + "longitude": -73.9659182 + }, + "geometries": [ + { + "r": 2000 + } + ] + }, + { + "location": { + "longitude": -73.976472, + "latitude": 40.693268 + }, + "geometries": [ + { + "r": 2000000 + } + ] + }, { "geometries": [ { diff --git a/web/frontend/package-lock.json b/web/frontend/package-lock.json index dd87e3bf5f0..7999881b9fb 100644 --- a/web/frontend/package-lock.json +++ b/web/frontend/package-lock.json @@ -37,6 +37,7 @@ "postcss": "8.4.24", "svelte": "^4.0.0", "svelte-check": "^3.4.4", + "svelte-inview": "^4.0.1", "tailwindcss": "3.3.2", "three": "0.152.2", "three-inspect": "0.3.4", @@ -6454,6 +6455,15 @@ "svelte": "^3.19.0 || ^4.0.0-next.0" } }, + "node_modules/svelte-inview": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/svelte-inview/-/svelte-inview-4.0.1.tgz", + "integrity": "sha512-8NuT/DKFiZAccDw1Z16cIsdZ7K6/BGxrUfDaFaWTCEdn3YqMj1TUAkfmQ08FQ1+INl1G+TQS+ImXIBiiISgXog==", + "dev": true, + "peerDependencies": { + "svelte": "^3.0.0 || ^4.0.0" + } + }, "node_modules/svelte-preprocess": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.0.4.tgz", diff --git a/web/frontend/package.json b/web/frontend/package.json index d50f222d13b..31a68188080 100644 --- a/web/frontend/package.json +++ b/web/frontend/package.json @@ -1,6 +1,6 @@ { "name": "@viamrobotics/remote-control", - "version": "2.0.11", + "version": "2.0.12", "license": "Apache-2.0", "type": "module", "files": [ @@ -52,6 +52,7 @@ "postcss": "8.4.24", "svelte": "^4.0.0", "svelte-check": "^3.4.4", + "svelte-inview": "^4.0.1", "tailwindcss": "3.3.2", "three": "0.152.2", "three-inspect": "0.3.4", diff --git a/web/frontend/src/api/navigation.ts b/web/frontend/src/api/navigation.ts index e435dade8a3..5d938da6969 100644 --- a/web/frontend/src/api/navigation.ts +++ b/web/frontend/src/api/navigation.ts @@ -198,6 +198,10 @@ export const getLocation = async (robotClient: Client, name: string) => { const lat = location?.getLatitude(); const lng = location?.getLongitude(); + rcLogConditionally(response?.getLocation()?.toObject()); + rcLogConditionally(location?.getLatitude()); + rcLogConditionally(location?.getLongitude()); + if (typeof lat !== 'number' || typeof lng !== 'number') { // eslint-disable-next-line unicorn/prefer-type-error throw new Error('Unable to locate robot'); diff --git a/web/frontend/src/components/base/index.svelte b/web/frontend/src/components/base/index.svelte index 3661d9d3e2d..29ec475efcf 100644 --- a/web/frontend/src/components/base/index.svelte +++ b/web/frontend/src/components/base/index.svelte @@ -80,6 +80,7 @@ const stop = async () => { stopped = true; + try { await baseClient.stop(); } catch (error) { @@ -200,17 +201,17 @@ disableViews = liveCameras > 1; }; - const handleVisibilityChange = () => { - // only issue the stop if there are keys pressed as to not interfere with commands issued by scripts/commands - if (document.visibilityState === 'hidden' && pressed.size > 0) { + const handleOnBlur = () => { + if (pressed.size <= 0) { pressed.clear(); stop(); } }; - const handleOnBlur = () => { - if (pressed.size <= 0) { - stop(); + const handleVisibilityChange = () => { + // only issue the stop if there are keys pressed as to not interfere with commands issued by scripts/commands + if (document.visibilityState === 'hidden') { + handleOnBlur(); } }; diff --git a/web/frontend/src/components/motor/index.svelte b/web/frontend/src/components/motor/index.svelte index 826ea44c5f7..3c63006d133 100644 --- a/web/frontend/src/components/motor/index.svelte +++ b/web/frontend/src/components/motor/index.svelte @@ -54,15 +54,63 @@ const setMovementType = (event: CustomEvent) => { }; const setPosition = (event: CustomEvent) => { - position = event.detail.value; + const target = event.currentTarget as HTMLInputElement; + + if (event.type === 'blur' && target.value === undefined) { + position = 0; + } + + if (target.value === '') { + return; + } + + const num = Number.parseFloat(target.value); + + if (Number.isNaN(num)) { + return; + } + + position = num; }; const setRpm = (event: CustomEvent) => { - rpm = event.detail.value; + const target = event.currentTarget as HTMLInputElement; + + if (event.type === 'blur' && target.value === undefined) { + rpm = 0; + } + + if (target.value === '') { + return; + } + + const num = Number.parseFloat(target.value); + + if (Number.isNaN(num)) { + return; + } + + rpm = num; }; const setRevolutions = (event: CustomEvent) => { - revolutions = event.detail.value; + const target = event.currentTarget as HTMLInputElement; + + if (event.type === 'blur' && target.value === undefined) { + revolutions = 0; + } + + if (target.value === '') { + return; + } + + const num = Number.parseInt(target.value, 10); + + if (Number.isNaN(num)) { + return; + } + + revolutions = num; }; const setPowerSlider = (event: CustomEvent) => { @@ -167,12 +215,13 @@ const handleToggle = async (event: CustomEvent<{ open: boolean }>) => {
- + {#if movementType === 'Go'} Continuously moves {:else if movementType === 'Go for'} @@ -184,31 +233,34 @@ const handleToggle = async (event: CustomEvent<{ open: boolean }>) => {
- {#if movementType === 'Go to'} -
+
+ {#if movementType === 'Go to'} -
- {:else if movementType === 'Go for'} -
+ {:else if movementType === 'Go for'} ) => { -
- {:else if movementType === 'Go'} -
+ {:else if movementType === 'Go'} ) => { on:input={setPowerSlider} />
-
- {/if} + {/if} +
diff --git a/web/frontend/src/components/navigation/components/input/lnglat.svelte b/web/frontend/src/components/navigation/components/input/lnglat.svelte index ede288f3b64..55729252ce5 100644 --- a/web/frontend/src/components/navigation/components/input/lnglat.svelte +++ b/web/frontend/src/components/navigation/components/input/lnglat.svelte @@ -9,11 +9,6 @@ export let readonly: true | undefined = undefined; export let lng: number | undefined; export let lat: number | undefined; -const decimalFormat = new Intl.NumberFormat(undefined, { maximumFractionDigits: 6 }); - -$: lngRounded = decimalFormat.format(lng ?? 0); -$: latRounded = decimalFormat.format(lat ?? 0); - const dispatch = createEventDispatcher<{ input: LngLat }>(); const handleLng = (event: CustomEvent<{ value: string }>) => { @@ -40,7 +35,7 @@ const handleLat = (event: CustomEvent<{ value: string }>) => { label={label ?? 'Latitude'} placeholder='0' incrementor={readonly ? undefined : 'slider'} - value={latRounded} + value={lat} step={$mapZoom ** 5} class='w-full' on:input={handleLat} @@ -51,7 +46,7 @@ const handleLat = (event: CustomEvent<{ value: string }>) => { label={label ? '' : 'Longitude'} placeholder='0' incrementor={readonly ? undefined : 'slider'} - value={lngRounded} + value={lng} step={$mapZoom ** 5} class='w-full' on:input={handleLng} diff --git a/web/frontend/src/components/navigation/components/map.svelte b/web/frontend/src/components/navigation/components/map.svelte index feda6b2d1bd..c3bde8399e0 100644 --- a/web/frontend/src/components/navigation/components/map.svelte +++ b/web/frontend/src/components/navigation/components/map.svelte @@ -2,7 +2,7 @@ import { onMount } from 'svelte'; import { Map, NavigationControl } from 'maplibre-gl'; -import { map, mapZoom, mapCenter, view } from '../stores'; +import { map, mapZoom, mapCenter, view, mapSize, cameraMatrix } from '../stores'; import { style } from '../style'; import ObstacleLayer from './obstacle-layer.svelte'; import Waypoints from './waypoints.svelte'; @@ -17,15 +17,6 @@ const handleViewSelect = (event: CustomEvent) => { $view = event.detail.value; }; -const handleMove = () => { - if (!map.current) { - return; - } - - mapCenter.set(map.current.getCenter()); - mapZoom.set(map.current.getZoom() / map.current.getMaxZoom()); -}; - onMount(() => { const mapInstance = new Map({ container: 'navigation-map', @@ -36,14 +27,47 @@ onMount(() => { minPitch, maxPitch: minPitch, }); + $map = mapInstance; + + const handleMove = () => { + mapCenter.set(mapInstance.getCenter()); + mapZoom.set(mapInstance.getZoom() / mapInstance.getMaxZoom()); + }; + + const handleResize = () => { + mapSize.update((value) => { + const { clientWidth, clientHeight } = mapInstance.getCanvas(); + value.width = clientWidth; + value.height = clientHeight; + return value; + }); + }; const nav = new NavigationControl({ showZoom: false }); mapInstance.addControl(nav, 'top-right'); - mapInstance.on('move', handleMove); + mapInstance.on('resize', handleResize); + + mapInstance.on('style.load', () => { + mapInstance.addLayer({ + id: 'obstacle-layer', + type: 'custom', + renderingMode: '3d', + render (_ctx, viewProjectionMatrix) { + cameraMatrix.fromArray(viewProjectionMatrix); + mapInstance.triggerRepaint(); + }, + }); + }); + handleMove(); + handleResize(); - $map = mapInstance; + return () => { + if (mapInstance.getLayer('obstacle-layer')) { + mapInstance.removeLayer('obstacle-layer'); + } + }; }); $: { @@ -70,11 +94,5 @@ $: { {#if $map} - + {/if} - - diff --git a/web/frontend/src/components/navigation/components/marker.svelte b/web/frontend/src/components/navigation/components/marker.svelte index 8d84c3af70b..1022a7f52df 100644 --- a/web/frontend/src/components/navigation/components/marker.svelte +++ b/web/frontend/src/components/navigation/components/marker.svelte @@ -10,6 +10,7 @@ export let visible = true; export let color: string | null = null; const marker = new Marker({ scale, color: color ?? undefined }); +marker.getElement().style.zIndex = '1'; $: { marker.setLngLat(lngLat); diff --git a/web/frontend/src/components/navigation/components/obstacle-layer.svelte b/web/frontend/src/components/navigation/components/obstacle-layer.svelte index 3fcf8ff02fd..16466b41e3a 100644 --- a/web/frontend/src/components/navigation/components/obstacle-layer.svelte +++ b/web/frontend/src/components/navigation/components/obstacle-layer.svelte @@ -1,52 +1,15 @@ -{#if context} - - +
+ + -{/if} +
diff --git a/web/frontend/src/components/navigation/components/robot-marker.svelte b/web/frontend/src/components/navigation/components/robot-marker.svelte index f77656f7f02..ba815ec01ff 100644 --- a/web/frontend/src/components/navigation/components/robot-marker.svelte +++ b/web/frontend/src/components/navigation/components/robot-marker.svelte @@ -23,6 +23,10 @@ const updateLocation = async () => { centered = true; } + if ($robotPosition?.lat === position.lat && $robotPosition.lng === position.lng) { + return; + } + $robotPosition = position; } catch (error) { notify.danger((error as ServiceError).message); diff --git a/web/frontend/src/components/navigation/components/scene.svelte b/web/frontend/src/components/navigation/components/scene.svelte index 39902d9805f..3748745f721 100644 --- a/web/frontend/src/components/navigation/components/scene.svelte +++ b/web/frontend/src/components/navigation/components/scene.svelte @@ -1,39 +1,26 @@ {#if !flat} - + {/if} -{#each $obstacles as obstacle} +{#each $obstacles as obstacle (obstacle.name)} {/each} diff --git a/web/frontend/src/components/navigation/index.svelte b/web/frontend/src/components/navigation/index.svelte index 78ae01db8a5..e5169acd925 100644 --- a/web/frontend/src/components/navigation/index.svelte +++ b/web/frontend/src/components/navigation/index.svelte @@ -5,13 +5,14 @@ import 'maplibre-gl/dist/maplibre-gl.css'; import { notify } from '@viamrobotics/prime'; import { navigationApi, type ServiceError } from '@viamrobotics/sdk'; -import { setMode, type NavigationModes } from '@/api/navigation'; -import { mapCenter, centerMap, robotPosition, flyToMap, write as writeStore } from './stores'; +import { setMode, getObstacles, type NavigationModes } from '@/api/navigation'; +import { mapCenter, centerMap, robotPosition, flyToMap, write as writeStore, obstacles } from './stores'; import { useRobotClient } from '@/hooks/robot-client'; import Collapse from '@/lib/components/collapse.svelte'; import Map from './components/map.svelte'; import Nav from './components/nav/index.svelte'; import LngLatInput from './components/input/lnglat.svelte'; +import { inview } from 'svelte-inview'; export let name: string; export let write = false; @@ -35,6 +36,10 @@ const setNavigationMode = async (event: CustomEvent) => { } }; +const handleEnter = async () => { + $obstacles = await getObstacles($robotClient, name); +}; + @@ -43,7 +48,11 @@ const setNavigationMode = async (event: CustomEvent) => { crumbs="navigation" /> -
+
@@ -73,7 +82,7 @@ const setNavigationMode = async (event: CustomEvent) => {