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}
-