Files
telemetry_visualization/frontend/src/components/DynamicComponent.vue

353 lines
10 KiB
Vue

<script setup lang="ts">
import {
AnyTypes,
type CommandParameterData,
type DynamicDataType,
type OptionalDynamicComponentData,
} from '@/composables/dynamic.ts';
import { computed, defineAsyncComponent, inject, type Ref, ref } from 'vue';
import CommandParameterListConfigurator from '@/components/CommandParameterListConfigurator.vue';
const TelemetryValue = defineAsyncComponent(
() => import('@/components/TelemetryValue.vue'),
);
const GridLayout = defineAsyncComponent(
() => import('@/components/layout/GridLayout.vue'),
);
const CommandInput = defineAsyncComponent(
() => import('@/components/CommandInput.vue'),
);
const model = defineModel<OptionalDynamicComponentData>('data', {
required: true,
});
const selection = defineModel<symbol>('selection');
const props = defineProps<{
editable: boolean;
}>();
const busy = ref(false);
// Provide a fallback option
const inputs = inject<Ref<{ [id: string]: DynamicDataType }>>(
'inputs',
ref({}),
);
const thisSymbol = Symbol();
const isSelected = computed(() => {
return selection.value == thisSymbol && props.editable;
});
function selectThis(e: Event) {
if (props.editable) {
// Only do this when we are editable
e.stopPropagation();
e.preventDefault();
}
selection.value = thisSymbol;
}
function deleteThis() {
model.value = {
type: 'none',
};
}
function makeText() {
model.value = {
type: 'text',
text: '',
justify_right: false,
};
}
function makeTelemetry() {
model.value = {
type: 'telemetry',
data: '',
};
}
function makeGrid() {
model.value = {
type: 'grid',
columns: 1,
equal_width: false,
cells: [],
};
}
function addRow() {
const grid = model.value;
if (grid.type == 'grid') {
const row: OptionalDynamicComponentData[] = [];
for (let i = 0; i < grid.columns; i++) {
row.push({ type: 'none' });
}
grid.cells.push(row);
model.value = grid;
}
}
function deleteRow() {
const grid = model.value;
if (grid.type == 'grid') {
grid.cells.pop();
model.value = grid;
}
}
function addColumn() {
const grid = model.value;
if (grid.type == 'grid') {
for (let i = 0; i < grid.cells.length; i++) {
grid.cells[i].push({ type: 'none' });
}
grid.columns += 1;
model.value = grid;
}
}
function deleteColumn() {
const grid = model.value;
if (grid.type == 'grid') {
for (let i = 0; i < grid.cells.length; i++) {
grid.cells[i].pop();
}
grid.columns -= 1;
model.value = grid;
}
}
function makeInput() {
model.value = {
type: 'input',
id: [...Array(32)]
.map(() => Math.floor(Math.random() * 16).toString(16))
.join(''),
data_type: 'Float32',
};
}
function makeCommandButton() {
model.value = {
type: 'command_button',
text: 'Button Text',
command_name: '',
parameters: {},
};
}
async function sendCommand(command: {
command_name: string;
parameters: { [key: string]: CommandParameterData };
}) {
busy.value = true;
const params: { [key: string]: DynamicDataType } = {};
for (const param_name in command.parameters) {
const parameter = command.parameters[param_name];
switch (parameter.type) {
case 'constant':
params[param_name] = parameter.value;
break;
case 'input':
params[param_name] = inputs.value[parameter.id];
break;
}
}
await fetch(`/api/cmd/${command.command_name}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(params),
});
busy.value = false;
}
</script>
<template>
<Teleport v-if="isSelected" to="#inspector">
<div class="row">
<button
v-if="model.type != 'none'"
@click.stop.prevent="deleteThis"
>
Delete
</button>
<button v-if="model.type != 'text'" @click.stop.prevent="makeText">
Make Text
</button>
<button
v-if="model.type != 'telemetry'"
@click.stop.prevent="makeTelemetry"
>
Make Telemetry
</button>
<button v-if="model.type != 'grid'" @click.stop.prevent="makeGrid">
Make Grid
</button>
<button
v-if="model.type != 'input'"
@click.stop.prevent="makeInput"
>
Make Input
</button>
<button
v-if="model.type != 'command_button'"
@click.stop.prevent="makeCommandButton"
>
Make Command Button
</button>
</div>
</Teleport>
<template v-if="model.type == 'none'">
<span
:class="`${editable ? 'editable' : ''} ${isSelected ? 'selected' : ''}`"
@click="selectThis"
></span>
</template>
<template v-else-if="model.type == 'text'">
<span
:class="`${model.justify_right ? 'justify-right' : ''} ${editable ? 'editable' : ''} ${isSelected ? 'selected' : ''}`"
@click="selectThis"
>
{{ model.text }}
</span>
<Teleport v-if="isSelected" to="#inspector">
<div class="row">
<label>Text: </label>
<input v-model="model.text" />
</div>
<div class="row">
<label>Justify Right: </label>
<input type="checkbox" v-model="model.justify_right" />
</div>
</Teleport>
</template>
<template v-else-if="model.type == 'telemetry'">
<span
v-if="editable"
:class="`${editable ? 'editable' : ''} ${isSelected ? 'selected' : ''}`"
@click="selectThis"
>
{{ '{' }} {{ model.data }} {{ '}' }}
</span>
<TelemetryValue
v-else
:class="`${editable ? 'editable' : ''} ${isSelected ? 'selected' : ''}`"
:data="model.data"
@click="selectThis"
></TelemetryValue>
<Teleport v-if="isSelected" to="#inspector">
<label>Telemetry Item: </label>
<input v-model="model.data" />
</Teleport>
</template>
<template v-else-if="model.type == 'grid'">
<GridLayout
:class="`${editable ? 'editable' : ''} ${isSelected ? 'selected' : ''}`"
:cols="model.columns"
:equal_col_width="model.equal_width"
@click="selectThis"
>
<template v-for="x in model.cells.length" :key="x">
<template v-for="y in model.columns" :key="y">
<DynamicComponent
v-model:data="model.cells[x - 1][y - 1]"
:editable="editable"
v-model:selection="selection"
></DynamicComponent>
</template>
</template>
</GridLayout>
<Teleport v-if="isSelected" to="#inspector">
<div class="row">
<label>Equal Width: </label>
<input type="checkbox" v-model="model.equal_width" />
</div>
<div class="row">
<button @click.stop.prevent="addRow">Add Row</button>
<button
:disabled="model.cells.length <= 0"
@click.stop.prevent="deleteRow"
>
Delete Row
</button>
<button @click.stop.prevent="addColumn">Add Column</button>
<button
:disabled="model.columns <= 0"
@click.stop.prevent="deleteColumn"
>
Delete Column
</button>
</div>
</Teleport>
</template>
<template v-else-if="model.type == 'input'">
<span
v-if="editable"
:class="`${editable ? 'editable' : ''} ${isSelected ? 'selected' : ''}`"
@click="selectThis"
>
{{ '[' }} {{ model.id }} {{ ']' }}
</span>
<CommandInput
v-else
:type="model.data_type"
v-model="inputs[model.id]"
></CommandInput>
<Teleport v-if="isSelected" to="#inspector">
<div class="row">
<label>Input ID: </label>
<input v-model="model.id" />
</div>
<div class="row">
<label>Data Type: </label>
<select v-model="model.data_type">
<option v-for="type in AnyTypes" :key="type" :value="type">
{{ type }}
</option>
</select>
</div>
</Teleport>
</template>
<template v-else-if="model.type == 'command_button'">
<button
:disabled="busy"
:class="`${editable ? 'editable' : ''} ${isSelected ? 'selected' : ''}`"
@click.stop.prevent="
(e) => (editable ? selectThis(e) : sendCommand(model as any))
"
>
{{ model.text }}
</button>
<Teleport v-if="isSelected" to="#inspector">
<div class="row">
<label>Button Text: </label>
<input v-model="model.text" />
</div>
<div class="row">
<label>Command: </label>
<input v-model="model.command_name" />
</div>
<CommandParameterListConfigurator
:key="model.command_name"
:command_name="model.command_name"
v-model="model.parameters"
></CommandParameterListConfigurator>
</Teleport>
</template>
<template v-else> ERROR: Unknown data: {{ model }} </template>
</template>
<style scoped lang="scss">
@use '@/assets/variables';
</style>