Skip to content

Commit 7af56c4

Browse files
committed
Support for updating app settings
1 parent 3273b05 commit 7af56c4

File tree

14 files changed

+552
-295
lines changed

14 files changed

+552
-295
lines changed

bridge-settings/react-app/src/App.tsx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,19 @@ import { useSettings } from "./hooks/useSettings";
22
import ConnectionStatus from "./components/ConnectionStatus";
33
import SystemStatus from "./components/SystemStatus";
44
import SystemSettings from "./components/SystemSettings";
5-
import AppSettings from "./components/AppSettings";
5+
import { AppSettings } from "./components/AppSettings";
66
import MABLStatus from "./components/MABLStatus";
77
import ESimSettings from "./components/ESimSettings";
88

99
function App() {
10-
const { loading, error, connected, executeAction, actionResults, executionStatus } = useSettings();
10+
const {
11+
loading,
12+
error,
13+
connected,
14+
executeAction,
15+
actionResults,
16+
executionStatus,
17+
} = useSettings();
1118

1219
if (loading) {
1320
return (
@@ -36,7 +43,11 @@ function App() {
3643
<SystemSettings />
3744
<AppSettings />
3845
<MABLStatus />
39-
<ESimSettings onExecuteAction={executeAction} actionResults={actionResults} executionStatus={executionStatus} />
46+
<ESimSettings
47+
onExecuteAction={executeAction}
48+
actionResults={actionResults}
49+
executionStatus={executionStatus}
50+
/>
4051
</div>
4152
</>
4253
)}

bridge-settings/react-app/src/components/ActionButton.tsx

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
LogEntry,
77
ExecutionStatus,
88
} from "../types/settings";
9+
import TextInput from "./TextInput";
910

1011
interface ActionButtonProps {
1112
appId: string;
@@ -74,11 +75,10 @@ const ActionButton: React.FC<ActionButtonProps> = ({
7475
handleParameterChange(param.name, parseFloat(e.target.value) || 0),
7576
},
7677
string: {
77-
type: "text" as const,
78+
type: "string" as const,
7879
value: String(currentValue),
7980
placeholder: param.description,
80-
onChange: (e: React.ChangeEvent<HTMLInputElement>) =>
81-
handleParameterChange(param.name, e.target.value),
81+
onChange: (value: string) => handleParameterChange(param.name, value),
8282
},
8383
};
8484

@@ -96,7 +96,15 @@ const ActionButton: React.FC<ActionButtonProps> = ({
9696
{param.name}
9797
{param.required && <span className="required">*</span>}
9898
</span>
99-
<input {...inputConfig} />
99+
{inputConfig.type === "string" ? (
100+
<TextInput
101+
value={inputConfig.value}
102+
onChange={inputConfig.onChange}
103+
placeholder={inputConfig.placeholder}
104+
/>
105+
) : (
106+
<input {...inputConfig} />
107+
)}
100108
{param.description && (
101109
<span className="parameter-description">{param.description}</span>
102110
)}
Lines changed: 80 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,104 +1,105 @@
1-
import React from 'react';
2-
import { useSettings } from '../hooks/useSettings';
3-
import ToggleSwitch from './ToggleSwitch';
4-
import Slider from './Slider';
1+
import React from "react";
2+
import { useSettings } from "../hooks/useSettings";
3+
import ToggleSwitch from "./ToggleSwitch";
4+
import Slider from "./Slider";
5+
import TextInput from "./TextInput";
56

6-
const AppSettings: React.FC = () => {
7-
const { allSettings, updateAppSetting } = useSettings();
7+
export const AppSettings: React.FC = () => {
8+
const { allSettings } = useSettings();
89

910
// Extract app settings (anything that's not 'system')
1011
const appSettingsEntries = Object.entries(allSettings).filter(
11-
([key]) => key !== 'system'
12+
([key]) => key !== "system"
1213
);
1314

1415
if (appSettingsEntries.length === 0) {
1516
return (
1617
<div className="settings-section">
1718
<h2>App Settings</h2>
18-
<p style={{ color: '#7f8c8d', fontStyle: 'italic' }}>
19-
No app settings registered yet. Apps will appear here when they register settings through the SDK.
19+
<p style={{ color: "#7f8c8d", fontStyle: "italic" }}>
20+
No app settings registered yet. Apps will appear here when they
21+
register settings through the SDK.
2022
</p>
2123
</div>
2224
);
2325
}
2426

25-
const handleAppSettingChange = (fullKey: string, settingKey: string, value: unknown) => {
26-
// Parse the full key to get appId and category
27-
const parts = fullKey.split('.');
28-
if (parts.length >= 2) {
29-
const appId = parts[0];
30-
const category = parts.slice(1).join('.');
31-
updateAppSetting(appId, category, settingKey, value);
32-
}
33-
};
34-
35-
const renderSettingControl = (fullKey: string, settingKey: string, value: unknown) => {
36-
// Try to determine the setting type from the value
37-
if (typeof value === 'boolean') {
38-
// Boolean setting
39-
return (
40-
<ToggleSwitch
41-
enabled={value}
42-
onChange={(enabled) => handleAppSettingChange(fullKey, settingKey, enabled)}
43-
/>
44-
);
45-
} else if (typeof value === 'number') {
46-
// Numeric setting - treat as slider with reasonable defaults
47-
const max = Math.max(100, value * 2); // Reasonable max
48-
return (
49-
<div className="setting-control">
50-
<Slider
51-
value={value}
52-
min={0}
53-
max={max}
54-
onChange={(newValue) => handleAppSettingChange(fullKey, settingKey, newValue)}
55-
/>
56-
<span className="status-display">{value}</span>
57-
</div>
58-
);
59-
} else {
60-
// String setting or unknown - just display for now
61-
return (
62-
<span className="status-display" style={{ maxWidth: '200px', overflow: 'hidden', textOverflow: 'ellipsis' }}>
63-
{String(value)}
64-
</span>
65-
);
66-
}
67-
};
68-
6927
return (
7028
<div className="settings-section">
7129
<h2>App Settings</h2>
72-
73-
{appSettingsEntries.map(([fullKey, settings]) => {
74-
const appName = fullKey.split('.')[0];
75-
const categoryName = fullKey.split('.').slice(1).join('.');
76-
77-
return (
78-
<div key={fullKey} style={{ marginBottom: '20px' }}>
79-
<h3 style={{
80-
color: '#34495e',
81-
fontSize: '1rem',
82-
marginBottom: '12px',
83-
borderBottom: '1px solid #ecf0f1',
84-
paddingBottom: '4px'
85-
}}>
86-
{appName} - {categoryName}
87-
</h3>
88-
89-
{Object.entries(settings).map(([settingKey, value]) => (
90-
<div key={settingKey} className="setting-item">
91-
<span className="setting-label">{settingKey}</span>
30+
{appSettingsEntries.map(([appId, settings]) => (
31+
<div key={appId} style={{ marginBottom: "20px" }}>
32+
<h3
33+
style={{
34+
color: "#34495e",
35+
fontSize: "1rem",
36+
marginBottom: "12px",
37+
borderBottom: "1px solid #ecf0f1",
38+
paddingBottom: "4px",
39+
}}
40+
>
41+
{appId}
42+
</h3>
43+
{Object.entries(settings).map(([category, categoryContents]) => {
44+
return Object.entries(
45+
categoryContents as Record<string, unknown>
46+
).map(([property, value]) => (
47+
<div key={`${category}.${property}`} className="setting-item">
48+
<span className="setting-label">{property}</span>
9249
<div className="setting-control">
93-
{renderSettingControl(fullKey, settingKey, value)}
50+
<SettingControlView
51+
appId={appId}
52+
category={category}
53+
property={property}
54+
value={value}
55+
/>
9456
</div>
9557
</div>
96-
))}
97-
</div>
98-
);
99-
})}
58+
));
59+
})}
60+
</div>
61+
))}
10062
</div>
10163
);
10264
};
10365

104-
export default AppSettings;
66+
const SettingControlView: React.FC<{
67+
appId: string;
68+
category: string;
69+
property: string;
70+
value: unknown;
71+
}> = ({ appId, category, property, value }) => {
72+
const { updateAppSetting } = useSettings();
73+
74+
if (typeof value === "boolean") {
75+
return (
76+
<ToggleSwitch
77+
enabled={value}
78+
onChange={(enabled) =>
79+
updateAppSetting(appId, category, property, enabled)
80+
}
81+
/>
82+
);
83+
} else if (typeof value === "number") {
84+
return (
85+
<div className="setting-control">
86+
<Slider
87+
value={value}
88+
onChange={(newValue) =>
89+
updateAppSetting(appId, category, property, newValue)
90+
}
91+
/>
92+
<span className="status-display">{value}</span>
93+
</div>
94+
);
95+
} else {
96+
return (
97+
<TextInput
98+
value={String(value)}
99+
onChange={(newValue) =>
100+
updateAppSetting(appId, category, property, newValue)
101+
}
102+
/>
103+
);
104+
}
105+
};

bridge-settings/react-app/src/components/Slider.tsx

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
1-
import React from 'react';
1+
import React from "react";
22

33
interface SliderProps {
44
value: number;
5-
min: number;
6-
max: number;
5+
min?: number;
6+
max?: number;
77
step?: number;
88
onChange: (value: number) => void;
99
disabled?: boolean;
1010
}
1111

12-
const Slider: React.FC<SliderProps> = ({
13-
value,
14-
min,
15-
max,
16-
step = 1,
17-
onChange,
18-
disabled = false
12+
const Slider: React.FC<SliderProps> = ({
13+
value,
14+
min,
15+
max,
16+
step = 1,
17+
onChange,
18+
disabled = false,
1919
}) => {
2020
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
2121
if (!disabled) {
@@ -38,4 +38,4 @@ const Slider: React.FC<SliderProps> = ({
3838
);
3939
};
4040

41-
export default Slider;
41+
export default Slider;
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import React from "react";
2+
3+
interface TextInputProps {
4+
value: string;
5+
onChange: (value: string) => void;
6+
placeholder?: string;
7+
disabled?: boolean;
8+
style?: React.CSSProperties;
9+
}
10+
11+
const TextInput: React.FC<TextInputProps> = ({
12+
value,
13+
onChange,
14+
placeholder = "",
15+
disabled = false,
16+
style = {},
17+
}) => {
18+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
19+
onChange(e.target.value);
20+
};
21+
22+
const defaultStyle: React.CSSProperties = {
23+
padding: "4px 8px",
24+
border: "1px solid #bdc3c7",
25+
borderRadius: "3px",
26+
fontSize: "0.9rem",
27+
minWidth: "120px",
28+
maxWidth: "200px",
29+
outline: "none",
30+
...style,
31+
};
32+
33+
return (
34+
<input
35+
type="text"
36+
value={value}
37+
onChange={handleChange}
38+
placeholder={placeholder}
39+
disabled={disabled}
40+
style={defaultStyle}
41+
onFocus={(e) => (e.target.style.borderColor = "#3498db")}
42+
onBlur={(e) => (e.target.style.borderColor = "#bdc3c7")}
43+
/>
44+
);
45+
};
46+
47+
export default TextInput;

bridge-settings/react-app/src/hooks/useSettings.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -127,18 +127,15 @@ export function useSettings() {
127127
}
128128
}, [lastMessage]);
129129

130-
const updateSystemSetting = (key: string, value: unknown) => {
131-
updateSetting("system", key, value);
132-
};
130+
const updateSystemSetting = (key: string, value: unknown) =>
131+
updateSetting("system", "main", key, value);
133132

134133
const updateAppSetting = (
135134
appId: string,
136135
category: string,
137136
key: string,
138137
value: unknown
139-
) => {
140-
updateSetting(`${appId}.${category}`, key, value);
141-
};
138+
) => updateSetting(appId, category, key, value);
142139

143140
const getSystemSettings = () => {
144141
return allSettings.system || {};

bridge-settings/react-app/src/services/websocketService.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,9 +118,15 @@ export class WebSocketService {
118118
}
119119
}
120120

121-
updateSetting(category: string, key: string, value: unknown): void {
121+
updateSetting(
122+
appId: string,
123+
category: string,
124+
key: string,
125+
value: unknown
126+
): void {
122127
this.send({
123128
type: "updateSetting",
129+
appId,
124130
category,
125131
key,
126132
value,
@@ -134,7 +140,11 @@ export class WebSocketService {
134140
});
135141
}
136142

137-
executeAction(appId: string, action: string, params: Record<string, unknown>): void {
143+
executeAction(
144+
appId: string,
145+
action: string,
146+
params: Record<string, unknown>
147+
): void {
138148
this.send({
139149
type: "executeAction",
140150
appId,

0 commit comments

Comments
 (0)