feat(ui): render the CCTV calibration readout and its pose editor from the store

presentCctvCalibration() joins the panel view: ADJUST, SAVE CAL, RESET CAL
and the seven pose chips render from the gev-cctv store. The inline editor
is store state: starting an edit publishes its field, starting value and
finish(), and the CctvCalValue chip renders the number input. The draft is
set once on mount so a refresh while typing keeps it; each edit gets its
own input. Commit and cancel rules, the captured base pose and the camera
guards stay with CctvControls.

formField.js had no other user and is removed.
This commit is contained in:
Sameh Khamis committed 2026-10-30 21:45:24 -07:00
1 parent 5b8cb40fce
commit 6c7035f617
13 files changed
+337 -240

No files matched your search

-96
View File
@@ -95,102 +95,6 @@ test('destroy invalidates image callbacks and releases each subscription once',
assert.equal(requests.length, 1);
});
function calibrationFixture(t) {
const { controls } = fixture(t);
const prior = globalThis.document;
const inputs = [];
globalThis.document = {
createElement() {
const input = new EventTarget();
Object.assign(input, {
focus() {},
select() {},
setAttribute(name, value) {
this[name] = value;
},
remove() {
this.parent.input = null;
},
});
inputs.push(input);
return input;
},
};
t.after(() => {
globalThis.document = prior;
});
const chip = {
dataset: { calField: 'heading' },
textContent: '',
input: null,
appendChild(input) {
this.input = input;
input.parent = this;
},
querySelector() {
return this.input;
},
};
const patches = [];
controls.actions.setParams = (params) => patches.push(params);
controls.actions.setPanelCollapsed = () => {};
controls._cctvCalReadout = { querySelectorAll: () => [chip] };
controls._cctvState = {
enabled: true,
activeCameraId: 'a',
activeCamera: { id: 'a', headingDeg: 30, basePose: { headingDeg: 20 } },
};
const key = (name) =>
Object.assign(new Event('keydown', { cancelable: true }), { key: name });
return { controls, chip, inputs, patches, key };
}
test('calibration commits against the captured camera base and releases its editor', (t) => {
const { controls, chip, inputs, patches, key } = calibrationFixture(t);
controls._beginCctvCalValueEdit(chip);
inputs[0].value = '100';
controls._cctvState.activeCamera.basePose.headingDeg = 60;
inputs[0].dispatchEvent(key('Enter'));
inputs[0].dispatchEvent(new Event('blur'));
assert.deepEqual(patches, [
{
selectedCameraId: 'a',
calibration: { cameraId: 'a', patch: { headingDeg: 80 } },
},
]);
assert.equal(controls._calibrationEdit, null);
});
test('a camera switch cancels calibration before old blur can change the new camera', (t) => {
const { controls, chip, inputs, patches } = calibrationFixture(t);
controls._beginCctvCalValueEdit(chip);
inputs[0].value = '100';
controls._renderCctvState({
enabled: true,
activeCameraId: 'b',
activeCamera: { id: 'b', headingDeg: 200, basePose: { headingDeg: 190 } },
});
inputs[0].dispatchEvent(new Event('blur'));
assert.deepEqual(patches, []);
assert.equal(chip.input, null);
assert.equal(chip.textContent, 'HDG 200.0°');
});
test('Escape claims calibration cancellation and disposal prevents late commits', (t) => {
const { controls, chip, inputs, patches, key } = calibrationFixture(t);
controls._beginCctvCalValueEdit(chip);
inputs[0].value = '100';
const escape = key('Escape');
inputs[0].dispatchEvent(escape);
inputs[0].dispatchEvent(new Event('blur'));
assert.equal(escape.defaultPrevented, true);
controls._beginCctvCalValueEdit(chip);
inputs[1].value = '120';
controls.destroy();
inputs[1].dispatchEvent(new Event('blur'));
assert.deepEqual(patches, []);
});
test('disposing during camera enable prevents the delayed focus and future clicks', async () => {
const button = new EventTarget();
let resolveEnable;