@adcops/autocore-react 3.3.9 → 3.3.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +58 -58
- package/additional-docs/AutoCoreTagContext.md +441 -441
- package/additional-docs/ButtonApiSpecs.md +48 -48
- package/additional-docs/GlobalEventEmitter.md +243 -243
- package/additional-docs/general_recommendations.md +22 -22
- package/additional-docs/react_performance_notes.md +94 -94
- package/dist/assets/svg/blockly_logo.svg +82 -82
- package/dist/assets/svg/distance.svg +40 -40
- package/dist/assets/svg/python_logo.svg +246 -246
- package/dist/assets/svg/rotation_ccw.svg +50 -50
- package/dist/assets/svg/rotation_ccw_a.svg +57 -57
- package/dist/assets/svg/rotation_ccw_b.svg +57 -57
- package/dist/assets/svg/rotation_ccw_c.svg +57 -57
- package/dist/assets/svg/rotation_cw.svg +49 -49
- package/dist/assets/svg/rotation_cw_a.svg +30 -30
- package/dist/assets/svg/rotation_cw_b.svg +30 -30
- package/dist/assets/svg/rotation_cw_c.svg +30 -30
- package/dist/assets/svg/speed.svg +39 -39
- package/dist/components/BlocklyEditor.css +93 -93
- package/dist/components/JogPanel.css +41 -41
- package/dist/components/ProgressBarWithValue.css +27 -27
- package/dist/components/ValueIndicator.css +31 -31
- package/dist/components/osk.css +123 -123
- package/dist/core/AutoCoreTagContext.d.ts.map +1 -1
- package/dist/core/AutoCoreTagContext.js +1 -1
- package/dist/hub/HubBase.d.ts +3 -3
- package/dist/hub/HubBase.d.ts.map +1 -1
- package/dist/hub/HubBase.js +1 -1
- package/package.json +104 -104
- package/readme.md +343 -343
- package/src/assets/BlocklyLogo.tsx +27 -27
- package/src/assets/Distance.tsx +18 -18
- package/src/assets/JogLong.tsx +13 -13
- package/src/assets/JogMedium.tsx +13 -13
- package/src/assets/JogShort.tsx +13 -13
- package/src/assets/PythonLogo.tsx +83 -83
- package/src/assets/Rotation3D.tsx +13 -13
- package/src/assets/RotationCcw.tsx +33 -33
- package/src/assets/RotationCcwA.tsx +45 -45
- package/src/assets/RotationCcwB.tsx +45 -45
- package/src/assets/RotationCcwC.tsx +45 -45
- package/src/assets/RotationCw.tsx +31 -31
- package/src/assets/RotationCwA.tsx +42 -42
- package/src/assets/RotationCwB.tsx +42 -42
- package/src/assets/RotationCwC.tsx +42 -42
- package/src/assets/Run.tsx +13 -13
- package/src/assets/Speed.tsx +18 -18
- package/src/assets/SpeedFast.tsx +13 -13
- package/src/assets/SpeedMedium.tsx +13 -13
- package/src/assets/SpeedNone.tsx +13 -13
- package/src/assets/SpeedSlow.tsx +13 -13
- package/src/assets/Walk.tsx +13 -13
- package/src/assets/index.ts +22 -22
- package/src/assets/svg/blockly_logo.svg +82 -82
- package/src/assets/svg/distance.svg +40 -40
- package/src/assets/svg/python_logo.svg +246 -246
- package/src/assets/svg/rotation_ccw.svg +50 -50
- package/src/assets/svg/rotation_ccw_a.svg +57 -57
- package/src/assets/svg/rotation_ccw_b.svg +57 -57
- package/src/assets/svg/rotation_ccw_c.svg +57 -57
- package/src/assets/svg/rotation_cw.svg +49 -49
- package/src/assets/svg/rotation_cw_a.svg +30 -30
- package/src/assets/svg/rotation_cw_b.svg +30 -30
- package/src/assets/svg/rotation_cw_c.svg +30 -30
- package/src/assets/svg/speed.svg +39 -39
- package/src/components/AutoCoreDevPanel.tsx +414 -414
- package/src/components/BlocklyEditor.css +93 -93
- package/src/components/BlocklyEditor.tsx +609 -609
- package/src/components/CodeEditor.tsx +155 -155
- package/src/components/FileList.tsx +390 -390
- package/src/components/FileSelect.tsx +128 -128
- package/src/components/FitText.tsx +35 -35
- package/src/components/Indicator.tsx +188 -188
- package/src/components/IndicatorButton.tsx +214 -214
- package/src/components/IndicatorRect.tsx +172 -172
- package/src/components/JogPanel.css +41 -41
- package/src/components/JogPanel.tsx +461 -461
- package/src/components/Lamp.tsx +243 -243
- package/src/components/Osk.tsx +192 -192
- package/src/components/OskDialog.tsx +164 -164
- package/src/components/ProgressBarWithValue.css +27 -27
- package/src/components/ProgressBarWithValue.tsx +48 -48
- package/src/components/TextInput.tsx +195 -195
- package/src/components/ToggleGroup.tsx +322 -322
- package/src/components/ValueDisplay.tsx +236 -236
- package/src/components/ValueIndicator.css +31 -31
- package/src/components/ValueIndicator.tsx +135 -135
- package/src/components/ValueInput.tsx +368 -368
- package/src/components/osk.css +123 -123
- package/src/core/ActionMode.ts +19 -19
- package/src/core/AutoCoreTagContext.tsx +625 -614
- package/src/core/AutoCoreTagTypes.ts +334 -334
- package/src/core/CoreStreamTypes.ts +512 -512
- package/src/core/EventEmitterContext.tsx +434 -434
- package/src/core/IndicatorButtonState.ts +34 -34
- package/src/core/IndicatorColor.ts +35 -35
- package/src/core/MaskPatterns.ts +87 -87
- package/src/core/NumerableTypes.ts +80 -80
- package/src/core/PositionContext.ts +59 -59
- package/src/core/UniqueId.ts +41 -41
- package/src/core/ValueSimulator.ts +166 -166
- package/src/core/hoc.tsx +65 -65
- package/src/hooks/adsHooks.tsx +287 -287
- package/src/hooks/commandHooks.tsx +300 -300
- package/src/hooks/index.ts +12 -12
- package/src/hooks/useAutoCoreTag.ts +103 -103
- package/src/hooks/useScaledValue.tsx +99 -99
- package/src/hub/CommandMessage.ts +89 -89
- package/src/hub/DebugPanel.ts +307 -307
- package/src/hub/HubBase.ts +249 -236
- package/src/hub/HubSimulate.ts +124 -124
- package/src/hub/HubTauri.ts +140 -140
- package/src/hub/HubWebSocket.ts +250 -250
- package/src/hub/debug.ts +211 -211
- package/src/hub/index.ts +81 -81
- package/src/themes/adc-dark/_extensions.scss +166 -166
- package/src/themes/adc-dark/_variables.scss +913 -913
- package/src/themes/adc-dark/blue/_fonts.scss +23 -23
- package/src/themes/adc-dark/blue/adc_theme.scss +31 -31
- package/src/themes/adc-dark/blue/theme.scss +14 -14
- package/src/themes/theme-base/_colors.scss +17 -17
- package/src/themes/theme-base/_common.scss +74 -74
- package/src/themes/theme-base/_components.scss +111 -111
- package/src/themes/theme-base/_mixins.scss +243 -243
- package/src/themes/theme-base/components/button/_button.scss +644 -644
- package/src/themes/theme-base/components/button/_speeddial.scss +91 -91
- package/src/themes/theme-base/components/button/_splitbutton.scss +358 -358
- package/src/themes/theme-base/components/data/_carousel.scss +39 -39
- package/src/themes/theme-base/components/data/_datascroller.scss +47 -47
- package/src/themes/theme-base/components/data/_datatable.scss +388 -388
- package/src/themes/theme-base/components/data/_dataview.scss +47 -47
- package/src/themes/theme-base/components/data/_filter.scss +137 -137
- package/src/themes/theme-base/components/data/_orderlist.scss +86 -86
- package/src/themes/theme-base/components/data/_organizationchart.scss +50 -50
- package/src/themes/theme-base/components/data/_paginator.scss +91 -91
- package/src/themes/theme-base/components/data/_picklist.scss +73 -73
- package/src/themes/theme-base/components/data/_timeline.scss +38 -38
- package/src/themes/theme-base/components/data/_tree.scss +184 -184
- package/src/themes/theme-base/components/data/_treetable.scss +431 -431
- package/src/themes/theme-base/components/file/_fileupload.scss +41 -41
- package/src/themes/theme-base/components/input/_autocomplete.scss +94 -94
- package/src/themes/theme-base/components/input/_calendar.scss +251 -251
- package/src/themes/theme-base/components/input/_cascadeselect.scss +107 -107
- package/src/themes/theme-base/components/input/_checkbox.scss +181 -181
- package/src/themes/theme-base/components/input/_chips.scss +102 -102
- package/src/themes/theme-base/components/input/_colorpicker.scss +17 -17
- package/src/themes/theme-base/components/input/_dropdown.scss +252 -252
- package/src/themes/theme-base/components/input/_editor.scss +122 -122
- package/src/themes/theme-base/components/input/_iconfield.scss +9 -9
- package/src/themes/theme-base/components/input/_inputgroup.scss +74 -74
- package/src/themes/theme-base/components/input/_inputicon.scss +14 -14
- package/src/themes/theme-base/components/input/_inputnumber.scss +4 -4
- package/src/themes/theme-base/components/input/_inputotp.scss +10 -10
- package/src/themes/theme-base/components/input/_inputswitch.scss +99 -99
- package/src/themes/theme-base/components/input/_inputtext.scss +101 -101
- package/src/themes/theme-base/components/input/_listbox.scss +138 -138
- package/src/themes/theme-base/components/input/_mention.scss +30 -30
- package/src/themes/theme-base/components/input/_multiselect.scss +278 -278
- package/src/themes/theme-base/components/input/_password.scss +32 -32
- package/src/themes/theme-base/components/input/_radiobutton.scss +169 -169
- package/src/themes/theme-base/components/input/_rating.scss +80 -80
- package/src/themes/theme-base/components/input/_selectbutton.scss +49 -49
- package/src/themes/theme-base/components/input/_slider.scss +49 -49
- package/src/themes/theme-base/components/input/_togglebutton.scss +99 -99
- package/src/themes/theme-base/components/input/_treeselect.scss +151 -151
- package/src/themes/theme-base/components/input/_tristatecheckbox.scss +46 -46
- package/src/themes/theme-base/components/menu/_breadcrumb.scss +42 -42
- package/src/themes/theme-base/components/menu/_contextmenu.scss +39 -39
- package/src/themes/theme-base/components/menu/_dock.scss +109 -109
- package/src/themes/theme-base/components/menu/_megamenu.scss +141 -141
- package/src/themes/theme-base/components/menu/_menu.scss +33 -33
- package/src/themes/theme-base/components/menu/_menubar.scss +216 -216
- package/src/themes/theme-base/components/menu/_panelmenu.scss +153 -153
- package/src/themes/theme-base/components/menu/_slidemenu.scss +60 -60
- package/src/themes/theme-base/components/menu/_steps.scss +57 -57
- package/src/themes/theme-base/components/menu/_tabmenu.scss +50 -50
- package/src/themes/theme-base/components/menu/_tieredmenu.scss +43 -43
- package/src/themes/theme-base/components/messages/_inlinemessage.scss +69 -69
- package/src/themes/theme-base/components/messages/_message.scss +107 -107
- package/src/themes/theme-base/components/messages/_toast.scss +100 -100
- package/src/themes/theme-base/components/misc/_avatar.scss +33 -33
- package/src/themes/theme-base/components/misc/_badge.scss +76 -76
- package/src/themes/theme-base/components/misc/_chip.scss +38 -38
- package/src/themes/theme-base/components/misc/_inplace.scss +17 -17
- package/src/themes/theme-base/components/misc/_metergroup.scss +80 -80
- package/src/themes/theme-base/components/misc/_progressbar.scss +17 -17
- package/src/themes/theme-base/components/misc/_scrolltop.scss +24 -24
- package/src/themes/theme-base/components/misc/_skeleton.scss +7 -7
- package/src/themes/theme-base/components/misc/_tag.scss +39 -39
- package/src/themes/theme-base/components/misc/_terminal.scss +12 -12
- package/src/themes/theme-base/components/multimedia/_galleria.scss +153 -153
- package/src/themes/theme-base/components/multimedia/_image.scss +53 -53
- package/src/themes/theme-base/components/overlay/_confirmpopup.scss +72 -72
- package/src/themes/theme-base/components/overlay/_dialog.scss +78 -78
- package/src/themes/theme-base/components/overlay/_overlaypanel.scss +64 -64
- package/src/themes/theme-base/components/overlay/_sidebar.scss +23 -23
- package/src/themes/theme-base/components/overlay/_tooltip.scss +33 -33
- package/src/themes/theme-base/components/panel/_accordion.scss +118 -118
- package/src/themes/theme-base/components/panel/_card.scss +30 -30
- package/src/themes/theme-base/components/panel/_divider.scss +30 -30
- package/src/themes/theme-base/components/panel/_fieldset.scss +47 -47
- package/src/themes/theme-base/components/panel/_panel.scss +47 -47
- package/src/themes/theme-base/components/panel/_scrollpanel.scss +10 -10
- package/src/themes/theme-base/components/panel/_splitter.scss +23 -23
- package/src/themes/theme-base/components/panel/_stepper.scss +136 -136
- package/src/themes/theme-base/components/panel/_tabview.scss +147 -147
- package/src/themes/theme-base/components/panel/_toolbar.scss +11 -11
- package/terser.config.cjs +25 -25
- package/todo.md +18 -18
- package/tools/build-themes.cjs +65 -65
- package/tools/copy-distribution-files.cjs +77 -77
- package/tools/minify.cjs +55 -55
- package/tsconfig.json +48 -48
- package/typedoc.json +12 -12
- package/.claude/settings.local.json +0 -7
|
@@ -1,214 +1,214 @@
|
|
|
1
|
-
import React, { useCallback, useContext, useEffect, useState } from "react";
|
|
2
|
-
import { Button, type ButtonProps } from "primereact/button";
|
|
3
|
-
import {
|
|
4
|
-
EventEmitterContext,
|
|
5
|
-
type EventEmitterContextType,
|
|
6
|
-
} from "../core/EventEmitterContext";
|
|
7
|
-
import { IndicatorColor } from "../core/IndicatorColor";
|
|
8
|
-
export { IndicatorColor };
|
|
9
|
-
import { ActionMode } from "../core/ActionMode";
|
|
10
|
-
export { ActionMode };
|
|
11
|
-
|
|
12
|
-
export declare type IndicatorButtonOptionsType =
|
|
13
|
-
| string[]
|
|
14
|
-
| undefined[]
|
|
15
|
-
| undefined;
|
|
16
|
-
|
|
17
|
-
export interface IndicatorButtonProps extends Omit<ButtonProps, "value"> {
|
|
18
|
-
/** Topic name to monitor for state indication. If `value` is not undefined, that value is used instead. */
|
|
19
|
-
topic?: string;
|
|
20
|
-
|
|
21
|
-
/** State to be displayed. */
|
|
22
|
-
value?: boolean | number | string;
|
|
23
|
-
|
|
24
|
-
/** Color for the button when the state is TRUE. */
|
|
25
|
-
onColor?: IndicatorColor;
|
|
26
|
-
|
|
27
|
-
/** Color for the button when the state is FALSE. */
|
|
28
|
-
offColor?: IndicatorColor;
|
|
29
|
-
|
|
30
|
-
/** Labels for available options (see docs). */
|
|
31
|
-
options?: IndicatorButtonOptionsType;
|
|
32
|
-
|
|
33
|
-
/** Base icon class (overridden by onIcon/offIcon). */
|
|
34
|
-
icon?: string;
|
|
35
|
-
|
|
36
|
-
/** Icon when TRUE. */
|
|
37
|
-
onIcon?: string;
|
|
38
|
-
|
|
39
|
-
/** Icon when FALSE. */
|
|
40
|
-
offIcon?: string;
|
|
41
|
-
|
|
42
|
-
/** Name of the command to invoke on button events. */
|
|
43
|
-
command?: string;
|
|
44
|
-
|
|
45
|
-
/** Optional topic sent with the command payload. */
|
|
46
|
-
commandTopic?: string;
|
|
47
|
-
|
|
48
|
-
/** Optional args sent with the command payload. */
|
|
49
|
-
commandArgs?: any;
|
|
50
|
-
|
|
51
|
-
/** Input mode (Tap, Pressed, Released). */
|
|
52
|
-
actionMode?: ActionMode;
|
|
53
|
-
|
|
54
|
-
/** Inverts values for Tap/Pressed/Released dispatches. */
|
|
55
|
-
invert?: boolean;
|
|
56
|
-
|
|
57
|
-
/** Directly disables the button (replaces old disableTopic). */
|
|
58
|
-
disabled?: boolean;
|
|
59
|
-
|
|
60
|
-
/** Hides the button (replaces old invisibleTopic/hiddenTopic). */
|
|
61
|
-
hidden?: boolean;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const toPrimeIcon = (cls?: string) =>
|
|
65
|
-
cls ? (cls.includes("pi ") ? cls : `pi ${cls}`) : undefined;
|
|
66
|
-
|
|
67
|
-
export const IndicatorButton: React.FC<IndicatorButtonProps> = ({
|
|
68
|
-
topic,
|
|
69
|
-
value,
|
|
70
|
-
onColor = IndicatorColor.IndicatorGreen,
|
|
71
|
-
offColor = "gray",
|
|
72
|
-
options,
|
|
73
|
-
icon,
|
|
74
|
-
onIcon,
|
|
75
|
-
offIcon,
|
|
76
|
-
command,
|
|
77
|
-
commandTopic,
|
|
78
|
-
commandArgs,
|
|
79
|
-
actionMode,
|
|
80
|
-
invert,
|
|
81
|
-
className,
|
|
82
|
-
label,
|
|
83
|
-
style,
|
|
84
|
-
disabled,
|
|
85
|
-
hidden,
|
|
86
|
-
...restProps
|
|
87
|
-
}) => {
|
|
88
|
-
const { subscribe, unsubscribe, dispatch } = useContext(
|
|
89
|
-
EventEmitterContext
|
|
90
|
-
) as EventEmitterContextType;
|
|
91
|
-
|
|
92
|
-
const [currentValue, setCurrentValue] = useState<
|
|
93
|
-
boolean | number | string | undefined
|
|
94
|
-
>(undefined);
|
|
95
|
-
const [isPressed, setIsPressed] = useState(false);
|
|
96
|
-
|
|
97
|
-
// Subscribe using autocore-react pattern: subscribe() returns a token; unsubscribe(token)
|
|
98
|
-
useEffect(() => {
|
|
99
|
-
if (!topic || !subscribe) return;
|
|
100
|
-
|
|
101
|
-
const token = subscribe(topic, (val: any) => setCurrentValue(val));
|
|
102
|
-
|
|
103
|
-
return () => {
|
|
104
|
-
try {
|
|
105
|
-
if (unsubscribe && token !== undefined) {
|
|
106
|
-
unsubscribe(token);
|
|
107
|
-
}
|
|
108
|
-
} catch {
|
|
109
|
-
/* no-op */
|
|
110
|
-
}
|
|
111
|
-
};
|
|
112
|
-
}, [topic, subscribe, unsubscribe]);
|
|
113
|
-
|
|
114
|
-
// Decide which value to show (prop wins over topic-driven value)
|
|
115
|
-
const displayValue = value !== undefined ? value : currentValue;
|
|
116
|
-
|
|
117
|
-
// Resolve label based on options + value (supports 2-state and multi-state)
|
|
118
|
-
const resolveLabel = (): string | undefined => {
|
|
119
|
-
if (!options || options.length === 0) return label;
|
|
120
|
-
|
|
121
|
-
// Multi-state: number indexes, string match-by-equality
|
|
122
|
-
if (options.length > 2) {
|
|
123
|
-
if (typeof displayValue === "number") {
|
|
124
|
-
const idx = Math.trunc(displayValue);
|
|
125
|
-
if (idx >= 0 && idx < options.length && options[idx] != null) {
|
|
126
|
-
return String(options[idx]);
|
|
127
|
-
}
|
|
128
|
-
} else if (typeof displayValue === "string") {
|
|
129
|
-
const idx = options.findIndex((s) => s === displayValue);
|
|
130
|
-
if (idx >= 0) return String(options[idx]);
|
|
131
|
-
}
|
|
132
|
-
// Fallback to provided label if no match
|
|
133
|
-
return label;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Two-state (or one): falsey -> options[0], truthy -> options[1]
|
|
137
|
-
const pick = displayValue ? options[1] : options[0];
|
|
138
|
-
if (pick !== undefined && pick !== null && String(pick).length > 0) {
|
|
139
|
-
return String(pick);
|
|
140
|
-
}
|
|
141
|
-
return label;
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
const btnLabel = resolveLabel();
|
|
145
|
-
|
|
146
|
-
// Icon selection
|
|
147
|
-
const chosenIcon = toPrimeIcon((displayValue ? onIcon : offIcon) ?? icon);
|
|
148
|
-
|
|
149
|
-
// Color selection (undefined -> invalid color)
|
|
150
|
-
let backgroundColor: string | IndicatorColor | undefined = displayValue
|
|
151
|
-
? onColor
|
|
152
|
-
: offColor;
|
|
153
|
-
if (displayValue === undefined) {
|
|
154
|
-
backgroundColor = IndicatorColor.IndicatorInvalid;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const dispatchCommand = useCallback(
|
|
158
|
-
(raw: boolean) => {
|
|
159
|
-
if (!command || !dispatch) return;
|
|
160
|
-
const payloadValue = invert ? !raw : raw;
|
|
161
|
-
const payload = {
|
|
162
|
-
topic: commandTopic,
|
|
163
|
-
value: payloadValue,
|
|
164
|
-
...(commandArgs ?? {}),
|
|
165
|
-
};
|
|
166
|
-
dispatch({ topic: command, payload });
|
|
167
|
-
},
|
|
168
|
-
[command, dispatch, invert, commandArgs, commandTopic]
|
|
169
|
-
);
|
|
170
|
-
|
|
171
|
-
const handleOnPressed = useCallback(() => {
|
|
172
|
-
if (isPressed) return;
|
|
173
|
-
setIsPressed(true);
|
|
174
|
-
|
|
175
|
-
if (actionMode === ActionMode.Tap || actionMode === ActionMode.Pressed) {
|
|
176
|
-
dispatchCommand(true);
|
|
177
|
-
}
|
|
178
|
-
}, [isPressed, actionMode, dispatchCommand]);
|
|
179
|
-
|
|
180
|
-
const handleOnReleased = useCallback(() => {
|
|
181
|
-
if (!isPressed) return;
|
|
182
|
-
setIsPressed(false);
|
|
183
|
-
|
|
184
|
-
if (actionMode === ActionMode.Tap) {
|
|
185
|
-
dispatchCommand(false);
|
|
186
|
-
} else if (actionMode === ActionMode.Released) {
|
|
187
|
-
dispatchCommand(true);
|
|
188
|
-
}
|
|
189
|
-
}, [isPressed, actionMode, dispatchCommand]);
|
|
190
|
-
|
|
191
|
-
const mergedStyle: React.CSSProperties = {
|
|
192
|
-
color: "white",
|
|
193
|
-
backgroundColor,
|
|
194
|
-
display: hidden ? "none" : "block",
|
|
195
|
-
...(style ?? {}),
|
|
196
|
-
};
|
|
197
|
-
|
|
198
|
-
return (
|
|
199
|
-
<Button
|
|
200
|
-
{...restProps}
|
|
201
|
-
label={btnLabel}
|
|
202
|
-
icon={chosenIcon}
|
|
203
|
-
className={className}
|
|
204
|
-
style={mergedStyle}
|
|
205
|
-
disabled={!!disabled}
|
|
206
|
-
onMouseDown={handleOnPressed}
|
|
207
|
-
onTouchStart={handleOnPressed}
|
|
208
|
-
onMouseUp={handleOnReleased}
|
|
209
|
-
onTouchEnd={handleOnReleased}
|
|
210
|
-
/>
|
|
211
|
-
);
|
|
212
|
-
};
|
|
213
|
-
|
|
214
|
-
export default IndicatorButton;
|
|
1
|
+
import React, { useCallback, useContext, useEffect, useState } from "react";
|
|
2
|
+
import { Button, type ButtonProps } from "primereact/button";
|
|
3
|
+
import {
|
|
4
|
+
EventEmitterContext,
|
|
5
|
+
type EventEmitterContextType,
|
|
6
|
+
} from "../core/EventEmitterContext";
|
|
7
|
+
import { IndicatorColor } from "../core/IndicatorColor";
|
|
8
|
+
export { IndicatorColor };
|
|
9
|
+
import { ActionMode } from "../core/ActionMode";
|
|
10
|
+
export { ActionMode };
|
|
11
|
+
|
|
12
|
+
export declare type IndicatorButtonOptionsType =
|
|
13
|
+
| string[]
|
|
14
|
+
| undefined[]
|
|
15
|
+
| undefined;
|
|
16
|
+
|
|
17
|
+
export interface IndicatorButtonProps extends Omit<ButtonProps, "value"> {
|
|
18
|
+
/** Topic name to monitor for state indication. If `value` is not undefined, that value is used instead. */
|
|
19
|
+
topic?: string;
|
|
20
|
+
|
|
21
|
+
/** State to be displayed. */
|
|
22
|
+
value?: boolean | number | string;
|
|
23
|
+
|
|
24
|
+
/** Color for the button when the state is TRUE. */
|
|
25
|
+
onColor?: IndicatorColor;
|
|
26
|
+
|
|
27
|
+
/** Color for the button when the state is FALSE. */
|
|
28
|
+
offColor?: IndicatorColor;
|
|
29
|
+
|
|
30
|
+
/** Labels for available options (see docs). */
|
|
31
|
+
options?: IndicatorButtonOptionsType;
|
|
32
|
+
|
|
33
|
+
/** Base icon class (overridden by onIcon/offIcon). */
|
|
34
|
+
icon?: string;
|
|
35
|
+
|
|
36
|
+
/** Icon when TRUE. */
|
|
37
|
+
onIcon?: string;
|
|
38
|
+
|
|
39
|
+
/** Icon when FALSE. */
|
|
40
|
+
offIcon?: string;
|
|
41
|
+
|
|
42
|
+
/** Name of the command to invoke on button events. */
|
|
43
|
+
command?: string;
|
|
44
|
+
|
|
45
|
+
/** Optional topic sent with the command payload. */
|
|
46
|
+
commandTopic?: string;
|
|
47
|
+
|
|
48
|
+
/** Optional args sent with the command payload. */
|
|
49
|
+
commandArgs?: any;
|
|
50
|
+
|
|
51
|
+
/** Input mode (Tap, Pressed, Released). */
|
|
52
|
+
actionMode?: ActionMode;
|
|
53
|
+
|
|
54
|
+
/** Inverts values for Tap/Pressed/Released dispatches. */
|
|
55
|
+
invert?: boolean;
|
|
56
|
+
|
|
57
|
+
/** Directly disables the button (replaces old disableTopic). */
|
|
58
|
+
disabled?: boolean;
|
|
59
|
+
|
|
60
|
+
/** Hides the button (replaces old invisibleTopic/hiddenTopic). */
|
|
61
|
+
hidden?: boolean;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const toPrimeIcon = (cls?: string) =>
|
|
65
|
+
cls ? (cls.includes("pi ") ? cls : `pi ${cls}`) : undefined;
|
|
66
|
+
|
|
67
|
+
export const IndicatorButton: React.FC<IndicatorButtonProps> = ({
|
|
68
|
+
topic,
|
|
69
|
+
value,
|
|
70
|
+
onColor = IndicatorColor.IndicatorGreen,
|
|
71
|
+
offColor = "gray",
|
|
72
|
+
options,
|
|
73
|
+
icon,
|
|
74
|
+
onIcon,
|
|
75
|
+
offIcon,
|
|
76
|
+
command,
|
|
77
|
+
commandTopic,
|
|
78
|
+
commandArgs,
|
|
79
|
+
actionMode,
|
|
80
|
+
invert,
|
|
81
|
+
className,
|
|
82
|
+
label,
|
|
83
|
+
style,
|
|
84
|
+
disabled,
|
|
85
|
+
hidden,
|
|
86
|
+
...restProps
|
|
87
|
+
}) => {
|
|
88
|
+
const { subscribe, unsubscribe, dispatch } = useContext(
|
|
89
|
+
EventEmitterContext
|
|
90
|
+
) as EventEmitterContextType;
|
|
91
|
+
|
|
92
|
+
const [currentValue, setCurrentValue] = useState<
|
|
93
|
+
boolean | number | string | undefined
|
|
94
|
+
>(undefined);
|
|
95
|
+
const [isPressed, setIsPressed] = useState(false);
|
|
96
|
+
|
|
97
|
+
// Subscribe using autocore-react pattern: subscribe() returns a token; unsubscribe(token)
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
if (!topic || !subscribe) return;
|
|
100
|
+
|
|
101
|
+
const token = subscribe(topic, (val: any) => setCurrentValue(val));
|
|
102
|
+
|
|
103
|
+
return () => {
|
|
104
|
+
try {
|
|
105
|
+
if (unsubscribe && token !== undefined) {
|
|
106
|
+
unsubscribe(token);
|
|
107
|
+
}
|
|
108
|
+
} catch {
|
|
109
|
+
/* no-op */
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
}, [topic, subscribe, unsubscribe]);
|
|
113
|
+
|
|
114
|
+
// Decide which value to show (prop wins over topic-driven value)
|
|
115
|
+
const displayValue = value !== undefined ? value : currentValue;
|
|
116
|
+
|
|
117
|
+
// Resolve label based on options + value (supports 2-state and multi-state)
|
|
118
|
+
const resolveLabel = (): string | undefined => {
|
|
119
|
+
if (!options || options.length === 0) return label;
|
|
120
|
+
|
|
121
|
+
// Multi-state: number indexes, string match-by-equality
|
|
122
|
+
if (options.length > 2) {
|
|
123
|
+
if (typeof displayValue === "number") {
|
|
124
|
+
const idx = Math.trunc(displayValue);
|
|
125
|
+
if (idx >= 0 && idx < options.length && options[idx] != null) {
|
|
126
|
+
return String(options[idx]);
|
|
127
|
+
}
|
|
128
|
+
} else if (typeof displayValue === "string") {
|
|
129
|
+
const idx = options.findIndex((s) => s === displayValue);
|
|
130
|
+
if (idx >= 0) return String(options[idx]);
|
|
131
|
+
}
|
|
132
|
+
// Fallback to provided label if no match
|
|
133
|
+
return label;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Two-state (or one): falsey -> options[0], truthy -> options[1]
|
|
137
|
+
const pick = displayValue ? options[1] : options[0];
|
|
138
|
+
if (pick !== undefined && pick !== null && String(pick).length > 0) {
|
|
139
|
+
return String(pick);
|
|
140
|
+
}
|
|
141
|
+
return label;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const btnLabel = resolveLabel();
|
|
145
|
+
|
|
146
|
+
// Icon selection
|
|
147
|
+
const chosenIcon = toPrimeIcon((displayValue ? onIcon : offIcon) ?? icon);
|
|
148
|
+
|
|
149
|
+
// Color selection (undefined -> invalid color)
|
|
150
|
+
let backgroundColor: string | IndicatorColor | undefined = displayValue
|
|
151
|
+
? onColor
|
|
152
|
+
: offColor;
|
|
153
|
+
if (displayValue === undefined) {
|
|
154
|
+
backgroundColor = IndicatorColor.IndicatorInvalid;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const dispatchCommand = useCallback(
|
|
158
|
+
(raw: boolean) => {
|
|
159
|
+
if (!command || !dispatch) return;
|
|
160
|
+
const payloadValue = invert ? !raw : raw;
|
|
161
|
+
const payload = {
|
|
162
|
+
topic: commandTopic,
|
|
163
|
+
value: payloadValue,
|
|
164
|
+
...(commandArgs ?? {}),
|
|
165
|
+
};
|
|
166
|
+
dispatch({ topic: command, payload });
|
|
167
|
+
},
|
|
168
|
+
[command, dispatch, invert, commandArgs, commandTopic]
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
const handleOnPressed = useCallback(() => {
|
|
172
|
+
if (isPressed) return;
|
|
173
|
+
setIsPressed(true);
|
|
174
|
+
|
|
175
|
+
if (actionMode === ActionMode.Tap || actionMode === ActionMode.Pressed) {
|
|
176
|
+
dispatchCommand(true);
|
|
177
|
+
}
|
|
178
|
+
}, [isPressed, actionMode, dispatchCommand]);
|
|
179
|
+
|
|
180
|
+
const handleOnReleased = useCallback(() => {
|
|
181
|
+
if (!isPressed) return;
|
|
182
|
+
setIsPressed(false);
|
|
183
|
+
|
|
184
|
+
if (actionMode === ActionMode.Tap) {
|
|
185
|
+
dispatchCommand(false);
|
|
186
|
+
} else if (actionMode === ActionMode.Released) {
|
|
187
|
+
dispatchCommand(true);
|
|
188
|
+
}
|
|
189
|
+
}, [isPressed, actionMode, dispatchCommand]);
|
|
190
|
+
|
|
191
|
+
const mergedStyle: React.CSSProperties = {
|
|
192
|
+
color: "white",
|
|
193
|
+
backgroundColor,
|
|
194
|
+
display: hidden ? "none" : "block",
|
|
195
|
+
...(style ?? {}),
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
return (
|
|
199
|
+
<Button
|
|
200
|
+
{...restProps}
|
|
201
|
+
label={btnLabel}
|
|
202
|
+
icon={chosenIcon}
|
|
203
|
+
className={className}
|
|
204
|
+
style={mergedStyle}
|
|
205
|
+
disabled={!!disabled}
|
|
206
|
+
onMouseDown={handleOnPressed}
|
|
207
|
+
onTouchStart={handleOnPressed}
|
|
208
|
+
onMouseUp={handleOnReleased}
|
|
209
|
+
onTouchEnd={handleOnReleased}
|
|
210
|
+
/>
|
|
211
|
+
);
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
export default IndicatorButton;
|