Skip to content

Commit 3535cf4

Browse files
authored
Merge pull request #230 from Open-STEM/rishi-dashboard
XRP Dashboard updates
2 parents 8461a40 + 3696bf2 commit 3535cf4

28 files changed

Lines changed: 1943 additions & 2545 deletions

package-lock.json

Lines changed: 4 additions & 97 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/components/dashboard/AddWidget.tsx

Lines changed: 109 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -2,167 +2,128 @@ import React from "react";
22
import { useGridStackContext } from "./lib/grid-stack-context";
33
import { Dropdown, DropdownItem } from "flowbite-react";
44
import { MdSpeed } from 'react-icons/md';
5-
import { FaBatteryHalf, FaBolt, FaCog, FaEye, FaGlobe, FaPlus } from 'react-icons/fa';
5+
import { FaBatteryHalf, FaBolt, FaCog, FaEye, FaGlobe, FaPlug, FaPlus, FaSlidersH } from 'react-icons/fa';
66
import { BsRulers } from 'react-icons/bs';
77
import { FlowBiteConstants } from "@/utils/constants";
88
import { useTranslation } from "react-i18next";
9+
import { getCustomSensors } from "./sensors/customRegistry";
910

10-
type ActionType = 'accelerometer' | 'current' | 'encoder' | 'gyroscope' | 'rangefinder' | 'reflectance' | 'voltage';
11+
12+
interface BuiltinSensorDef {
13+
action: string;
14+
icon: React.ComponentType<{ size?: number }>;
15+
titleKey: string;
16+
gridH: number;
17+
gridW: number;
18+
minW: number;
19+
minH: number;
20+
componentName: string;
21+
}
22+
23+
const BUILTIN_SENSORS: BuiltinSensorDef[] = [
24+
{ action: 'accelerometer', icon: MdSpeed, titleKey: 'accelerometer', gridH: 5, gridW: 4, minW: 2, minH: 5, componentName: 'Accelerometer' },
25+
{ action: 'current', icon: FaBolt, titleKey: 'current', gridH: 4, gridW: 4, minW: 1, minH: 5, componentName: 'Current' },
26+
{ action: 'gyroscope', icon: FaGlobe, titleKey: 'gyroscope', gridH: 4, gridW: 4, minW: 2, minH: 5, componentName: 'Gyroscope' },
27+
{ action: 'encoder', icon: FaCog, titleKey: 'encoders', gridH: 5, gridW: 4, minW: 1, minH: 4, componentName: 'Encoder' },
28+
{ action: 'reflectance', icon: FaEye, titleKey: 'reflectance', gridH: 5, gridW: 4, minW: 2, minH: 5, componentName: 'Reflectance' },
29+
{ action: 'rangefinder', icon: BsRulers, titleKey: 'rangefinder', gridH: 8, gridW: 4, minW: 2, minH: 8, componentName: 'Rangefinder' },
30+
{ action: 'voltage', icon: FaBatteryHalf, titleKey: 'voltage', gridH: 6, gridW: 4, minW: 2, minH: 6, componentName: 'Voltage' },
31+
];
1132

1233
const AddWidgets: React.FC = () => {
1334
const { t } = useTranslation();
1435
const { addWidget } = useGridStackContext();
1536

16-
const handleAction = (action: ActionType) => {
17-
switch (action) {
18-
case 'accelerometer': {
19-
const node = () => ({
20-
// Remove custom id - let GridStack auto-generate
21-
h: 5,
22-
w: 4,
23-
x: 0,
24-
y: 2,
25-
minW: 2,
26-
minH: 5,
27-
content: JSON.stringify({
28-
name: 'Accelerometer',
29-
props: {
30-
isActive: true
31-
// Remove widgetId - we'll get it from GridStack
32-
},
33-
}),
34-
});
35-
addWidget(node);
36-
console.log('Accelerometer action triggered');
37-
break;
38-
}
39-
case 'current': {
40-
const node = () => ({
41-
h: 4,
42-
w: 4,
43-
x: 0,
44-
y: 2,
45-
minW: 1,
46-
minH: 5,
47-
content: JSON.stringify({
48-
name: 'Current',
49-
props: {
50-
isActive: true
51-
},
52-
}),
53-
});
54-
addWidget(node);
55-
console.log('Current sensor action triggered');
56-
break;
57-
}
58-
case 'gyroscope': {
59-
const node = () => ({
60-
h: 4,
61-
w: 4,
62-
x: 0,
63-
y: 2,
64-
minW: 2,
65-
minH: 5,
66-
content: JSON.stringify({
67-
name: 'Gyroscope',
68-
props: {
69-
isActive: true
70-
},
71-
}),
72-
});
73-
addWidget(node);
74-
console.log('Gyroscope action triggered');
75-
break;
76-
}
77-
case 'encoder': {
78-
const node = () => ({
79-
h: 5,
80-
w: 4,
81-
x: 0,
82-
y: 2,
83-
minW: 1,
84-
minH: 4,
85-
content: JSON.stringify({
86-
name: 'Encoder',
87-
props: {
88-
isActive: true
89-
},
90-
}),
91-
});
92-
addWidget(node);
93-
console.log('Encoder action triggered');
94-
break;
95-
}
96-
case 'reflectance': {
97-
const node = () => ({
98-
h: 5,
99-
w: 4,
100-
x: 0,
101-
y: 2,
102-
minW: 2,
103-
minH: 5,
104-
content: JSON.stringify({
105-
name: 'Reflectance',
106-
props: {
107-
isActive: true
108-
},
109-
}),
110-
});
111-
addWidget(node);
112-
console.log('Reflectance action triggered');
113-
break;
114-
}
115-
case 'voltage': {
116-
const node = () => ({
117-
h: 6,
118-
w: 4,
119-
x: 0,
120-
y: 2,
121-
minW: 2,
122-
minH: 6,
123-
content: JSON.stringify({
124-
name: 'Voltage',
125-
props: {
126-
isActive: true
127-
},
128-
}),
129-
});
130-
addWidget(node);
131-
console.log('Voltage action triggered');
132-
break;
133-
}
134-
case 'rangefinder': {
135-
const node = () => ({
136-
h: 8,
137-
w: 4,
138-
x: 0,
139-
y: 2,
140-
minW: 2,
141-
minH: 8,
142-
content: JSON.stringify({
143-
name: 'Rangefinder',
144-
props: {
145-
isActive: true
146-
},
147-
}),
148-
});
149-
addWidget(node);
150-
console.log('Rangefinder action triggered');
151-
break;
152-
}
153-
}
37+
const customSensors = getCustomSensors();
38+
39+
const handleAddBuiltin = (def: BuiltinSensorDef) => {
40+
addWidget(() => ({
41+
h: def.gridH,
42+
w: def.gridW,
43+
x: 0,
44+
y: 2,
45+
minW: def.minW,
46+
minH: def.minH,
47+
content: JSON.stringify({
48+
name: def.componentName,
49+
props: { isActive: true },
50+
}),
51+
}));
52+
};
53+
54+
const handleAddCustom = (sensorName: string) => {
55+
const def = customSensors.find(s => s.sensorName === sensorName);
56+
if (!def) return;
57+
58+
const grid = def.gridDefaults ?? {};
59+
addWidget(() => ({
60+
h: grid.h ?? 5,
61+
w: grid.w ?? 4,
62+
x: 0,
63+
y: 2,
64+
minW: grid.minW ?? 2,
65+
minH: grid.minH ?? 5,
66+
content: JSON.stringify({
67+
name: 'CustomSensor',
68+
props: { sensorName: def.sensorName },
69+
}),
70+
}));
15471
};
15572

15673
return (
15774
<div className="flex items-center mt-4 sm:mt-0">
158-
<Dropdown label={<FaPlus size={20} />} inline={true} theme={FlowBiteConstants.DropdownTheme} className="flex items-center mt-4 sm:mt-0 ">
159-
<DropdownItem icon={MdSpeed} onClick={() => handleAction('accelerometer')}>{t('accelerometer')}</DropdownItem>
160-
<DropdownItem icon={FaBolt} onClick={() => handleAction('current')}>{t('current')}</DropdownItem>
161-
<DropdownItem icon={FaGlobe} onClick={() => handleAction('gyroscope')}>{t('gyroscope')}</DropdownItem>
162-
<DropdownItem icon={FaCog} onClick={() => handleAction('encoder')}>{t('encoders')}</DropdownItem>
163-
<DropdownItem icon={FaEye} onClick={() => handleAction('reflectance')}>{t('reflectance')}</DropdownItem>
164-
<DropdownItem icon={BsRulers} onClick={() => handleAction('rangefinder')}>{t('rangefinder')}</DropdownItem>
165-
<DropdownItem icon={FaBatteryHalf} onClick={() => handleAction('voltage')}>{t('voltage')}</DropdownItem>
75+
<Dropdown
76+
label={<FaPlus size={20} />}
77+
inline={true}
78+
theme={FlowBiteConstants.DropdownTheme}
79+
className="flex items-center mt-4 sm:mt-0"
80+
>
81+
{/* Built-in sensors */}
82+
{BUILTIN_SENSORS.map((def) => (
83+
<DropdownItem
84+
key={def.action}
85+
icon={def.icon as any}
86+
onClick={() => handleAddBuiltin(def)}
87+
>
88+
{t(def.titleKey)}
89+
</DropdownItem>
90+
))}
91+
92+
{/* Custom Variable (bidirectional XPP variable inspector/editor) */}
93+
<hr className="my-1 border-gray-200" />
94+
<DropdownItem
95+
icon={FaSlidersH}
96+
onClick={() => {
97+
addWidget(() => ({
98+
h: 5,
99+
w: 4,
100+
x: 0,
101+
y: 2,
102+
minW: 2,
103+
minH: 4,
104+
content: JSON.stringify({
105+
name: 'CustomVariable',
106+
props: {},
107+
}),
108+
}));
109+
}}
110+
>
111+
{'Custom Variable'}
112+
</DropdownItem>
113+
{customSensors.length > 0 && (
114+
<>
115+
<hr className="my-1 border-gray-200" />
116+
{customSensors.map((def) => (
117+
<DropdownItem
118+
key={def.sensorName}
119+
icon={FaPlug}
120+
onClick={() => handleAddCustom(def.sensorName)}
121+
>
122+
{def.title}
123+
</DropdownItem>
124+
))}
125+
</>
126+
)}
166127
</Dropdown>
167128
</div>
168129
);
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { useCallback } from 'react';
2+
import { useGridStackContext } from '../lib/grid-stack-context';
3+
import { useGridStackWidgetContext } from '../lib/grid-stack-widget-context';
4+
5+
export function useGridStackWidget() {
6+
const { removeWidget } = useGridStackContext();
7+
const { widget } = useGridStackWidgetContext();
8+
9+
const handleDelete = useCallback(() => {
10+
if (widget.id && removeWidget) {
11+
removeWidget(widget.id);
12+
} else {
13+
console.error('Could not delete widget — missing ID or removeWidget');
14+
}
15+
}, [widget.id, removeWidget]);
16+
17+
return { widgetId: widget.id, handleDelete };
18+
}

0 commit comments

Comments
 (0)