adds charts panel
This commit is contained in:
309
frontend/src/views/ChartDefinitionView.vue
Normal file
309
frontend/src/views/ChartDefinitionView.vue
Normal 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>
|
||||
51
frontend/src/views/ChartRenderView.vue
Normal file
51
frontend/src/views/ChartRenderView.vue
Normal 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>
|
||||
50
frontend/src/views/ChartView.vue
Normal file
50
frontend/src/views/ChartView.vue
Normal 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>
|
||||
@@ -8,9 +8,8 @@ import FlexDivider from '@/components/FlexDivider.vue';
|
||||
|
||||
const searchValue = ref('');
|
||||
|
||||
const selectValue = ref<
|
||||
[TelemetryDefinition | null, TelemetryDefinition | null]
|
||||
>([null, null]);
|
||||
const selected = ref<TelemetryDefinition | null>(null);
|
||||
const mousedover = ref<TelemetryDefinition | null>(null);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -29,7 +28,11 @@ const selectValue = ref<
|
||||
<div class="column grow stretch">
|
||||
<TelemetryList
|
||||
:search="searchValue"
|
||||
@select="(selection) => (selectValue = selection)"
|
||||
v-model="selected"
|
||||
@mouseover="
|
||||
(mousedover_value) =>
|
||||
(mousedover = mousedover_value)
|
||||
"
|
||||
></TelemetryList>
|
||||
</div>
|
||||
</div>
|
||||
@@ -37,8 +40,8 @@ const selectValue = ref<
|
||||
<FlexDivider></FlexDivider>
|
||||
<div class="column grow stretch no-basis">
|
||||
<TelemetryInfo
|
||||
:telemetry_definition="selectValue[0]"
|
||||
:secondary="selectValue[1]"
|
||||
:mouseover="mousedover"
|
||||
:selection="selected"
|
||||
></TelemetryInfo>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user