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

Missioncontrol addbasestationstates #44

Merged
merged 9 commits into from
Jan 4, 2025
226 changes: 160 additions & 66 deletions src/components/Navbar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ import PowerPlugIcon from 'vue-material-design-icons/PowerPlug.vue';
import ControllerIcon from 'vue-material-design-icons/ControllerClassic.vue';
import { useRoslibStore } from '@/store/roslibStore';
import { useControllerStore } from '@/store/controllerStore';
import { useOperationStateStore } from '../store/operationStateStore';

//TODO implement latency

const roslib = useRoslibStore();
const controller = useControllerStore();
const operation = useOperationStateStore();
const currentTab = ref(0);
const setCurrentTab = (newValue: number) => {
currentTab.value = newValue;
Expand Down Expand Up @@ -80,62 +82,94 @@ const pageIconArr: PageIcon = [
</script>
<template>
<nav>
<img id="logo" src="../assets/trickfire_logo_transparent.png" alt="Trickfire logo" />
<h1 id="logo-text">Mission Control</h1>
<RouterLink
v-for="(pageIcon, index) in pageIconArr"
:key="index"
:to="pageIcon.label"
class="container navbar-tab"
:class="{ 'current-page': currentTab === index }"
@click="setCurrentTab(index)"
>
<h4>{{ pageIcon.label }}</h4>
<component :is="pageIcon.icon" class="page-icon" :title="pageIcon.helperText" />
</RouterLink>
<div
class="container indicator-container"
:title="`Websocket: ${roslib.isWebSocketConnected ? `Connected` : `Disconnected`}`"
>
<h4 id="status">WS</h4>
<component
:is="PowerPlugIcon"
class="page-icon"
:class="{ green: roslib.isWebSocketConnected, red: !roslib.isWebSocketConnected }"
/>
</div>
<div
class="container indicator-container"
:title="`Camera: ${roslib.isWebSocketConnected ? `Connected` : `Disconnected`}`"
>
<h4 id="status">CAM</h4>
<component
:is="CameraIcon"
class="page-icon"
:class="{ green: roslib.isWebSocketConnected, red: !roslib.isWebSocketConnected }"
/>
</div>
<div
class="container indicator-container"
:title="`Controller: ${controller.isGamepadConnected ? `Connected` : `Disconnected`}`"
>
<h4 id="status">CTRL</h4>
<component
:is="ControllerIcon"
class="page-icon"
:class="{ green: controller.isGamepadConnected, red: !controller.isGamepadConnected }"
/>
</div>
<div class="container indicator-container">
<h4 id="status">Ping</h4>
<h4>{{ roslib.latency + 'ms' }}</h4>
</div>
<section id="logo-section">
<img id="logo" src="../assets/trickfire_logo_transparent.png" alt="Trickfire logo" />
<h1 id="logo-text">Mission Control</h1>
</section>
<section id="page-section">
<RouterLink
v-for="(pageIcon, index) in pageIconArr"
:key="index"
:to="pageIcon.label"
class="container navbar-tab"
:class="{ 'current-page': currentTab === index }"
@click="setCurrentTab(index)"
>
<h4>{{ pageIcon.label }}</h4>
<component :is="pageIcon.icon" class="page-icon" :title="pageIcon.helperText" />
</RouterLink>
</section>
<section id="states-section">
<div id="operation-selector" class="container">
<button
id="disabled-button"
title="Disabled"
:class="{ checked: operation.operationState === 'disabled' }"
@click="operation.setOperationState('disabled')"
>
Disable
</button>
<button
id="teleoperation-button"
title="TeleOperation"
:class="{ checked: operation.operationState === 'teleoperation' }"
@click="operation.setOperationState('teleoperation')"
>
TeleOp
</button>
<button
id="autonomous-button"
title="Autonomous"
:class="{ checked: operation.operationState === 'autonomous' }"
@click="operation.setOperationState('autonomous')"
>
Auto
</button>
</div>
<div
class="container"
:title="`Websocket: ${roslib.isWebSocketConnected ? `Connected` : `Disconnected`}`"
>
<h4 id="status">WS</h4>
<component
:is="PowerPlugIcon"
class="page-icon"
:class="{ green: roslib.isWebSocketConnected, red: !roslib.isWebSocketConnected }"
/>
</div>
<div
class="container"
:title="`Camera: ${roslib.isWebSocketConnected ? `Connected` : `Disconnected`}`"
>
<h4 id="status">CAM</h4>
<component
:is="CameraIcon"
class="page-icon"
:class="{ green: roslib.isWebSocketConnected, red: !roslib.isWebSocketConnected }"
/>
</div>
<div
class="container"
:title="`Controller: ${controller.isGamepadConnected ? `Connected` : `Disconnected`}`"
>
<h4 id="status">CTRL</h4>
<component
:is="ControllerIcon"
class="page-icon"
:class="{ green: controller.isGamepadConnected, red: !controller.isGamepadConnected }"
/>
</div>
<div class="container">
<h4 id="status">Ping</h4>
<h4>{{ roslib.latency + 'ms' }}</h4>
</div>
</section>
</nav>
</template>

<style lang="scss" scoped>
nav {
padding: 0rem 1rem;
padding: 0 0 0 1rem;
grid-area: nav;
display: flex;
align-items: center;
Expand All @@ -145,42 +179,102 @@ nav {
h2,
h3,
h4,
p {
p,
select {
color: var(--white);
white-space: nowrap;
overflow: hidden;
}
.page-icon {
transform: scale(1.25);
}
.current-page {
background-color: var(--light-grey);
}
.navbar-tab {
padding: 0 0.3rem;
min-width: 4rem;
min-width: 4.5rem;
.page-icon {
transform: scale(1.25);
}
}
.navbar-tab:not(.current-page):hover {
background-color: hsl(0, 0%, 16%);
}
#logo {
max-width: 100%;
max-height: 4rem;
}
#logo-text {
margin: 0 1rem;
}
.container {
height: var(--nav-bar-size);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.indicator-container {
min-width: max-content;
padding: 0 1.5rem;
#logo-section {
display: flex;
align-items: center;
flex-shrink: 0;

#logo {
max-width: 100%;
max-height: 3rem;
}
#logo-text {
margin: 0 1rem;
}
}
#page-section {
display: flex;
overflow-x: scroll;
overflow-y: hidden;
scrollbar-width: none;
flex-grow: 1;
border-right: 1px solid var(--white);
border-left: 1px solid var(--white);
}
#states-section {
display: flex;
gap: 1.75rem;
height: var(--nav-bar-size);
padding: 0 2rem 0 1.5rem;
background-color: hsl(240, 20%, 20%);

#operation-selector {
margin: auto 0;
display: flex;
flex-direction: row;
padding: 0 0.5rem;
background-color: hsl(240, 20%, 10%);
border-radius: 4px;
height: 80%;
gap: 0.25rem;

button {
height: 80%;
cursor: pointer;
align-items: center;
padding: 0 1rem;
border-radius: 4px;
background-color: transparent;
color: white;
}

button:hover:not(.checked) {
background-image: linear-gradient(rgb(0 0 0/40%) 0 0);
}

button.checked {
background-image: none;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
}

button#disabled-button.checked {
background-color: hsl(0, 100%, 27%);
}

button#autonomous-button.checked {
background-color: hsl(300, 100%, 23%);
}

button#teleoperation-button.checked {
Copy link
Collaborator

Choose a reason for hiding this comment

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

My bad for not noticing this earlier, but let's use the power of scss to make this even cleaner.

Basically instead of repeating the word button, just use &

button {
        height: 80%;
        cursor: pointer;
        align-items: center;
        padding: 0 1rem;
        border-radius: 4px;
        background-color: transparent;
        color: white;
      }
      &:hover:not(.checked) {
      ...
      }
      &disabled-button.checked {
      ...
      }
}
...

background-color: hsl(120, 100%, 15%);
}
}
}
.red {
color: var(--error);
Expand Down
25 changes: 25 additions & 0 deletions src/store/operationStateStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { defineStore } from 'pinia';
import { createPublisher } from '@/lib/roslibUtils/createPublisher';
import { ref } from 'vue';

export type OperationState = 'disabled' | 'teleoperation' | 'autonomous';

export const useOperationStateStore = defineStore('operationType', () => {
const operationStatePublisher = createPublisher({
topicName: '/setOperationState',
topicType: 'std_msgs/String',
});
const operationState = ref<OperationState>('disabled');

/**
* This function sets the operation state to the specified state and sends out a message on the ros topic to change the state.
* @param state - the new operation state to set the driver to.
*/
function setOperationState(state: OperationState) {
uellenberg marked this conversation as resolved.
Show resolved Hide resolved
operationState.value = state;
operationStatePublisher.publish({ data: operationState.value });
}

// Return all state, getters and functions
return { operationState, setOperationState };
});
Loading