Skip to content

Commit 53b72f1

Browse files
committed
⚡️Controles
1 parent 500e915 commit 53b72f1

File tree

6 files changed

+1024
-464
lines changed

6 files changed

+1024
-464
lines changed

package.json

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "enflujo-control-camarapi",
3-
"packageManager": "yarn@4.9.2",
3+
"packageManager": "yarn@4.11.0",
44
"version": "0.0.2",
55
"author": "Laboratorio EnFlujo <[email protected]>",
66
"license": "MIT",
@@ -13,17 +13,17 @@
1313
"lint:fix": "prettier --write --list-different ."
1414
},
1515
"dependencies": {
16-
"vue": "^3.5.17"
16+
"vue": "^3.5.24"
1717
},
1818
"devDependencies": {
19-
"@vitejs/plugin-vue": "^5.2.4",
20-
"autoprefixer": "^10.4.21",
21-
"cssnano": "^7.0.7",
19+
"@vitejs/plugin-vue": "^6.0.2",
20+
"autoprefixer": "^10.4.22",
21+
"cssnano": "^7.1.2",
2222
"postcss": "^8.5.6",
23-
"prettier": "^3.6.0",
24-
"sass": "^1.89.2",
25-
"typescript": "^5.8.3",
26-
"vite": "^6.3.5",
27-
"vue-tsc": "^2.2.10"
23+
"prettier": "^3.6.2",
24+
"sass": "^1.94.1",
25+
"typescript": "^5.9.3",
26+
"vite": "^7.2.2",
27+
"vue-tsc": "^3.1.4"
2828
}
2929
}

src/Aplicacion.vue

Lines changed: 224 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,91 +1,251 @@
11
<script setup lang="ts">
2-
import { ref, onMounted } from 'vue';
2+
import { ref, onMounted, computed } from 'vue';
33
import { pedirDatos } from './utilidades/ayudas';
4+
import RecorteImagen from './componentes/RecorteImagen.vue';
5+
import { diccionarioEs } from './utilidades/diccionario';
6+
import { gruposControles } from './utilidades/controles';
7+
48
interface OpcionCamara {
59
nombre: string;
610
predeterminado: number | number[] | boolean | null;
7-
max: number | null;
8-
min: number | null;
9-
paso: number | null;
10-
tipo: string;
11+
max: number | number[] | null;
12+
min: number | number[] | null;
1113
}
14+
15+
// Estado principal
1216
const imagen = ref('');
13-
const listaOpcionesCamara = ref<OpcionCamara[] | null>(null);
17+
const listaOpcionesCamara = ref<OpcionCamara[]>([]);
18+
const valoresActuales = ref<Record<string, any>>({});
19+
const controlesAgrupados = computed(() => {
20+
const map: Record<string, OpcionCamara[]> = {};
21+
22+
for (const op of listaOpcionesCamara.value) {
23+
const grupo = Object.entries(gruposControles).find(([n, arr]) => arr.includes(op.nombre))?.[0] || 'Otros';
24+
25+
if (!map[grupo]) map[grupo] = [];
26+
map[grupo].push(op);
27+
}
28+
29+
return map;
30+
});
31+
32+
// ──────────────────────────────────────────
33+
// WEBSOCKET DE VIDEO
34+
// ──────────────────────────────────────────
1435
1536
onMounted(async () => {
1637
const ws = new WebSocket(`ws://enflujo9.local:8000/ws`);
1738
ws.onmessage = (evento) => {
1839
imagen.value = 'data:image/jpeg;base64,' + evento.data;
1940
};
2041
42+
await cargarControles();
43+
});
44+
45+
// ──────────────────────────────────────────
46+
// CARGAR CONTROLES DESDE EL BACKEND
47+
// ──────────────────────────────────────────
48+
49+
async function cargarControles() {
2150
try {
22-
const datosCamara = await pedirDatos<OpcionCamara[]>('http://enflujo9.local:8000/controles');
23-
console.log('Datos de cámara recibidos:', datosCamara);
24-
if (datosCamara && typeof datosCamara === 'object') {
25-
const opcionesCamaraOrdenados = Object.entries(datosCamara).sort((a, b) => a[0].localeCompare(b[0]));
26-
listaOpcionesCamara.value = opcionesCamaraOrdenados.map(([nombre, opcion]) => ({
27-
nombre,
28-
predeterminado: opcion.predeterminado ?? null,
29-
max: opcion.max ?? null,
30-
min: opcion.min ?? null,
31-
paso: opcion.paso ?? null,
32-
tipo: opcion.tipo ?? '??',
33-
}));
34-
} else {
35-
console.error('Datos de cámara no válidos:', datosCamara);
36-
}
51+
const datos = await pedirDatos<Record<string, any>>('http://enflujo9.local:8000/controles');
52+
53+
const lista = Object.entries(datos).map(([nombre, opcion]) => ({
54+
nombre,
55+
predeterminado: opcion.predeterminado,
56+
min: opcion.min,
57+
max: opcion.max,
58+
}));
59+
60+
// Ordenar alfabéticamente
61+
lista.sort((a, b) => a.nombre.localeCompare(b.nombre));
62+
63+
// 1. Filtrar los que no queremos acá
64+
const sinScaler = lista.filter((op) => op.nombre !== 'ScalerCrop');
65+
66+
// 2. Ordenar según el orden de grupos
67+
listaOpcionesCamara.value = sinScaler.sort((a, b) => {
68+
const getGrupo = (nombre: string) =>
69+
Object.entries(gruposControles).find(([_, items]) => items.includes(nombre))?.[0] || 'Z_otros';
70+
71+
const ga = getGrupo(a.nombre);
72+
const gb = getGrupo(b.nombre);
73+
74+
if (ga === gb) return a.nombre.localeCompare(b.nombre);
75+
76+
return ga.localeCompare(gb);
77+
});
78+
79+
// Estado actual editable
80+
lista.forEach((op) => {
81+
valoresActuales.value[op.nombre] = Array.isArray(op.predeterminado) ? [...op.predeterminado] : op.predeterminado;
82+
});
3783
} catch (error) {
38-
console.error('Error al obtener los datos de la cámara:', error);
84+
console.error('Error cargando controles:', error);
3985
}
40-
});
86+
}
87+
88+
async function enviarControl(nombre: string) {
89+
const valor = valoresActuales.value[nombre];
90+
91+
const res = await fetch('http://enflujo9.local:8000/controlar', {
92+
method: 'POST',
93+
headers: { 'Content-Type': 'application/json' },
94+
body: JSON.stringify({ nombre, valor }),
95+
});
96+
97+
const data = await res.json();
98+
console.log('Control cambiado:', data);
99+
}
100+
101+
async function restaurarPredeterminados() {
102+
const payload: Record<string, any> = {};
103+
104+
listaOpcionesCamara.value.forEach((op) => {
105+
// Saltar controles que no tienen predeterminado válido
106+
if (op.predeterminado === null || op.predeterminado === undefined) {
107+
return;
108+
}
109+
110+
payload[op.nombre] = Array.isArray(op.predeterminado) ? [...op.predeterminado] : op.predeterminado;
111+
});
112+
113+
console.log('Restaurando:', payload);
114+
115+
const res = await fetch('http://enflujo9.local:8000/controlar-multiples', {
116+
method: 'POST',
117+
headers: { 'Content-Type': 'application/json' },
118+
body: JSON.stringify(payload),
119+
});
120+
121+
const data = await res.json();
122+
console.log('Restaurado:', data);
123+
124+
// Actualizar la UI solo con controles válidos
125+
Object.entries(payload).forEach(([nombre, valor]) => {
126+
valoresActuales.value[nombre] = Array.isArray(valor) ? [...valor] : valor;
127+
});
128+
}
129+
130+
function descripcionDe(nombre: string): string {
131+
return diccionarioEs[nombre] ?? 'Sin descripción disponible';
132+
}
41133
</script>
42134

43135
<template>
44-
<img :src="imagen" alt="Cámara" />
45-
<table id="opcionesCamara">
46-
<thead>
47-
<tr>
48-
<th>Nombre</th>
49-
<th>Tipo</th>
50-
<th>Valor predeterminado</th>
51-
<th>Mínimo</th>
52-
<th>Máximo</th>
53-
<th>Paso</th>
54-
</tr>
55-
</thead>
56-
<tbody class="opciones">
57-
<tr v-for="obj in listaOpcionesCamara" :key="obj.nombre">
58-
<td class="nombre">{{ obj.nombre }}</td>
59-
<td class="tipo">{{ obj.tipo }}</td>
60-
<td class="valor">{{ obj.predeterminado }}</td>
61-
<td>{{ obj.min }}</td>
62-
<td>{{ obj.max }}</td>
63-
<td>{{ obj.paso }}</td>
64-
</tr>
65-
</tbody>
66-
</table>
136+
<div class="contenedor">
137+
<img class="preview" :src="imagen" alt="Cámara" />
138+
<RecorteImagen />
139+
<button @click="restaurarPredeterminados">Restaurar predeterminados</button>
140+
141+
<div class="panel">
142+
<h2>Controles de cámara</h2>
143+
144+
<div v-for="(items, grupo) in controlesAgrupados" :key="grupo" class="grupo">
145+
<h2>{{ grupo }}</h2>
146+
<div v-for="op in items" :key="op.nombre" class="control">
147+
<div class="nombre">
148+
<h3>{{ op.nombre }}</h3>
149+
<span class="descripccion">
150+
{{ descripcionDe(op.nombre) }}
151+
</span>
152+
</div>
153+
154+
<!-- BOOLEANOS -->
155+
<template v-if="typeof op.predeterminado === 'boolean'">
156+
<label>
157+
<input type="checkbox" v-model="valoresActuales[op.nombre]" @change="enviarControl(op.nombre)" />
158+
{{ valoresActuales[op.nombre] ? 'On' : 'Off' }}
159+
</label>
160+
</template>
161+
162+
<!-- NÚMEROS -->
163+
<template v-else-if="typeof op.predeterminado === 'number' || typeof valoresActuales[op.nombre] === 'number'">
164+
<input
165+
type="range"
166+
:min="typeof op.min === 'number' ? op.min : undefined"
167+
:max="typeof op.max === 'number' ? op.max : undefined"
168+
step="0.1"
169+
v-model.number="valoresActuales[op.nombre]"
170+
@input="enviarControl(op.nombre)"
171+
/>
172+
<input
173+
class="num"
174+
type="number"
175+
:min="typeof op.min === 'number' ? op.min : undefined"
176+
:max="typeof op.max === 'number' ? op.max : undefined"
177+
step="0.1"
178+
v-model.number="valoresActuales[op.nombre]"
179+
@change="enviarControl(op.nombre)"
180+
/>
181+
</template>
182+
183+
<!-- LISTAS / TUPLAS -->
184+
<template v-else-if="Array.isArray(op.predeterminado)">
185+
<div class="lista">
186+
<div class="item" v-for="(_, i) in valoresActuales[op.nombre]" :key="i">
187+
<input
188+
type="number"
189+
step="0.1"
190+
v-model.number="valoresActuales[op.nombre][i]"
191+
@change="enviarControl(op.nombre)"
192+
/>
193+
</div>
194+
</div>
195+
</template>
196+
197+
<!-- FALLBACK -->
198+
<template v-else>
199+
<span>No editable</span>
200+
</template>
201+
</div>
202+
</div>
203+
</div>
204+
</div>
67205
</template>
68206

69-
<style lang="scss" scoped>
70-
#opcionesCamara {
71-
list-style-type: none;
72-
padding: 0;
73-
margin: 0;
207+
<style scoped>
208+
.contenedor {
209+
display: flex;
210+
gap: 20px;
211+
}
74212
75-
.opciones {
76-
.tipo {
77-
font-style: italic;
78-
color: #555;
79-
}
213+
.preview {
214+
width: 640px;
215+
height: 480px;
216+
background: black;
217+
}
80218
81-
.nombre {
82-
font-weight: bold;
83-
color: #000;
84-
}
219+
.panel {
220+
max-height: 480px;
221+
overflow-y: auto;
222+
padding-right: 10px;
223+
}
85224
86-
.valor {
87-
color: #007bff;
88-
}
89-
}
225+
.control {
226+
margin-bottom: 15px;
227+
padding: 8px;
228+
border-bottom: 1px solid #ddd;
229+
}
230+
231+
.nombre h3 {
232+
margin-bottom: 0;
233+
}
234+
235+
.descripccion {
236+
font-weight: normal;
237+
font-size: 0.9em;
238+
color: #555;
239+
font-style: italic;
240+
margin-bottom: 2em;
241+
}
242+
243+
.lista {
244+
display: flex;
245+
gap: 5px;
246+
}
247+
248+
.num {
249+
width: 70px;
90250
}
91251
</style>

0 commit comments

Comments
 (0)