Skip to content

Commit 6edf413

Browse files
authored
Add resizing of FLA panels (#1068)
* Add resizing of FLA panels * Address copilot review comments
1 parent 12f050c commit 6edf413

3 files changed

Lines changed: 188 additions & 112 deletions

File tree

gcs/src/components/fla/graph.jsx

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -487,26 +487,23 @@ export default function Graph({ data, customColors, openPresetModal }) {
487487
}, [events, flightModes, datasetKey])
488488

489489
return (
490-
<div>
491-
<PanelGroup direction="horizontal" style={{ height: "auto" }}>
490+
<div className="h-full flex flex-col">
491+
<PanelGroup direction="horizontal" className="flex-1 min-h-0">
492492
<Panel minSize={20}>
493-
<div
494-
className="chart-container relative cursor-crosshair"
495-
style={{ minHeight: "60vh" }}
496-
>
493+
<div className="chart-container relative cursor-crosshair h-full">
497494
<Line ref={chartRef} options={config} data={data} />
498495
</div>
499496
</Panel>
500497
{showMap && (
501498
<>
502-
<PanelResizeHandle className='w-1 bg-zinc-700 hover:bg-zinc-500 data-[resize-handle-state="hover"]:bg-zinc-500 data-[resize-handle-state="drag"]:bg-zinc-500' />
499+
<PanelResizeHandle className='w-1 bg-falcongrey-700 hover:bg-falconred-500 data-[resize-handle-state="hover"]:bg-falconred-500 data-[resize-handle-state="drag"]:bg-falconred-500' />
503500
<Panel minSize={10}>
504501
<FlaMapSection />
505502
</Panel>
506503
</>
507504
)}
508505
</PanelGroup>
509-
<div className="flex flex-row gap-2 py-2">
506+
<div className="flex flex-row gap-2 py-2 flex-shrink-0">
510507
<MantineTooltip label="Zoom in">
511508
<ActionIcon
512509
variant="filled"

gcs/src/components/fla/mainDisplay.jsx

Lines changed: 159 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
import { Accordion, Button, ScrollArea, Tabs, Tooltip } from "@mantine/core"
2-
import { useDisclosure } from "@mantine/hooks"
2+
import { useDebouncedCallback, useDisclosure } from "@mantine/hooks"
33
import { Fragment } from "react"
4-
import { useSelector } from "react-redux"
4+
import { useDispatch, useSelector } from "react-redux"
5+
import { Panel, PanelGroup, PanelResizeHandle } from "react-resizable-panels"
56
import {
67
selectAircraftType,
78
selectFile,
89
selectFirmwareVersion,
910
selectMessageMeans,
11+
selectPanelLayout,
12+
setHorizontalLayout,
13+
setVerticalLayout,
1014
} from "../../redux/slices/logAnalyserSlice.js"
1115
import ChartDataCard from "./chartDataCard.jsx"
1216
import DataTable from "./dataTable.jsx"
@@ -20,11 +24,25 @@ import SavePresetModal from "./savePresetModal.jsx"
2024
* Main display component for the Falcon Log Analyser (FLA).
2125
*/
2226
export default function MainDisplay({ closeLogFile, chartData, customColors }) {
27+
const dispatch = useDispatch()
28+
2329
// Redux selectors
2430
const file = useSelector(selectFile)
2531
const aircraftType = useSelector(selectAircraftType)
2632
const messageMeans = useSelector(selectMessageMeans)
2733
const firmwareVersion = useSelector(selectFirmwareVersion)
34+
const panelLayout = useSelector(selectPanelLayout)
35+
36+
// Debounced layout update handlers (300ms delay)
37+
const debouncedSetHorizontalLayout = useDebouncedCallback(
38+
(sizes) => dispatch(setHorizontalLayout(sizes)),
39+
300,
40+
)
41+
42+
const debouncedSetVerticalLayout = useDebouncedCallback(
43+
(sizes) => dispatch(setVerticalLayout(sizes)),
44+
300,
45+
)
2846

2947
// Shared presets state for all children (presets + modal)
3048
const {
@@ -40,117 +58,154 @@ export default function MainDisplay({ closeLogFile, chartData, customColors }) {
4058
] = useDisclosure(false)
4159

4260
return (
43-
<div className="flex h-full gap-4 px-2 py-4 mb-4 overflow-hidden">
44-
{/* Message selection column */}
45-
<div className="w-1/4 pb-6 flex flex-col min-h-0">
46-
<div className="flex flex-col mb-2 text-sm gap-y-2 flex-shrink-0">
47-
<div className="flex flex-row justify-between">
48-
<Tooltip label={file.path}>
49-
<div className="px-4 py-2 text-gray-200 bg-falcongrey-700 rounded truncate max-w-[400px] inline-block">
50-
File Name:
51-
<span
52-
className="ml-2 text-white underline cursor-pointer"
53-
onClick={() => {
54-
window.ipcRenderer.send(
55-
"window:open-file-in-explorer",
56-
file.path,
57-
)
58-
}}
61+
<div className="flex h-full px-2 py-4 mb-4 overflow-hidden">
62+
<PanelGroup
63+
direction="horizontal"
64+
onLayout={debouncedSetHorizontalLayout}
65+
>
66+
{/* Left Panel - Message selection column */}
67+
<Panel defaultSize={panelLayout.leftSidebar} minSize={10}>
68+
<div className="pb-6 flex flex-col min-h-0 h-full pr-2">
69+
<div className="flex flex-col mb-2 text-sm gap-y-2 flex-shrink-0">
70+
<div className="flex flex-row justify-between">
71+
<Tooltip label={file.path}>
72+
<div className="px-4 py-2 text-gray-200 bg-falcongrey-700 rounded truncate max-w-[400px] inline-block">
73+
File Name:
74+
<span
75+
className="ml-2 text-white underline cursor-pointer"
76+
onClick={() => {
77+
window.ipcRenderer.send(
78+
"window:open-file-in-explorer",
79+
file.path,
80+
)
81+
}}
82+
>
83+
{file.name}
84+
</span>
85+
</div>
86+
</Tooltip>
87+
<Button
88+
className="ml-2"
89+
size="sm"
90+
color="red"
91+
onClick={closeLogFile}
5992
>
60-
{file.name}
61-
</span>
93+
Close file
94+
</Button>
6295
</div>
63-
</Tooltip>
64-
<Button
65-
className="ml-2"
66-
size="sm"
67-
color="red"
68-
onClick={closeLogFile}
69-
>
70-
Close file
71-
</Button>
72-
</div>
73-
<div className="flex justify-between px-4 py-2 text-gray-200 rounded bg-falcongrey-700">
74-
<div className="whitespace-nowrap">Aircraft Type:</div>
75-
<div className="text-white ml-auto capitalize">
76-
{aircraftType ?? "No Aircraft Type"}
96+
<div className="flex justify-between px-4 py-2 text-gray-200 rounded bg-falcongrey-700">
97+
<div className="whitespace-nowrap">Aircraft Type:</div>
98+
<div className="text-white ml-auto capitalize">
99+
{aircraftType ?? "No Aircraft Type"}
100+
</div>
101+
</div>
102+
{firmwareVersion !== null && (
103+
<div className="flex justify-between px-4 py-2 text-gray-200 rounded bg-falcongrey-700">
104+
<div className="whitespace-nowrap">Firmware Version:</div>
105+
<div className="text-white ml-auto">{firmwareVersion}</div>
106+
</div>
107+
)}
77108
</div>
109+
<ScrollArea className="flex-1 min-h-0">
110+
<Accordion multiple={true} defaultValue={["messages"]}>
111+
{/* Presets */}
112+
<Accordion.Item key="presets" value="presets">
113+
<Accordion.Control className="rounded-md">
114+
Presets
115+
</Accordion.Control>
116+
<Accordion.Panel>
117+
<PresetsAccordion
118+
presetCategories={presetCategories}
119+
deleteCustomPreset={deleteCustomPreset}
120+
/>
121+
</Accordion.Panel>
122+
</Accordion.Item>
123+
124+
{/* All messages (checkboxes in accordions) */}
125+
<Accordion.Item
126+
key="messages"
127+
value="messages"
128+
styles={{ item: { borderBottom: "none" } }}
129+
>
130+
<Accordion.Control className="rounded-md">
131+
Messages
132+
</Accordion.Control>
133+
<Accordion.Panel>
134+
<MessagesFiltersAccordion />
135+
</Accordion.Panel>
136+
</Accordion.Item>
137+
</Accordion>
138+
</ScrollArea>
78139
</div>
79-
{firmwareVersion !== null && (
80-
<div className="flex justify-between px-4 py-2 text-gray-200 rounded bg-falcongrey-700">
81-
<div className="whitespace-nowrap">Firmware Version:</div>
82-
<div className="text-white ml-auto">{firmwareVersion}</div>
83-
</div>
84-
)}
85-
</div>
86-
<ScrollArea className="flex-1 min-h-0">
87-
<Accordion multiple={true} defaultValue={["messages"]}>
88-
{/* Presets */}
89-
<Accordion.Item key="presets" value="presets">
90-
<Accordion.Control className="rounded-md">
91-
Presets
92-
</Accordion.Control>
93-
<Accordion.Panel>
94-
<PresetsAccordion
95-
presetCategories={presetCategories}
96-
deleteCustomPreset={deleteCustomPreset}
140+
</Panel>
141+
142+
{/* Resize Handle */}
143+
<PanelResizeHandle className='w-1 bg-falcongrey-700 hover:bg-falconred-500 data-[resize-handle-state="hover"]:bg-falconred-500 data-[resize-handle-state="drag"]:bg-falconred-500 transition-colors cursor-col-resize' />
144+
145+
{/* Right Panel - Graph and Tabs */}
146+
<Panel defaultSize={panelLayout.rightContent} minSize={65}>
147+
<PanelGroup
148+
direction="vertical"
149+
className="h-full"
150+
onLayout={debouncedSetVerticalLayout}
151+
>
152+
{/* Top Panel - Graph */}
153+
<Panel defaultSize={panelLayout.graph} minSize={30}>
154+
<div className="h-full pl-2 min-w-0">
155+
<Graph
156+
data={chartData}
157+
customColors={customColors}
158+
openPresetModal={openSavePresetModal}
97159
/>
98-
</Accordion.Panel>
99-
</Accordion.Item>
160+
</div>
161+
</Panel>
100162

101-
{/* All messages (checkboxes in accordions) */}
102-
<Accordion.Item
103-
key="messages"
104-
value="messages"
105-
styles={{ item: { borderBottom: "none" } }}
106-
>
107-
<Accordion.Control className="rounded-md">
108-
Messages
109-
</Accordion.Control>
110-
<Accordion.Panel>
111-
<MessagesFiltersAccordion />
112-
</Accordion.Panel>
113-
</Accordion.Item>
114-
</Accordion>
115-
</ScrollArea>
116-
</div>
117-
{/* Graph column */}
118-
<div className="w-full h-full pr-4 min-w-0 flex flex-col">
119-
<Graph
120-
data={chartData}
121-
customColors={customColors}
122-
openPresetModal={openSavePresetModal}
123-
/>
163+
{/* Vertical Resize Handle */}
164+
<PanelResizeHandle className='h-1 bg-falcongrey-700 hover:bg-falconred-500 data-[resize-handle-state="hover"]:bg-falconred-500 data-[resize-handle-state="drag"]:bg-falconred-500 transition-colors cursor-row-resize' />
124165

125-
<Tabs
126-
defaultValue={"chart_cards"}
127-
className="flex flex-col flex-1 min-h-0"
128-
>
129-
<Tabs.List>
130-
<Tabs.Tab value="chart_cards">Chart Cards</Tabs.Tab>
131-
<Tabs.Tab value="data_table">Data Table</Tabs.Tab>
132-
</Tabs.List>
166+
{/* Bottom Panel - Tabs */}
167+
<Panel defaultSize={panelLayout.tabs} minSize={15}>
168+
<div className="h-full px-2 py-1 min-w-0 flex flex-col">
169+
<Tabs
170+
defaultValue={"chart_cards"}
171+
className="flex flex-col flex-1 min-h-0"
172+
>
173+
<Tabs.List>
174+
<Tabs.Tab value="chart_cards">Chart Cards</Tabs.Tab>
175+
<Tabs.Tab value="data_table">Data Table</Tabs.Tab>
176+
</Tabs.List>
133177

134-
<Tabs.Panel value="chart_cards" className="min-h-0 h-full pb-4">
135-
<ScrollArea className="h-full">
136-
<div className="grid grid-cols-6 gap-2 py-2">
137-
{chartData.datasets.map((item) => (
138-
<Fragment key={item.label}>
139-
<ChartDataCard
140-
item={item}
141-
unit={item.yAxisID} // item.yAxisID is the unit
142-
messageMeans={messageMeans}
143-
/>
144-
</Fragment>
145-
))}
178+
<Tabs.Panel
179+
value="chart_cards"
180+
className="min-h-0 h-full pb-4"
181+
>
182+
<ScrollArea className="h-full">
183+
<div className="grid grid-cols-6 gap-2 py-2">
184+
{chartData.datasets.map((item) => (
185+
<Fragment key={item.label}>
186+
<ChartDataCard
187+
item={item}
188+
unit={item.yAxisID} // item.yAxisID is the unit
189+
messageMeans={messageMeans}
190+
/>
191+
</Fragment>
192+
))}
193+
</div>
194+
</ScrollArea>
195+
</Tabs.Panel>
196+
<Tabs.Panel
197+
value="data_table"
198+
className="min-h-0 h-full pb-4"
199+
>
200+
<DataTable />
201+
</Tabs.Panel>
202+
</Tabs>
146203
</div>
147-
</ScrollArea>
148-
</Tabs.Panel>
149-
<Tabs.Panel value="data_table" className="min-h-0 h-full pb-4">
150-
<DataTable />
151-
</Tabs.Panel>
152-
</Tabs>
153-
</div>
204+
</Panel>
205+
</PanelGroup>
206+
</Panel>
207+
</PanelGroup>
208+
154209
<SavePresetModal
155210
isSavePresetModalOpen={isSavePresetModalOpen}
156211
closeSavePresetModal={closeSavePresetModal}

gcs/src/redux/slices/logAnalyserSlice.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ const logAnalyserSlice = createSlice({
2020
firmwareVersion: null,
2121
params: null,
2222
mapPositionData: null,
23+
panelLayout: {
24+
leftSidebar: 20,
25+
rightContent: 80,
26+
graph: 70,
27+
tabs: 30,
28+
},
2329
},
2430
reducers: {
2531
setFile: (state, action) => {
@@ -76,6 +82,19 @@ const logAnalyserSlice = createSlice({
7682
setMapPositionData: (state, action) => {
7783
state.mapPositionData = action.payload
7884
},
85+
setPanelLayout: (state, action) => {
86+
state.panelLayout = action.payload
87+
},
88+
setHorizontalLayout: (state, action) => {
89+
// action.payload is [leftSidebar, rightContent]
90+
state.panelLayout.leftSidebar = action.payload[0]
91+
state.panelLayout.rightContent = action.payload[1]
92+
},
93+
setVerticalLayout: (state, action) => {
94+
// action.payload is [graph, tabs]
95+
state.panelLayout.graph = action.payload[0]
96+
state.panelLayout.tabs = action.payload[1]
97+
},
7998
},
8099
selectors: {
81100
selectFile: (state) => state.file,
@@ -97,6 +116,7 @@ const logAnalyserSlice = createSlice({
97116
selectParams: (state) => state.params,
98117
selectMapPositionData: (state) => state.mapPositionData,
99118
selectCanShowMapPositionData: (state) => state.mapPositionData !== null,
119+
selectPanelLayout: (state) => state.panelLayout,
100120
},
101121
})
102122

@@ -119,6 +139,9 @@ export const {
119139
setFirmwareVersion,
120140
setParams,
121141
setMapPositionData,
142+
setPanelLayout,
143+
setHorizontalLayout,
144+
setVerticalLayout,
122145
} = logAnalyserSlice.actions
123146

124147
export const {
@@ -141,6 +164,7 @@ export const {
141164
selectParams,
142165
selectMapPositionData,
143166
selectCanShowMapPositionData,
167+
selectPanelLayout,
144168
} = logAnalyserSlice.selectors
145169

146170
export default logAnalyserSlice

0 commit comments

Comments
 (0)