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

App 723 cameras #1485

Merged
merged 24 commits into from
Oct 19, 2022
Merged
Show file tree
Hide file tree
Changes from 15 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
28 changes: 28 additions & 0 deletions web/frontend/cypress/e2e/robot-components/base.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
describe('should interact with base', () => {
micheal-parks marked this conversation as resolved.
Show resolved Hide resolved
it('passes', () => {
cy.visit('/');

// Open base
cy.contains('h2', 'test_base').should('exist').click();

// Activate and deactivate keyboard
cy.get('[aria-label="Keyboard Disabled"]').should('exist').click();
cy.get('[aria-label="Keyboard Enabled"]').should('exist').click();

// Select camera
cy.get('[aria-label="Select Cameras"]').find('[aria-disabled="false"]').click().type('test_camera{enter}{esc}');
cy.get('[data-stream-preview="test_camera"').find('video');

// Confirm that camera component can open stream that is active already
// Open camera
cy.contains('h2', 'test_camera').should('exist').click();

// View and hide camera
cy.get('[aria-label="View Camera"]').find('button').should('exist').click();
cy.get('[aria-label="test_camera camera stream"').should('exist');
cy.get('[aria-label="Hide Camera"]').find('button').should('exist').click();
cy.get('[aria-label="test_camera camera stream"').should('not.be.visible');
});
});

export {};
14 changes: 7 additions & 7 deletions web/frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion web/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"@rollup/plugin-alias": "^4.0.0",
"@rollup/plugin-commonjs": "^23.0.0",
"@rollup/plugin-node-resolve": "^15.0.0",
"@viamrobotics/prime": "^0.0.69",
"@viamrobotics/prime": "^0.0.71",
"@viamrobotics/rpc": "^0.1.23",
"@vitejs/plugin-vue": "^3.1.0",
"@vueuse/core": "^8.9.4",
Expand Down
51 changes: 15 additions & 36 deletions web/frontend/src/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,14 @@ const status = $ref<Record<string, Status>>({});
const errors = $ref<Record<string, boolean>>({});

let statusStream: ResponseStream<StreamStatusResponse>;
let baseCameraState = new Map<string, boolean>();
let lastStatusTS = Date.now();
let disableAuthElements = $ref(false);
let cameraFrameIntervalId = $ref(-1);
let currentOps = $ref<{ op: Operation.AsObject, elapsed: number }[]>([]);
let sensorNames = $ref<ResourceName.AsObject[]>([]);
let resources = $ref<Resource[]>([]);
let errorMessage = $ref('');
let activePreviews = $ref<string[]>([]);
let connectionManager = $ref<{
statuses: {
resources: boolean;
Expand Down Expand Up @@ -492,37 +492,24 @@ const filteredInputControllerList = () => {
};

const viewCamera = async (name: string, isOn: boolean) => {
const streamContainers = document.querySelectorAll(`[data-stream="${name}"]`);

if (isOn) {
try {
await addStream(name);
for (const container of streamContainers) {
container.querySelector('img')?.remove();
}

const previews = document.querySelectorAll(`[data-stream-preview="${name}"]`);
for (const preview of previews) {
preview.removeAttribute('hidden');
// only add stream if base camera is not active
if (!baseCameraState.get(name)) {
await addStream(name);
}
} catch (error) {
displayError(error as ServiceError);
}

return;
}

try {
const previews = document.querySelectorAll(`[data-stream-preview="${name}"]`);
for (const preview of previews) {
preview.setAttribute('hidden', 'true');
}
await removeStream(name);
for (const container of streamContainers) {
container.querySelector('img')?.remove();
} else {
try {
// only remove stream if base camera is not active
if (!baseCameraState.get(name)) {
await removeStream(name);
}
} catch (error) {
displayError(error as ServiceError);
}
} catch (error) {
displayError(error as ServiceError);
}
};

Expand Down Expand Up @@ -625,16 +612,8 @@ const waitForClientAndStart = async () => {
}
};

const handleSelectCamera = async (event: string) => {
if (event === '') {
await Promise.all(activePreviews.map((preview) => viewCamera(preview, false)));
activePreviews = [];
return;
}

const previews = event.split(',');
await Promise.all(previews.map((preview) => viewCamera(preview, !activePreviews.includes(preview))));
activePreviews = previews;
const updatedBaseCameraState = (e: Map<string, boolean>) => {
baseCameraState = e;
};

onMounted(async () => {
Expand Down Expand Up @@ -706,7 +685,7 @@ onMounted(async () => {
:key="base.name"
:name="base.name"
:resources="resources"
@showcamera="handleSelectCamera($event)"
@base-camera-state="updatedBaseCameraState($event)"
/>

<!-- ******* GANTRY ******* -->
Expand Down
80 changes: 45 additions & 35 deletions web/frontend/src/components/base.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { grpc } from '@improbable-eng/grpc-web';
import { ref } from 'vue';
import { ref, onMounted } from 'vue';
import { computeKeyboardBaseControls, BaseControlHelper } from '../rc/control_helpers';
import baseApi from '../gen/proto/api/component/base/v1/base_pb.esm';
import commonApi from '../gen/proto/api/common/v1/common_pb.esm';
Expand All @@ -25,7 +25,7 @@ type SpinTypes = 'Clockwise' | 'Counterclockwise'
type Directions = 'Forwards' | 'Backwards'

interface Emits {
(event: 'showcamera', value: string): void
(event: 'base-camera-state', value: Map<string, boolean>): void
}

const emit = defineEmits<Emits>();
Expand All @@ -42,12 +42,17 @@ const speed = ref(200);
const spinSpeed = ref(90);
const angle = ref(0);

const resetDiscreteState = () => {
movementMode.value = 'Straight';
movementType.value = 'Continuous';
direction.value = 'Forwards';
spinType.value = 'Clockwise';
};
let videoStreamStates = new Map<string, boolean>();

const resetStreamState = () => {
for (var value of filterResources(props.resources, 'rdk', 'component', 'camera')) {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
for (var value of filterResources(props.resources, 'rdk', 'component', 'camera')) {
for (const value of filterResources(props.resources, 'rdk', 'component', 'camera')) {

videoStreamStates.set(value.name, false);
}
}

onMounted(() => {
resetStreamState();
})

const setMovementMode = (mode: MovementModes) => {
movementMode.value = mode;
Expand Down Expand Up @@ -131,36 +136,40 @@ const baseRun = () => {
}
};

const viewPreviewCamera = async (name: string, isOn: boolean) => {
if (isOn) {
try {
await addStream(name);
} catch (error) {
displayError(error as ServiceError);
}
return;
}
const viewPreviewCamera = async (name: string) => {
for (let [key, value] of videoStreamStates) {
const streamContainers = document.querySelector(`[data-stream="${key}"]`);

try {
await removeStream(name);
} catch (error) {
displayError(error as ServiceError);
// Only turn on if state is off
if (name.includes(key) && value === false) {
try {
// Only add stream if other components have not already
if (streamContainers?.classList.contains('hidden')) {
await addStream(key);
}
videoStreamStates.set(key, true);
emit('base-camera-state', videoStreamStates);
} catch (error) {
displayError(error as ServiceError);
}
// Only turn off if state is on
} else if (!name.includes(key) && value === true) {
try {
// Only remove stream if other components are not using the stream
if (streamContainers?.classList.contains('hidden')) {
await removeStream(key);
}
videoStreamStates.set(key, false);
emit('base-camera-state', videoStreamStates);
} catch (error) {
displayError(error as ServiceError);
}
}
}
};

const handleTabSelect = (tab: Tabs) => {
selectedItem.value = tab;

if (tab === 'Keyboard') {
viewPreviewCamera(props.name, true);
} else {
viewPreviewCamera(props.name, false);
resetDiscreteState();
}
};

const handleSelectCamera = (event: string) => {
emit('showcamera', event);
};

</script>
Expand Down Expand Up @@ -196,7 +205,7 @@ const handleSelectCamera = (event: string) => {
class="h-auto p-4"
>
<div class="grid grid-cols-2">
<div class="mt-2">
<div>
<KeyboardInput
:name="name"
@keyboard-ctl="baseKeyboardCtl(name, $event)"
Expand All @@ -207,12 +216,13 @@ const handleSelectCamera = (event: string) => {
class="mb-4"
variant="multiple"
placeholder="Select Cameras"
aria-label="Select Cameras"
:options="
filterResources(resources, 'rdk', 'component', 'camera')
.map(({ name }) => name)
.join(',')
"
@input="handleSelectCamera($event.detail.value)"
@input="viewPreviewCamera($event.detail.value)"
/>
<template
v-for="basecamera in filterResources(
Expand All @@ -226,7 +236,7 @@ const handleSelectCamera = (event: string) => {
<div
v-if="basecamera"
:data-stream-preview="basecamera.name"
class="mb-4 border border-white"
:class="{ 'hidden': !videoStreamStates.get(basecamera.name) }"
/>
</template>
</div>
Expand Down
10 changes: 6 additions & 4 deletions web/frontend/src/components/camera.vue
Original file line number Diff line number Diff line change
Expand Up @@ -95,11 +95,12 @@ const exportScreenshot = (cameraName: string) => {
<div class="pt-4">
<div class="flex items-center gap-2">
<v-switch
:label="camera ? 'Hide Camera' : 'View Camera'"
:aria-label="camera ? 'Hide Camera' : 'View Camera'"
id="camera"
:value="camera ? 'on' : 'off'"
@input="toggleExpand"
/>
<span class="pr-2 text-xs">View Camera</span>
</div>

<div class="float-right pb-4">
Expand Down Expand Up @@ -172,18 +173,19 @@ const exportScreenshot = (cameraName: string) => {
</div>
</div>
<div
v-if="camera"
:aria-label="`${props.cameraName} camera stream`"
:data-stream="props.cameraName"
:class="{ 'hidden': !camera }"
class="clear-both h-fit transition-all duration-300 ease-in-out"
/>
</div>
<div class="pt-4">
<div class="flex items-center gap-2">
<div class="flex align-top items-center gap-2">
<v-switch
:label="pcdExpanded ? 'Hide Point Cloud Data' : 'View Point Cloud Data'"
:value="pcdExpanded ? 'on' : 'off'"
@input="togglePCDExpand"
/>
<span class="pr-0.5 text-xs">Point Cloud Data</span>
<InfoButton :info-rows="['When turned on, point cloud will be recalculated']" />
</div>

Expand Down
6 changes: 2 additions & 4 deletions web/frontend/src/components/keyboard-input.vue
Original file line number Diff line number Diff line change
Expand Up @@ -109,16 +109,14 @@ onClickOutside(root, () => {
class="h-23 flex w-full flex-col items-center"
>
<div
class="flex gap-2 pb-4"
class="flex gap-2 pb-4 w-48"
@click="toggleKeyboard"
>
<v-switch
:label="isActive ? 'Keyboard Enabled' : 'Keyboard Disabled'"
class="pr-4"
:value="isActive ? 'on' : 'off'"
/>
<h3>
Keyboard {{ isActive ? 'active' : 'disabled' }}
</h3>
</div>
<div
v-for="(lineKeys, index) in keysLayout"
Expand Down
4 changes: 2 additions & 2 deletions web/runtime-shared/static/control.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion web/runtime-shared/static/control.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion web/runtime-shared/static/main.css

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion web/runtime-shared/static/prime.css

This file was deleted.