Skip to content

Commit 07a6e12

Browse files
committed
Merge branch 'main' into stream-on-demand-v3
2 parents eaca9ec + dcdb8be commit 07a6e12

11 files changed

+135
-21
lines changed

package-lock.json

+4-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"@testing-library/user-event": "^14.5.2",
1212
"framer-motion": "^11",
1313
"immutable": "^4.3.6",
14-
"nighthouse": "4.2.0",
14+
"nighthouse": "5.0.1",
1515
"react": "^18.3.1",
1616
"react-card-flip": "^1.2.3",
1717
"react-dom": "^18.3.1",

src/components/ObjectInspectorTable.tsx

+17-8
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ import { TableBody, TableHeader } from 'react-stately';
1212
export type Names<T> = { [Property in keyof T]?: string };
1313

1414
export interface ObjectInspectorTableProps<T extends object> {
15-
objects: T[];
15+
objects: (T | null)[];
1616
names: Names<T>;
17+
labelWidth?: number;
1718
selection?: keyof T;
1819
onSelect?: (prop?: keyof T) => void;
1920
render?: <K extends keyof T>(value: T[K], prop: K) => ReactNode;
@@ -22,6 +23,7 @@ export interface ObjectInspectorTableProps<T extends object> {
2223
export function ObjectInspectorTable<T extends object>({
2324
objects,
2425
names,
26+
labelWidth,
2527
selection,
2628
onSelect,
2729
render = (value, _prop) => <ObjectInspectorValue value={value} />,
@@ -40,10 +42,10 @@ export function ObjectInspectorTable<T extends object>({
4042
? (Object.keys(names) as (keyof T)[]).map(prop => ({
4143
key: prop as string,
4244
prop,
43-
values: [names[prop], ...objects.map(v => v[prop])] as [
44-
string,
45-
...T[keyof T][],
46-
],
45+
values: [
46+
names[prop],
47+
...objects.map(v => (v === null ? undefined : v[prop])),
48+
] as [string, ...T[keyof T][]],
4749
}))
4850
: [],
4951
[objects, names]
@@ -63,6 +65,7 @@ export function ObjectInspectorTable<T extends object>({
6365
return (
6466
<Table
6567
hideHeader
68+
layout="fixed"
6669
classNames={{
6770
table: 'bg-red',
6871
}}
@@ -74,18 +77,24 @@ export function ObjectInspectorTable<T extends object>({
7477
aria-label="Lamp monitoring values"
7578
>
7679
<TableHeader columns={columns}>
77-
{column => <TableColumn key={column.key}>{column.key}</TableColumn>}
80+
{column => (
81+
<TableColumn key={column.key} align="end">
82+
{column.key}
83+
</TableColumn>
84+
)}
7885
</TableHeader>
7986
<TableBody items={rows}>
8087
{item => (
8188
<TableRow key={item.key}>
8289
{columnKey => {
8390
const i = parseInt(columnKey as string);
8491
return (
85-
<TableCell key={i}>
92+
<TableCell key={i} width={i === 0 ? labelWidth : undefined}>
8693
{i === 0
8794
? item.values[0]
88-
: render(item.values[i] as T[keyof T], item.prop)}
95+
: item.values[i] !== undefined
96+
? render(item.values[i] as T[keyof T], item.prop)
97+
: undefined}
8998
</TableCell>
9099
);
91100
}}

src/components/TimeInterval.tsx

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
export interface TimeIntervalProps {
2+
seconds: number;
3+
layout?: 'horizontal' | 'vertical';
4+
}
5+
6+
export function TimeInterval({
7+
seconds: rawSeconds,
8+
layout = 'horizontal',
9+
}: TimeIntervalProps) {
10+
const seconds = Math.floor(rawSeconds);
11+
const components: string[] = [`${seconds % 60} s`];
12+
if (seconds > 60) {
13+
const minutes = Math.floor(seconds / 60);
14+
components.push(`${minutes % 60} m`);
15+
if (minutes > 60) {
16+
const hours = Math.floor(minutes / 60);
17+
components.push(`${hours % 24} h`);
18+
if (hours > 24) {
19+
const days = Math.floor(hours / 24);
20+
components.push(`${days} d`);
21+
}
22+
}
23+
}
24+
components.reverse();
25+
return (
26+
<div
27+
className={`flex ${layout === 'horizontal' ? 'flex-row gap-1' : 'items-start flex-col'}`}
28+
>
29+
{components.map((c, i) => (
30+
<div>
31+
{c}
32+
{i !== components.length - 1 ? ',' : undefined}
33+
</div>
34+
))}
35+
</div>
36+
);
37+
}

src/hooks/useBreakpoint.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ export function useBreakpoint(): Breakpoint {
2222
return Breakpoint.Md;
2323
} else if (width < Breakpoint.Xl) {
2424
return Breakpoint.Lg;
25-
} else {
25+
} else if (width < Breakpoint.TwoXl) {
2626
return Breakpoint.Xl;
27+
} else {
28+
return Breakpoint.TwoXl;
2729
}
2830
}

src/screens/home/admin/MonitorInspector.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,15 @@ export interface MonitorInspectorProps {
99
setCriterion: (criterion?: MonitorCriterion) => void;
1010
flatRoomMetrics?: FlatRoomV2Metrics;
1111
lampMetrics?: LampV2Metrics[];
12+
padLampCount: number;
1213
}
1314

1415
export function MonitorInspector({
1516
criterion,
1617
setCriterion,
1718
flatRoomMetrics,
1819
lampMetrics,
20+
padLampCount,
1921
}: MonitorInspectorProps) {
2022
return (
2123
<div className="flex flex-col space-y-3">
@@ -28,6 +30,7 @@ export function MonitorInspector({
2830
criterion={criterion?.type === 'lamp' ? criterion : undefined}
2931
setCriterion={setCriterion}
3032
metrics={lampMetrics ?? []}
33+
padLampCount={padLampCount}
3134
/>
3235
</div>
3336
);

src/screens/home/admin/MonitorInspectorLampsCard.tsx

+12-2
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,21 @@ import {
77
} from '@luna/components/ObjectInspectorTable';
88
import { ObjectInspectorValue } from '@luna/components/ObjectInspectorValue';
99
import { IconCheck, IconLamp } from '@tabler/icons-react';
10+
import { TimeInterval } from '@luna/components/TimeInterval';
1011

1112
export interface MonitorInspectorLampsCardProps {
1213
criterion?: MonitorLampCriterion;
1314
setCriterion: (criterion?: MonitorLampCriterion) => void;
1415
metrics: LampV2Metrics[];
16+
padLampCount: number;
1517
}
1618

1719
const names: Names<LampV2Metrics> = {
1820
responding: 'Responding',
1921
firmware_version: 'Firmware version',
2022
uptime: 'Uptime',
2123
timeout: 'Timeout',
22-
temperature: 'Temperature (not accurate)',
24+
temperature: 'Temperature (inaccurate)',
2325
fuse_tripped: 'Fuse tripped',
2426
flashing_status: 'Flashing status',
2527
};
@@ -28,13 +30,19 @@ export function MonitorInspectorLampsCard({
2830
criterion,
2931
setCriterion,
3032
metrics,
33+
padLampCount,
3134
}: MonitorInspectorLampsCardProps) {
35+
const paddedMetrics: (LampV2Metrics | null)[] = [...metrics];
36+
while (paddedMetrics.length < padLampCount) {
37+
paddedMetrics.push(null);
38+
}
3239
return (
3340
<TitledCard icon={<IconLamp />} title="Lamps">
3441
{metrics.length > 0 ? (
3542
<ObjectInspectorTable
36-
objects={metrics}
43+
objects={paddedMetrics}
3744
names={names}
45+
labelWidth={150}
3846
selection={criterion?.key}
3947
onSelect={key =>
4048
setCriterion(key ? { type: 'lamp', key } : undefined)
@@ -64,6 +72,8 @@ function MonitorInspectorLampValue<K extends keyof LampV2Metrics>({
6472
return <IconCheck />;
6573
}
6674
break;
75+
case 'uptime':
76+
return <TimeInterval seconds={value as number} layout="vertical" />;
6777
}
6878
return <ObjectInspectorValue value={value} />;
6979
}

src/screens/home/admin/MonitorInspectorRoomCard.tsx

+5
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { MonitorRoomCriterion } from '@luna/screens/home/admin/helpers/MonitorCr
88
import { ObjectInspectorValue } from '@luna/components/ObjectInspectorValue';
99
import { IconDoor } from '@tabler/icons-react';
1010
import { useMemo } from 'react';
11+
import { TimeInterval } from '@luna/components/TimeInterval';
1112

1213
export interface MonitorInspectorRoomCardProps {
1314
criterion?: MonitorRoomCriterion;
@@ -82,5 +83,9 @@ function MonitorInspectorRoomValue<K extends keyof FlatRoomV2Metrics>({
8283
value: FlatRoomV2Metrics[K];
8384
prop: K;
8485
}) {
86+
switch (prop) {
87+
case 'uptime':
88+
return <TimeInterval seconds={value as number} />;
89+
}
8590
return <ObjectInspectorValue value={value} unit={units[prop]} />;
8691
}

src/screens/home/admin/MonitorView.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -278,14 +278,15 @@ export function MonitorView() {
278278
</div>
279279
<div
280280
className={
281-
isColumnLayout ? '' : 'flex flex-row justify-end grow-0 w-1/3'
281+
isColumnLayout ? '' : 'flex flex-row justify-end grow-0 2xl:w-[45%]'
282282
}
283283
>
284284
<MonitorInspector
285285
criterion={criterion}
286286
setCriterion={setCriterion}
287287
flatRoomMetrics={focusedFlatRoomMetrics}
288288
lampMetrics={focusedLampMetrics}
289+
padLampCount={isColumnLayout ? 0 : 6}
289290
/>
290291
</div>
291292
</div>

src/screens/home/displays/DisplayInspectorInputCard.tsx

+43-3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ import { InputConfig } from '@luna/screens/home/displays/helpers/InputConfig';
88
import { InputState } from '@luna/screens/home/displays/helpers/InputState';
99
import { AnimatePresence } from '@luna/utils/motion';
1010
import {
11+
IconAlt,
12+
IconArrowBigUp,
13+
IconChevronUp,
14+
IconCommand,
1115
IconDeviceGamepad,
1216
IconDeviceGamepad2,
1317
IconKeyboard,
@@ -17,6 +21,7 @@ import { motion } from 'framer-motion';
1721
import {
1822
GamepadEvent,
1923
KeyEvent,
24+
KeyModifiers,
2025
LegacyControllerEvent,
2126
LegacyKeyEvent,
2227
MouseEvent,
@@ -64,7 +69,7 @@ export function DisplayInspectorInputCard({
6469

6570
return (
6671
<TitledCard icon={<IconDeviceGamepad2 />} title="Input">
67-
<div className="flex flex-col space-y-2">
72+
<div className="flex flex-col space-y-2 md:w-[200px]">
6873
<Tooltip
6974
placement="left"
7075
content={
@@ -172,8 +177,9 @@ function MouseEventView({ event }: { event?: MouseEvent }) {
172177
}
173178

174179
const keyEventNames: Names<KeyEvent> = {
175-
key: 'Key',
176180
down: 'Down',
181+
repeat: 'Repeat',
182+
code: 'Code',
177183
};
178184

179185
const legacyKeyEventNames: Names<LegacyKeyEvent> = {
@@ -186,13 +192,47 @@ function KeyEventView({ event }: { event?: KeyEvent | LegacyKeyEvent }) {
186192
'dwn' in event ? (
187193
<ObjectInspectorTable objects={[event]} names={legacyKeyEventNames} />
188194
) : (
189-
<ObjectInspectorTable objects={[event]} names={keyEventNames} />
195+
<div className="flex flex-col items-center gap-1">
196+
<ObjectInspectorTable objects={[event]} names={keyEventNames} />
197+
<Divider />
198+
<KeyModifiersView modifiers={event.modifiers} />
199+
<Divider />
200+
</div>
190201
)
191202
) : (
192203
<EventInfoText>no key events yet</EventInfoText>
193204
);
194205
}
195206

207+
function KeyModifiersView({ modifiers }: { modifiers: KeyModifiers }) {
208+
return (
209+
<div className="flex flex-row gap-2">
210+
<ModifierView down={modifiers.shift}>
211+
<IconArrowBigUp />
212+
</ModifierView>
213+
<ModifierView down={modifiers.ctrl}>
214+
<IconChevronUp />
215+
</ModifierView>
216+
<ModifierView down={modifiers.alt}>
217+
<IconAlt />
218+
</ModifierView>
219+
<ModifierView down={modifiers.meta}>
220+
<IconCommand />
221+
</ModifierView>
222+
</div>
223+
);
224+
}
225+
226+
function ModifierView({
227+
down,
228+
children,
229+
}: {
230+
down: boolean;
231+
children: ReactNode;
232+
}) {
233+
return <div className={down ? '' : 'opacity-50'}>{children}</div>;
234+
}
235+
196236
const legacyControllerEventNames: Names<LegacyControllerEvent> = {
197237
btn: 'Button',
198238
dwn: 'Down',

src/screens/home/displays/DisplayView.tsx

+8-1
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,14 @@ export function DisplayView() {
9696
type: 'key',
9797
source: clientId,
9898
down,
99-
key: e.key,
99+
repeat: e.repeat,
100+
code: e.code,
101+
modifiers: {
102+
alt: e.altKey,
103+
ctrl: e.ctrlKey,
104+
meta: e.metaKey,
105+
shift: e.shiftKey,
106+
},
100107
};
101108
await api.putInput(username, event);
102109
setInputState(state => ({ ...state, lastKeyEvent: event }));

0 commit comments

Comments
 (0)