Skip to content

Commit c0976c8

Browse files
committed
fix interaction in collapsible steps
1 parent d034783 commit c0976c8

3 files changed

Lines changed: 75 additions & 23 deletions

File tree

Keypad.Flasher.Client/src/ch55xbl.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -669,6 +669,7 @@
669669
justify-content: space-between;
670670
gap: 8px;
671671
margin-bottom: 6px;
672+
cursor: pointer;
672673
}
673674

674675
.step-header-actions {

Keypad.Flasher.Client/src/ch55xbl.tsx

Lines changed: 74 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,12 @@ export default function CH55xBootloaderMinimal() {
382382
return generated;
383383
};
384384

385+
const cloneStepWithId = (prevStep: HidStepDto, nextStep: HidStepDto): HidStepDto => {
386+
const id = getStepId(prevStep);
387+
stepIdMap.current.set(nextStep, id);
388+
return nextStep;
389+
};
390+
385391
const resetModalClosePending = () => {
386392
modalClosePendingRef.current.clear();
387393
};
@@ -523,7 +529,7 @@ export default function CH55xBootloaderMinimal() {
523529
setEditSteps((prev) => prev.map((s, i) => {
524530
if (i !== capturingStepIndex) return s;
525531
if (s.kind !== "Key") return s;
526-
return { ...s, keycode: code };
532+
return cloneStepWithId(s, { ...s, keycode: code });
527533
}));
528534
setCapturingStepIndex(null);
529535
input?.blur();
@@ -908,6 +914,18 @@ export default function CH55xBootloaderMinimal() {
908914
highlightTimerRef.current = window.setTimeout(() => setHighlightedSteps([]), 450);
909915
};
910916

917+
const isInteractiveElement = (target: EventTarget | null): boolean => {
918+
if (!target || !(target as HTMLElement).closest) return false;
919+
const elem = target as HTMLElement;
920+
if (elem.closest("button, input, select, option, textarea, label")) return true;
921+
if (elem.closest(".drag-handle")) return true;
922+
return false;
923+
};
924+
925+
const toggleStepCollapse = (index: number) => {
926+
setActiveStepIndex((prev) => (prev === index ? null : index));
927+
};
928+
911929
const moveSteps = (sourceIndices: number[], targetIndex: number, afterMove?: (newIndices: number[], nextSteps: HidStepDto[]) => void, keepSelection: boolean = true) => {
912930
setEditSteps((prev) => {
913931
if (prev.length === 0) return prev;
@@ -1116,14 +1134,19 @@ export default function CH55xBootloaderMinimal() {
11161134
const toggleStepModifier = (index: number, bit: number) => {
11171135
setEditSteps((prev) => prev.map((s, i) => {
11181136
if (i !== index || s.kind !== "Key") return s;
1119-
return { ...s, modifiers: (s.modifiers & bit) !== 0 ? (s.modifiers & ~bit) : (s.modifiers | bit) };
1137+
const nextStep: HidStepDto = { ...s, modifiers: (s.modifiers & bit) !== 0 ? (s.modifiers & ~bit) : (s.modifiers | bit) };
1138+
return cloneStepWithId(s, nextStep);
11201139
}));
11211140
};
11221141

11231142
const updateFunctionValue = (index: number, value: string) => {
11241143
const parsed = Number(value);
11251144
const nextValue = Number.isFinite(parsed) && parsed > 0 ? parsed : 1;
1126-
setEditSteps((prev) => prev.map((s, i) => (i === index && s.kind === "Function" ? { ...s, functionValue: nextValue } : s)));
1145+
setEditSteps((prev) => prev.map((s, i) => {
1146+
if (i !== index || s.kind !== "Function") return s;
1147+
const nextStep: HidStepDto = { ...s, functionValue: nextValue };
1148+
return cloneStepWithId(s, nextStep);
1149+
}));
11271150
};
11281151

11291152
const updateStepTiming = (index: number, field: "holdMs" | "gapMs", value: string) => {
@@ -1132,16 +1155,16 @@ export default function CH55xBootloaderMinimal() {
11321155
setEditSteps((prev) => prev.map((s, i) => {
11331156
if (i !== index) return s;
11341157
if (s.kind === "Key") {
1135-
return { ...s, [field]: nextValue } as HidStepDto;
1158+
return cloneStepWithId(s, { ...s, [field]: nextValue } as HidStepDto);
11361159
}
11371160
if (field === "gapMs" && s.kind === "Pause") {
1138-
return { ...s, gapMs: nextValue };
1161+
return cloneStepWithId(s, { ...s, gapMs: nextValue });
11391162
}
11401163
if (field === "gapMs" && s.kind === "Function") {
1141-
return { ...s, gapMs: nextValue };
1164+
return cloneStepWithId(s, { ...s, gapMs: nextValue });
11421165
}
11431166
if (field === "gapMs" && s.kind === "Mouse") {
1144-
return { ...s, gapMs: nextValue };
1167+
return cloneStepWithId(s, { ...s, gapMs: nextValue });
11451168
}
11461169
return s;
11471170
}));
@@ -1154,24 +1177,24 @@ export default function CH55xBootloaderMinimal() {
11541177
const keycode = s.kind === "Key" ? s.keycode : 97;
11551178
const holdMs = s.kind === "Key" && s.holdMs > 0 ? s.holdMs : 10;
11561179
const gapMs = s.kind === "Key" && s.gapMs > 0 ? s.gapMs : 10;
1157-
return { kind: "Key", keycode, modifiers: s.kind === "Key" ? s.modifiers : 0, holdMs, gapMs };
1180+
return cloneStepWithId(s, { kind: "Key", keycode, modifiers: s.kind === "Key" ? s.modifiers : 0, holdMs, gapMs });
11581181
}
11591182
if (kind === "Pause") {
11601183
const gapMs = s.kind === "Key" || s.kind === "Function" || s.kind === "Mouse" ? (s.gapMs > 0 ? s.gapMs : 100) : s.gapMs;
1161-
return { kind: "Pause", gapMs: gapMs > 0 ? gapMs : 100 };
1184+
return cloneStepWithId(s, { kind: "Pause", gapMs: gapMs > 0 ? gapMs : 100 });
11621185
}
11631186
if (kind === "Mouse") {
11641187
const pointerType = s.kind === "Mouse" ? s.pointerType : HID_POINTER_TYPE.LeftClick;
11651188
const pointerValue = (pointerType === HID_POINTER_TYPE.LeftClick || pointerType === HID_POINTER_TYPE.RightClick)
11661189
? 0
11671190
: defaultMouseValue(pointerType);
11681191
const gapMs = s.kind === "Mouse" && s.gapMs >= 0 ? s.gapMs : 0;
1169-
return { kind: "Mouse", pointerType: pointerType as HidPointerType, pointerValue, gapMs };
1192+
return cloneStepWithId(s, { kind: "Mouse", pointerType: pointerType as HidPointerType, pointerValue, gapMs });
11701193
}
11711194
const gapMs = s.kind === "Function" && s.gapMs >= 0 ? s.gapMs : 0;
11721195
const functionPointer = s.kind === "Function" ? (s.functionPointer || DEFAULT_FUNCTION_POINTER) : DEFAULT_FUNCTION_POINTER;
11731196
const functionValue = FUNCTIONS_WITH_VALUE.has(functionPointer) && s.kind === "Function" && s.functionValue ? s.functionValue : 1;
1174-
return { kind: "Function", functionPointer, functionValue, gapMs };
1197+
return cloneStepWithId(s, { kind: "Function", functionPointer, functionValue, gapMs });
11751198
}));
11761199
if (kind !== "Key" && capturingStepIndex != null && capturingStepIndex === index) {
11771200
setCapturingStepIndex(null);
@@ -1632,7 +1655,7 @@ export default function CH55xBootloaderMinimal() {
16321655
: measuredHeight != null
16331656
? `${measuredHeight}px`
16341657
: (isFresh ? "0px" : "1200px");
1635-
const cardClasses = `step-card${kind === "Pause" ? " step-card-pause" : ""}${selected ? " step-card-selected" : ""}${draggingStepIndex === idx ? " step-card-dragging" : ""}${dragOverIndex === idx ? " step-card-drop-target" : ""}${inDragGroup ? " step-card-drag-group" : ""}${highlighted ? " step-card-highlight" : ""}${removing ? " step-card-removing" : ""}${collapsed ? " step-card-collapsed" : ""}${isFresh ? " step-card-fresh" : ""}`;
1658+
const cardClasses = `step-card${selected ? " step-card-selected" : ""}${draggingStepIndex === idx ? " step-card-dragging" : ""}${dragOverIndex === idx ? " step-card-drop-target" : ""}${inDragGroup ? " step-card-drag-group" : ""}${highlighted ? " step-card-highlight" : ""}${removing ? " step-card-removing" : ""}${collapsed ? " step-card-collapsed" : ""}${isFresh ? " step-card-fresh" : ""}`;
16361659
return (
16371660
<div
16381661
className={cardClasses}
@@ -1650,11 +1673,10 @@ export default function CH55xBootloaderMinimal() {
16501673
onDrop={(e) => handleDrop(e, idx)}
16511674
onDragLeave={() => { if (dragOverIndex === idx) setDragOverIndex(null); }}
16521675
onDragEnd={handleDragEnd}
1653-
onClick={() => setActiveStepIndex((prev) => (prev === idx ? null : idx))}
16541676
onAnimationEnd={(e) => handleCardAnimationEnd(stepKey, idx, e)}
16551677
aria-label={`Step ${idx + 1} ${kind}`}
16561678
>
1657-
<div className="step-header">
1679+
<div className="step-header" onClick={(e) => { if (!isInteractiveElement(e.target)) toggleStepCollapse(idx); }}>
16581680
<div className="step-header-left">
16591681
<span className="drag-handle" title="Drag to reorder">::</span>
16601682
<label className="checkbox step-select">
@@ -1685,10 +1707,30 @@ export default function CH55xBootloaderMinimal() {
16851707
style={{ maxHeight: bodyMaxHeight }}
16861708
>
16871709
<div className="step-kind-toggle">
1688-
<button className={`btn ghost${kind === "Key" ? " active" : ""}`} onClick={() => setStepKind(idx, "Key")}>Key</button>
1689-
<button className={`btn ghost${kind === "Pause" ? " active" : ""}`} onClick={() => setStepKind(idx, "Pause")}>Pause</button>
1690-
<button className={`btn ghost${kind === "Mouse" ? " active" : ""}`} onClick={() => setStepKind(idx, "Mouse")}>Mouse</button>
1691-
<button className={`btn ghost${kind === "Function" ? " active" : ""}`} onClick={() => setStepKind(idx, "Function")}>Function</button>
1710+
<button
1711+
className={`btn ghost${kind === "Key" ? " active" : ""}`}
1712+
onClick={(e) => { e.stopPropagation(); setStepKind(idx, "Key"); }}
1713+
>
1714+
Key
1715+
</button>
1716+
<button
1717+
className={`btn ghost${kind === "Pause" ? " active" : ""}`}
1718+
onClick={(e) => { e.stopPropagation(); setStepKind(idx, "Pause"); }}
1719+
>
1720+
Pause
1721+
</button>
1722+
<button
1723+
className={`btn ghost${kind === "Mouse" ? " active" : ""}`}
1724+
onClick={(e) => { e.stopPropagation(); setStepKind(idx, "Mouse"); }}
1725+
>
1726+
Mouse
1727+
</button>
1728+
<button
1729+
className={`btn ghost${kind === "Function" ? " active" : ""}`}
1730+
onClick={(e) => { e.stopPropagation(); setStepKind(idx, "Function"); }}
1731+
>
1732+
Function
1733+
</button>
16921734
</div>
16931735
{kind === "Pause" && (
16941736
<>
@@ -1720,7 +1762,11 @@ export default function CH55xBootloaderMinimal() {
17201762
onChange={(e) => {
17211763
const parsed = Number(e.target.value);
17221764
if (!Number.isFinite(parsed)) return;
1723-
setEditSteps((prev) => prev.map((s, i) => (i === idx && s.kind === "Key" ? { ...s, keycode: parsed } : s)));
1765+
setEditSteps((prev) => prev.map((s, i) => {
1766+
if (i !== idx || s.kind !== "Key") return s;
1767+
const nextStep: HidStepDto = { ...s, keycode: parsed };
1768+
return cloneStepWithId(s, nextStep);
1769+
}));
17241770
}}
17251771
>
17261772
<option value="0">None (modifiers only)</option>
@@ -1799,7 +1845,8 @@ export default function CH55xBootloaderMinimal() {
17991845
const nextValue = nextType === HID_POINTER_TYPE.LeftClick || nextType === HID_POINTER_TYPE.RightClick
18001846
? 0
18011847
: defaultMouseValue(nextType);
1802-
return { ...s, pointerType: nextType, pointerValue: nextValue };
1848+
const nextStep: HidStepDto = { ...s, pointerType: nextType, pointerValue: nextValue };
1849+
return cloneStepWithId(s, nextStep);
18031850
}));
18041851
}}
18051852
>
@@ -1820,7 +1867,11 @@ export default function CH55xBootloaderMinimal() {
18201867
type="number"
18211868
min={0}
18221869
value={step.pointerType === HID_POINTER_TYPE.LeftClick || step.pointerType === HID_POINTER_TYPE.RightClick ? "" : step.pointerValue}
1823-
onChange={(e) => setEditSteps((prev) => prev.map((s, i) => (i === idx && s.kind === "Mouse" ? { ...s, pointerValue: Number(e.target.value) } : s)))}
1870+
onChange={(e) => setEditSteps((prev) => prev.map((s, i) => {
1871+
if (i !== idx || s.kind !== "Mouse") return s;
1872+
const nextStep: HidStepDto = { ...s, pointerValue: Number(e.target.value) };
1873+
return cloneStepWithId(s, nextStep);
1874+
}))}
18241875
disabled={step.pointerType === HID_POINTER_TYPE.LeftClick || step.pointerType === HID_POINTER_TYPE.RightClick}
18251876
placeholder={step.pointerType === HID_POINTER_TYPE.LeftClick || step.pointerType === HID_POINTER_TYPE.RightClick ? "N/A" : ""}
18261877
title={step.pointerType === HID_POINTER_TYPE.LeftClick || step.pointerType === HID_POINTER_TYPE.RightClick ? "Value is ignored for click actions" : "Movement/scroll amount"}
@@ -1860,7 +1911,8 @@ export default function CH55xBootloaderMinimal() {
18601911
if (i !== idx || s.kind !== "Function") return s;
18611912
const nextPointer = e.target.value || DEFAULT_FUNCTION_POINTER;
18621913
const nextValue = FUNCTIONS_WITH_VALUE.has(nextPointer) ? (s.functionValue ?? 1) : 1;
1863-
return { ...s, functionPointer: nextPointer, functionValue: nextValue };
1914+
const nextStep: HidStepDto = { ...s, functionPointer: nextPointer, functionValue: nextValue };
1915+
return cloneStepWithId(s, nextStep);
18641916
}))}
18651917
>
18661918
{Object.entries(FRIENDLY_FUNCTIONS).map(([fn, friendly]) => (

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ This project contains custom firmware compatible with a number of different CH55
66

77
This project is work in progress, what's left to do:
88
- Add LED configuration and effects
9-
- Add demo mode so people can play around the UI without needing a device
109
- Build the adapter to easily enter bootloader mode without opening the device up or making the contraption manually
1110
- Refactor and clean up the codebase into more manageable pieces
1211
- Probably some other stuff I've forgotten about

0 commit comments

Comments
 (0)