diff --git a/changelog.json b/changelog.json index 8fd423b..3181c65 100644 --- a/changelog.json +++ b/changelog.json @@ -1,4 +1,14 @@ [ + { + "version": "0.24.1", + "date": "", + "en": [ + "Band map: the width can be dragged. Both the map docked beside the tables and the per-band cards in the Band map tab were locked at a fixed width, so an operator watching a busy band could not give the map more room — nor take it back for the log. Grab the edge to resize, double-click it to go back to the default. The width is remembered and travels with your data folder, like the other layout settings. In the tab, one width applies to every card: they sit side by side, and columns of different widths read as a mistake." + ], + "fr": [ + "Band map : la largeur se règle à la souris. La carte ancrée à côté des tableaux et les cartes par bande de l'onglet Band map étaient figées à une largeur fixe : impossible de donner plus de place à la carte sur une bande chargée, ni de la reprendre pour le journal. Attrape le bord pour redimensionner, double-clic pour revenir au défaut. La largeur est mémorisée et voyage avec ton dossier de données, comme les autres réglages de disposition. Dans l'onglet, une seule largeur vaut pour toutes les cartes : elles sont côte à côte, et des colonnes de largeurs différentes se lisent comme une erreur." + ] + }, { "version": "0.24.0", "date": "", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index ec1dfb2..eef404e 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -907,6 +907,49 @@ export default function App() { return Number.isFinite(n) && n >= 15 && n <= 85 ? n : 50; }); useEffect(() => { writeUiPref('opslog.mainSplit', String(Math.round(mainSplit))); }, [mainSplit]); + + // Band-map widths. Two of them, because they are two different things: the + // docked map sits beside the tables and competes with them for room, while + // the cards in the Band map tab share a scrolling row and want to be uniform. + // Both were hardcoded — 300px and 260px — so an operator watching a busy band + // could not give the map the space it needed, nor claw it back for the log. + const BANDMAP_W_DEFAULT = 300, BANDMAP_W_MIN = 200, BANDMAP_W_MAX = 900; + const BANDMAP_TAB_W_DEFAULT = 260, BANDMAP_TAB_W_MIN = 160, BANDMAP_TAB_W_MAX = 700; + const readWidth = (key: string, def: number, min: number, max: number) => { + const n = parseFloat(localStorage.getItem(key) || ''); + return Number.isFinite(n) && n >= min && n <= max ? n : def; + }; + const [bandMapWidth, setBandMapWidth] = useState( + () => readWidth('opslog.bandMapWidth', BANDMAP_W_DEFAULT, BANDMAP_W_MIN, BANDMAP_W_MAX)); + const [bandMapTabWidth, setBandMapTabWidth] = useState( + () => readWidth('opslog.bandMapTabWidth', BANDMAP_TAB_W_DEFAULT, BANDMAP_TAB_W_MIN, BANDMAP_TAB_W_MAX)); + useEffect(() => { writeUiPref('opslog.bandMapWidth', String(Math.round(bandMapWidth))); }, [bandMapWidth]); + useEffect(() => { writeUiPref('opslog.bandMapTabWidth', String(Math.round(bandMapTabWidth))); }, [bandMapTabWidth]); + + // Drag one edge of a fixed-width column. Measures from the pointer's START + // position rather than the container, so it behaves the same whether the grip + // is on the left or the right edge — the docked map is docked on either side. + const startWidthDrag = ( + e: React.PointerEvent, current: number, edge: 'left' | 'right', + min: number, max: number, apply: (w: number) => void, + ) => { + e.preventDefault(); + // Pointer capture, for the same reason as the main splitter: without it a + // map or a grid under the cursor swallows the moves. + (e.currentTarget as HTMLElement).setPointerCapture(e.pointerId); + const x0 = e.clientX; + const onMove = (ev: PointerEvent) => { + const delta = edge === 'right' ? ev.clientX - x0 : x0 - ev.clientX; + apply(Math.min(max, Math.max(min, Math.round(current + delta)))); + }; + const onUp = () => { + window.removeEventListener('pointermove', onMove); + window.removeEventListener('pointerup', onUp); + }; + window.addEventListener('pointermove', onMove); + window.addEventListener('pointerup', onUp); + }; + const mainSplitRef = useRef(null); const startMainSplitDrag = (e: React.PointerEvent) => { e.preventDefault(); @@ -5948,9 +5991,15 @@ export default function App() { {/* ===== LOWER: tabbed table / cluster / band map ===== */} {compact ? null : <> -
-
+ {/* The band map is a fixed-width column with a draggable grip on its inner + edge — the gap between the two panes doubles as the handle, so no room + is spent on it. Same idiom as the Main tab's splitter. */} +
+
{t('tab.main')} @@ -6522,7 +6571,23 @@ export default function App() { Pick one or more bands above to show their band maps side by side.
) : bandMapBands.map((b) => ( -
+
+ {/* One width for every card: they sit side by side in a + scrolling row, and columns of different widths read as a + mistake rather than a choice. Grip on the right edge. */} +
startWidthDrag( + e, bandMapTabWidth, 'right', + BANDMAP_TAB_W_MIN, BANDMAP_TAB_W_MAX, setBandMapTabWidth)} + onDoubleClick={() => setBandMapTabWidth(BANDMAP_TAB_W_DEFAULT)} + className="group absolute inset-y-0 right-0 z-10 w-2 cursor-col-resize flex items-center justify-center" + > + +
s.band === b)} @@ -6541,7 +6606,22 @@ export default function App() {
{showBandMap && ( -
+
startWidthDrag( + e, bandMapWidth, bandMapSide === 'left' ? 'right' : 'left', + BANDMAP_W_MIN, BANDMAP_W_MAX, setBandMapWidth)} + onDoubleClick={() => setBandMapWidth(BANDMAP_W_DEFAULT)} + className="group relative order-2 cursor-col-resize flex items-center justify-center" + > + +
+ )} + {showBandMap && ( +