adds charts panel

This commit is contained in:
2025-02-15 15:42:33 -08:00
parent 69c0b0965d
commit e9751c2489
16 changed files with 600 additions and 42 deletions

View File

View File

@@ -37,11 +37,21 @@
max-width: 1200px; max-width: 1200px;
} }
.content.full {
margin: 5vh 5vw;
max-width: none;
}
.screen.content { .screen.content {
width: calc(100% - 20vw); width: calc(100% - 20vw);
height: calc(100vh - 40vh); height: calc(100vh - 40vh);
} }
.screen.content.full {
width: calc(100% - 10vw);
height: calc(100vh - 10vh);
}
.stretch { .stretch {
align-items: stretch; align-items: stretch;
} }
@@ -54,6 +64,10 @@
flex-grow: 2; flex-grow: 2;
} }
.wrap {
flex-wrap: wrap;
}
.no-basis { .no-basis {
flex-basis: 0; flex-basis: 0;
} }

View File

@@ -13,6 +13,7 @@ body {
align-content: center; align-content: center;
min-height: 100vh; min-height: 100vh;
margin: 0; margin: 0;
stroke-width: 0;
} }
main { main {
@@ -28,10 +29,13 @@ main {
} }
* { * {
stroke-width: 0;
box-sizing: border-box; box-sizing: border-box;
} }
.hidden {
visibility: hidden;
}
polyline { polyline {
stroke-width: 1px; stroke-width: 1px;
} }

View File

@@ -0,0 +1,50 @@
<script setup lang="ts">
defineProps<{
icon: string;
}>();
</script>
<template>
<i :class="`icon ${icon}`"></i>
</template>
<style scoped lang="scss">
@use '@/assets/variables';
.icon {
display: inline-block;
height: 1em;
}
.icon:before {
display: inline-block;
content: ' ';
background-color: variables.$text-color;
mask-size: cover;
width: 1em;
height: 1em;
}
.icon.hamburger.menu:before {
mask-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9IjAgMCAxMCAxMCIgc3Ryb2tlPSJ3aGl0ZSIgc3Ryb2tlV2lkdGg9IjFweCI+PGxpbmUgeDE9IjAiIHkxPSIxIiB4Mj0iMTAiIHkyPSIxIj48L2xpbmU+PGxpbmUgeDE9IjAiIHkxPSI1IiB4Mj0iMTAiIHkyPSI1Ij48L2xpbmU+PGxpbmUgeDE9IjAiIHkxPSI5IiB4Mj0iMTAiIHkyPSI5Ij48L2xpbmU+PC9zdmc+IA==');
}
.icon.close:before {
mask-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9IjAgMCAxMCAxMCIgc3Ryb2tlPSJ3aGl0ZSIgc3Ryb2tlV2lkdGg9IjFweCI+PGxpbmUgeDE9IjAiIHkxPSIwIiB4Mj0iMTAiIHkyPSIxMCI+PC9saW5lPjxsaW5lIHgxPSIxMCIgeTE9IjAiIHgyPSIwIiB5Mj0iMTAiPjwvbGluZT48L3N2Zz4g');
}
.icon.up.arrow:before {
mask-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9IjAgMCAxMCAxMCIgc3Ryb2tlPSJ3aGl0ZSIgc3Ryb2tlV2lkdGg9IjFweCI+PGxpbmUgeDE9IjAiIHkxPSIxMCIgeDI9IjUiIHkyPSIwIj48L2xpbmU+PGxpbmUgeDE9IjUiIHkxPSIwIiB4Mj0iMTAiIHkyPSIxMCI+PC9saW5lPjwvc3ZnPg==');
}
.icon.down.arrow:before {
mask-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9IjAgMCAxMCAxMCIgc3Ryb2tlPSJ3aGl0ZSIgc3Ryb2tlV2lkdGg9IjFweCI+PGxpbmUgeDE9IjAiIHkxPSIwIiB4Mj0iNSIgeTI9IjEwIj48L2xpbmU+PGxpbmUgeDE9IjUiIHkxPSIxMCIgeDI9IjEwIiB5Mj0iMCI+PC9saW5lPjwvc3ZnPg==');
}
/*
<svg xmlns='http://www.w3.org/2000/svg' viewBox="0 0 10 10" stroke="white" strokeWidth="1px">
<line x1="0" y1="0" x2="5" y2="10"></line>
<line x1="5" y1="10" x2="10" y2="0"></line>
</svg>
*/
</style>

View File

@@ -16,6 +16,7 @@ import {
getDurationString, getDurationString,
parseDurationString, parseDurationString,
} from '@/datetime'; } from '@/datetime';
import { onDocumentShown } from '@/composables/document.ts';
const props = defineProps<{ const props = defineProps<{
initial_duration?: number; initial_duration?: number;
@@ -32,7 +33,7 @@ const props = defineProps<{
const divRef = useTemplateRef<HTMLDivElement>('graph-div'); const divRef = useTemplateRef<HTMLDivElement>('graph-div');
const width = ref(0); const width = ref(0);
const height = ref(0); const raw_height = ref(0);
const controls_height = 32; const controls_height = 32;
const min_time_label_separation = 250; const min_time_label_separation = 250;
@@ -41,13 +42,18 @@ const resize_observer = new ResizeObserver((elements) => {
for (const element of elements) { for (const element of elements) {
if (element.target == divRef.value) { if (element.target == divRef.value) {
width.value = element.contentBoxSize[0].inlineSize; width.value = element.contentBoxSize[0].inlineSize;
height.value = raw_height.value = element.contentBoxSize[0].blockSize;
element.contentBoxSize[0].blockSize -
(props.include_controls ? controls_height : 0);
} }
} }
}); });
const height = computed(() => {
return Math.max(
raw_height.value - (props.include_controls ? controls_height : 0),
1,
);
});
watch([divRef], ([divRef]) => { watch([divRef], ([divRef]) => {
if (divRef) { if (divRef) {
resize_observer.observe(divRef); resize_observer.observe(divRef);
@@ -132,6 +138,10 @@ watch([now], ([now_value]) => {
} }
}); });
onDocumentShown(() => {
fetch_history.value++;
});
const max_x_text = computed({ const max_x_text = computed({
// getter // getter
get() { get() {
@@ -193,8 +203,12 @@ const legend_x_stride = computed(() => 0);
const legend_y_stride = computed(() => 16); const legend_y_stride = computed(() => 16);
const legend_width_output = computed(() => legend_width - 8); const legend_width_output = computed(() => legend_width - 8);
const graph_width = computed(() => {
return Math.max(width.value - border_left.value - border_right.value, 1);
});
const line_duration = computed(() => { const line_duration = computed(() => {
const width_px = width.value; const width_px = graph_width.value;
const diff_x = max_x.value - min_x.value; const diff_x = max_x.value - min_x.value;
return time_lines.find((duration) => { return time_lines.find((duration) => {
const line_count = diff_x / duration; const line_count = diff_x / duration;
@@ -263,8 +277,7 @@ provide<GraphData>(GRAPH_DATA, {
max_temporal_resolution: max_temporal_resolution, max_temporal_resolution: max_temporal_resolution,
live: live, live: live,
fetch_history: fetch_history, fetch_history: fetch_history,
width: () => width: graph_width,
Math.max(width.value - border_left.value - border_right.value, 0),
height: () => height: () =>
Math.max(height.value - border_top.value - border_bottom.value, 0), Math.max(height.value - border_top.value - border_bottom.value, 0),
x_map: x_map, x_map: x_map,

View File

@@ -6,17 +6,35 @@ import Axis from '@/components/GraphAxis.vue';
import { computed } from 'vue'; import { computed } from 'vue';
const props = defineProps<{ const props = defineProps<{
telemetry_definition: TelemetryDefinition | null; selection: TelemetryDefinition | null;
secondary: TelemetryDefinition | null; mouseover: TelemetryDefinition | null;
}>(); }>();
const telemetry_definition = computed(() => {
if (props.mouseover) {
return props.mouseover;
}
return props.selection;
});
const secondary = computed(() => {
if (props.mouseover) {
if (props.selection) {
if (props.selection.uuid != props.mouseover.uuid) {
return props.selection;
}
}
}
return null;
});
const lines = computed(() => { const lines = computed(() => {
const result = []; const result = [];
if (props.secondary) { if (secondary.value) {
result.push(props.secondary); result.push(secondary.value);
} }
if (props.telemetry_definition) { if (telemetry_definition.value) {
result.push(props.telemetry_definition); result.push(telemetry_definition.value);
} }
return result; return result;
}); });

View File

@@ -389,7 +389,7 @@ function onMouseExit(event: MouseEvent) {
</g> </g>
<ValueLabel <ValueLabel
v-if="current_data_point" v-if="current_data_point"
class="fade_other_selected" class="fade_other_selected label"
:x="graph_data.x_map(toValue(graph_data.max_x)) + text_offset" :x="graph_data.x_map(toValue(graph_data.max_x)) + text_offset"
:y="axis_data.y_map(current_data_point.y)" :y="axis_data.y_map(current_data_point.y)"
:value="current_data_point.y" :value="current_data_point.y"
@@ -473,6 +473,10 @@ function onMouseExit(event: MouseEvent) {
opacity: 25%; opacity: 25%;
} }
.fade .fade_other_selected.label {
opacity: 0;
}
.fade .no-fade .fade_other_selected { .fade .no-fade .fade_other_selected {
opacity: 100%; opacity: 100%;
} }

View File

@@ -9,11 +9,10 @@ const props = defineProps<{
search?: string; search?: string;
}>(); }>();
const selected = defineModel<TelemetryDefinition | null>();
const emit = defineEmits<{ const emit = defineEmits<{
( (e: 'mouseover', tlm_entry: TelemetryDefinition | null): void;
e: 'select',
tlm_entry: [TelemetryDefinition | null, TelemetryDefinition | null],
): void;
}>(); }>();
const search_value = computed(() => (props.search || '').toLowerCase()); const search_value = computed(() => (props.search || '').toLowerCase());
@@ -33,7 +32,6 @@ const sorted_tlm_data = computed(() => {
}); });
const mousedover = ref<TelemetryDefinition | null>(null); const mousedover = ref<TelemetryDefinition | null>(null);
const selected = ref<TelemetryDefinition | null>(null);
function onMouseover(tlm_entry: TelemetryDefinition) { function onMouseover(tlm_entry: TelemetryDefinition) {
mousedover.value = tlm_entry; mousedover.value = tlm_entry;
@@ -47,17 +45,8 @@ function onClick(tlm_entry: TelemetryDefinition) {
selected.value = tlm_entry; selected.value = tlm_entry;
} }
watch([mousedover, selected], ([mousedover_val, selected_val]) => { watch([mousedover], ([mousedover_val]) => {
if (mousedover_val) { emit('mouseover', mousedover_val);
emit('select', [
mousedover_val,
mousedover_val.uuid != selected_val?.uuid ? selected_val : null,
]);
} else if (selected_val) {
emit('select', [selected_val, null]);
} else {
emit('select', [null, null]);
}
}); });
</script> </script>

View File

@@ -0,0 +1,24 @@
import { onMounted, onUnmounted, ref } from 'vue';
export function onDocumentVisibilityChange(
handler: (visible: boolean) => void,
) {
const handlerRef = ref(() => {
handler(!document.hidden);
});
onMounted(() => {
document.addEventListener('visibilitychange', handlerRef.value);
});
onUnmounted(() => {
document.removeEventListener('visibilitychange', handlerRef.value);
});
}
export function onDocumentShown(handler: () => void) {
onDocumentVisibilityChange((visible) => {
if (visible) {
handler();
}
});
}

View File

@@ -11,6 +11,7 @@ import {
watch, watch,
} from 'vue'; } from 'vue';
import type { TelemetryDefinition } from '@/composables/telemetry'; import type { TelemetryDefinition } from '@/composables/telemetry';
import { onDocumentVisibilityChange } from '@/composables/document.ts';
export interface TelemetryDataItem { export interface TelemetryDataItem {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -27,12 +28,14 @@ export class WebsocketHandle {
websocket: WebSocket | null; websocket: WebSocket | null;
should_be_connected: boolean; should_be_connected: boolean;
connected: Ref<boolean>; connected: Ref<boolean>;
enabled: Ref<boolean>;
on_telem_value: Map<string, Array<(value: TelemetryDataItem) => void>>; on_telem_value: Map<string, Array<(value: TelemetryDataItem) => void>>;
constructor() { constructor() {
this.websocket = null; this.websocket = null;
this.should_be_connected = false; this.should_be_connected = false;
this.connected = ref(false); this.connected = ref(false);
this.enabled = ref(true);
this.on_telem_value = new Map(); this.on_telem_value = new Map();
} }
@@ -119,9 +122,9 @@ export class WebsocketHandle {
const is_live = computed(() => toValue(live)); const is_live = computed(() => toValue(live));
watch( watch(
[uuid, this.connected, minimum_separation, is_live], [uuid, this.connected, this.enabled, minimum_separation, is_live],
([uuid_value, connected, min_sep, live_value]) => { ([uuid_value, connected, enabled, min_sep, live_value]) => {
if (connected && uuid_value && live_value) { if (connected && enabled && uuid_value && live_value) {
this.websocket?.send( this.websocket?.send(
JSON.stringify({ JSON.stringify({
RegisterTlmListener: { RegisterTlmListener: {
@@ -160,6 +163,14 @@ export class WebsocketHandle {
return value_result; return value_result;
} }
pause() {
this.enabled.value = false;
}
resume() {
this.enabled.value = true;
}
} }
export const WEBSOCKET_SYMBOL = Symbol(); export const WEBSOCKET_SYMBOL = Symbol();
@@ -170,6 +181,13 @@ export function useWebsocket() {
onMounted(() => { onMounted(() => {
handle.value.connect(); handle.value.connect();
}); });
onDocumentVisibilityChange((visible) => {
if (visible) {
handle.value.resume();
} else {
handle.value.pause();
}
});
onUnmounted(() => { onUnmounted(() => {
handle.value.disconnect(); handle.value.disconnect();
}); });

View File

@@ -1,8 +1,8 @@
import type { RouteLocationRaw } from 'vue-router'; import type { RouteLocationRaw } from 'vue-router';
export enum PanelHeirarchyType { export enum PanelHeirarchyType {
LEAF, LEAF = 'leaf',
FOLDER, FOLDER = 'folder',
} }
export type PanelHeirarchyLeaf = { export type PanelHeirarchyLeaf = {
@@ -37,6 +37,12 @@ export function getPanelHeirarchy(): PanelHeirarchyChildren {
type: PanelHeirarchyType.LEAF, type: PanelHeirarchyType.LEAF,
}); });
result.push({
name: 'Chart',
to: { name: 'chart' },
type: PanelHeirarchyType.LEAF,
});
return result; return result;
} }

View File

@@ -18,6 +18,11 @@ const router = createRouter({
name: 'list', name: 'list',
component: () => import('../views/TelemetryListView.vue'), component: () => import('../views/TelemetryListView.vue'),
}, },
{
path: '/chart',
name: 'chart',
component: () => import('../views/ChartView.vue'),
},
], ],
}); });

View File

@@ -0,0 +1,309 @@
<script setup lang="ts">
import FlexDivider from '@/components/FlexDivider.vue';
import TelemetryList from '@/components/TelemetryList.vue';
import { onMounted, ref } from 'vue';
import TextInput from '@/components/TextInput.vue';
import type { TelemetryDefinition } from '@/composables/telemetry.ts';
import InlineIcon from '@/components/InlineIcon.vue';
const model = defineModel<TelemetryDefinition[][][]>({
required: true,
default: [],
});
const selected = ref(0);
const selected_cell_x = ref(0);
const selected_cell_y = ref(0);
const searchValue = ref('');
const options = [
[1, 1],
[2, 1],
[1, 2],
[2, 2],
[3, 2],
];
function selectOption(i: number) {
selected.value = i;
if (selected_cell_x.value >= options[i][0]) {
selected_cell_x.value = 0;
}
if (selected_cell_y.value >= options[i][1]) {
selected_cell_y.value = 0;
}
const initial_cells = model.value;
const result_cells: TelemetryDefinition[][][] = [];
for (let x = 0; x < options[i][0]; x++) {
result_cells.push([]);
for (let y = 0; y < options[i][1]; y++) {
if (x < initial_cells.length && y < initial_cells[x].length) {
result_cells[x].push(initial_cells[x][y]);
} else {
result_cells[x].push([]);
}
}
}
model.value = result_cells;
}
onMounted(() => {
const model_value = model.value;
let s = 0;
for (let i = 0; i < options.length; i++) {
// X length correct
if (options[i][0] == model_value.length) {
// Y length correct
if (options[i][1] == model_value[0].length) {
// selectOption(i);
s = i;
break;
}
}
}
// Fall back to option 0
selectOption(s);
});
function selectCell(x: number, y: number) {
selected_cell_x.value = x;
selected_cell_y.value = y;
}
function selectTelemetry(telemetry: TelemetryDefinition | null) {
if (telemetry != null) {
if (
!model.value[selected_cell_x.value][selected_cell_y.value].includes(
telemetry,
)
) {
model.value[selected_cell_x.value][selected_cell_y.value].push(
telemetry,
);
}
}
}
function moveUp(index: number) {
if (index > 0) {
const removed = model.value[selected_cell_x.value][
selected_cell_y.value
].splice(index - 1, 2);
removed.reverse();
model.value[selected_cell_x.value][selected_cell_y.value].splice(
index - 1,
0,
...removed,
);
}
}
function moveDown(index: number) {
if (
index + 1 <
model.value[selected_cell_x.value][selected_cell_y.value].length
) {
const removed = model.value[selected_cell_x.value][
selected_cell_y.value
].splice(index, 2);
removed.reverse();
model.value[selected_cell_x.value][selected_cell_y.value].splice(
index,
0,
...removed,
);
}
}
function removeTelemetry(index: number) {
model.value[selected_cell_x.value][selected_cell_y.value].splice(index, 1);
}
</script>
<template>
<div class="row grow stretch">
<div class="column grow stretch no-basis">
<div class="grow no-basis type_grid no-min-height scroll">
<svg
v-for="(option, i) in options"
:key="i"
:class="`layout ${selected == i ? 'selected' : ''}`"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 100 100"
@click="selectOption(i)"
>
<template v-for="x in option[0]" :key="x">
<template v-for="y in option[1]" :key="y">
<rect
:x="1 + ((x - 1) * 98) / option[0]"
:y="1 + ((y - 1) * 98) / option[1]"
:width="98 / option[0]"
:height="98 / option[1]"
></rect>
</template>
</template>
</svg>
</div>
<FlexDivider class="horizontal_divider_margin"></FlexDivider>
<div class="row grow center no-basis">
<div
class="selected_grid grow"
:style="`grid-template: repeat(${options[selected][1]}, 1fr) / repeat(${options[selected][0]}, 1fr);`"
>
<template v-for="y in options[selected][1]" :key="y">
<template v-for="x in options[selected][0]" :key="x">
<div
:class="`cell ${selected_cell_x == x - 1 && selected_cell_y == y - 1 ? 'selected' : ''}`"
@click="selectCell(x - 1, y - 1)"
></div>
</template>
</template>
</div>
</div>
</div>
<FlexDivider></FlexDivider>
<div class="column grow2 stretch no-basis">
<div class="row grow stretch no-basis">
<div class="column grow stretch">
<template v-if="model.length > 0">
<div
class="row chosen"
v-for="(selected, i) in model[selected_cell_x][
selected_cell_y
]"
:key="i"
>
<span>
{{ selected.name }}
</span>
<span class="grow"></span>
<div class="column tiny_text">
<InlineIcon
icon="up arrow"
:class="`${i == 0 ? 'hidden' : 'button'}`"
@click="moveUp(i)"
></InlineIcon>
<InlineIcon
icon="down arrow"
:class="`${i == model[selected_cell_x][selected_cell_y].length - 1 ? 'hidden' : 'button'}`"
@click="moveDown(i)"
></InlineIcon>
</div>
<span
class="close icon button"
@click="removeTelemetry(i)"
>
<InlineIcon icon="close"></InlineIcon>
</span>
</div>
</template>
</div>
</div>
<FlexDivider class="horizontal_divider_margin"></FlexDivider>
<div class="row grow stretch no-basis">
<div class="column grow stretch no-basis">
<div class="row">
<TextInput
autofocus
class="grow"
v-model="searchValue"
placeholder="Search"
></TextInput>
</div>
<div class="row scroll grow no-min-height no-basis">
<div class="column grow stretch">
<TelemetryList
:search="searchValue"
:model-value="null"
@update:model-value="
(value) => selectTelemetry(value)
"
></TelemetryList>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
@use '@/assets/variables';
.type_grid {
display: grid;
grid-auto-flow: row;
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
grid-auto-rows: max-content;
gap: 1em;
justify-items: stretch;
align-items: stretch;
justify-content: center;
align-content: center;
}
.type_grid > * {
aspect-ratio: 1 / 1;
}
.layout {
stroke: variables.$grid-line;
stroke-width: 1px;
fill: none;
cursor: pointer;
}
.selected {
background-color: variables.$light-background-color;
}
.selected_grid {
display: grid;
gap: 1em;
justify-items: stretch;
align-self: stretch;
justify-content: stretch;
align-content: stretch;
}
.cell {
border: variables.$grid-line 1px solid;
cursor: pointer;
}
.horizontal_divider_margin {
margin: 1em 0;
}
.chosen {
padding: 0.3em;
border: 0;
border-bottom: variables.$gray-3 solid 1px;
border-top: 0;
align-items: center;
}
.chosen:first-child {
border-top: variables.$gray-3 solid 1px;
}
.chosen:hover {
background-color: variables.$light2-background-color;
}
.close.icon.button {
height: 1em;
cursor: pointer;
}
.arrow.icon.button {
cursor: pointer;
}
.column.tiny_text {
font-size: variables.$normal-text-size / 2;
height: 100%;
justify-content: space-between;
}
</style>

View File

@@ -0,0 +1,51 @@
<script setup lang="ts">
import type { TelemetryDefinition } from '@/composables/telemetry.ts';
import SvgGraph from '@/components/SvgGraph.vue';
import { GraphSide } from '@/graph/graph.ts';
import GraphAxis from '@/components/GraphAxis.vue';
import TelemetryLine from '@/components/TelemetryLine.vue';
defineProps<{
charts: TelemetryDefinition[][][];
}>();
</script>
<template>
<div
class="chart grid grow"
:style="`grid-template: repeat(${charts[0].length} , 1fr) / repeat(${charts.length}, 1fr);`"
v-if="charts.length > 0"
>
<template v-for="y in charts[0].length" :key="y">
<template v-for="x in charts.length" :key="x">
<div class="no-min-height">
<SvgGraph
right_axis
cursor
:legend="GraphSide.Left"
include_controls
>
<GraphAxis>
<TelemetryLine
v-for="tlm in charts[x - 1][y - 1]"
:key="tlm.uuid"
:data="tlm.name"
></TelemetryLine>
</GraphAxis>
</SvgGraph>
</div>
</template>
</template>
</div>
</template>
<style scoped lang="scss">
@use '@/assets/variables';
.chart.grid {
display: grid;
grid-auto-flow: row;
place-content: stretch;
height: 100%;
}
</style>

View File

@@ -0,0 +1,50 @@
<script setup lang="ts">
import { ref } from 'vue';
import InlineIcon from '@/components/InlineIcon.vue';
import ChartDefinitionView from '@/views/ChartDefinitionView.vue';
import type { TelemetryDefinition } from '@/composables/telemetry.ts';
import ChartRenderView from '@/views/ChartRenderView.vue';
const settingsOpen = ref(true);
const settings = ref<TelemetryDefinition[][][]>([]);
function openSettings() {
settingsOpen.value = true;
}
function closeSettings() {
settingsOpen.value = false;
}
</script>
<template>
<div class="column stretch screen content full center divider">
<template v-if="!settingsOpen">
<div class="settings column center" @click="openSettings">
<InlineIcon icon="hamburger menu"></InlineIcon>
</div>
<ChartRenderView :charts="settings"></ChartRenderView>
</template>
<template v-else>
<div class="settings column center" @click="closeSettings">
<InlineIcon icon="close"></InlineIcon>
</div>
<ChartDefinitionView v-model="settings"></ChartDefinitionView>
</template>
</div>
</template>
<style scoped lang="scss">
@use '@/assets/variables';
.settings {
position: absolute;
top: 0;
right: 0;
background-color: variables.$dark-background-color;
border: 1px solid variables.$light2-background-color;
padding: 0.5em;
margin: 1px;
aspect-ratio: 1 / 1;
cursor: pointer;
}
</style>

View File

@@ -8,9 +8,8 @@ import FlexDivider from '@/components/FlexDivider.vue';
const searchValue = ref(''); const searchValue = ref('');
const selectValue = ref< const selected = ref<TelemetryDefinition | null>(null);
[TelemetryDefinition | null, TelemetryDefinition | null] const mousedover = ref<TelemetryDefinition | null>(null);
>([null, null]);
</script> </script>
<template> <template>
@@ -29,7 +28,11 @@ const selectValue = ref<
<div class="column grow stretch"> <div class="column grow stretch">
<TelemetryList <TelemetryList
:search="searchValue" :search="searchValue"
@select="(selection) => (selectValue = selection)" v-model="selected"
@mouseover="
(mousedover_value) =>
(mousedover = mousedover_value)
"
></TelemetryList> ></TelemetryList>
</div> </div>
</div> </div>
@@ -37,8 +40,8 @@ const selectValue = ref<
<FlexDivider></FlexDivider> <FlexDivider></FlexDivider>
<div class="column grow stretch no-basis"> <div class="column grow stretch no-basis">
<TelemetryInfo <TelemetryInfo
:telemetry_definition="selectValue[0]" :mouseover="mousedover"
:secondary="selectValue[1]" :selection="selected"
></TelemetryInfo> ></TelemetryInfo>
</div> </div>
</div> </div>