@adcops/autocore-react 3.0.8 → 3.0.12

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.
@@ -2,18 +2,18 @@
2
2
  * Copyright (C) 2024 Automated Design Corp.. All Rights Reserved.
3
3
  * Created Date: 2024-03-20 13:05:42
4
4
  * -----
5
- * Last Modified: 2024-03-21 09:37:17
5
+ * Last Modified: 2024-04-16 19:06:17
6
6
  * -----
7
7
  *
8
8
  */
9
9
 
10
10
 
11
- import React from "react";
11
+ import React, { createRef } from "react";
12
12
 
13
- import { InputNumber} from 'primereact/inputnumber';
13
+ import { InputNumber } from 'primereact/inputnumber';
14
14
  import { Button } from "primereact/button";
15
15
 
16
- import { EventEmitterContext } from "../core/EventEmitterContext.js";
16
+ import { EventEmitterContext } from "../core/EventEmitterContext";
17
17
 
18
18
  /**
19
19
  * Properties of the ValueInput component.
@@ -23,23 +23,23 @@ interface ValueInputProps {
23
23
  /**
24
24
  * The label for the ValueInput field.
25
25
  */
26
- label : React.ReactNode | undefined;
26
+ label: React.ReactNode | undefined;
27
27
 
28
28
 
29
29
  /**
30
30
  * The value for the field.
31
31
  */
32
- value : number | null;
32
+ value: number | null;
33
33
 
34
34
  /**
35
35
  * Minimum value for the field.
36
36
  */
37
- min : number | undefined;
37
+ min: number | undefined;
38
38
 
39
39
  /**
40
40
  * Minimum value for the field.
41
41
  */
42
- max : number | undefined;
42
+ max: number | undefined;
43
43
 
44
44
  /**
45
45
  * Minimum number of decimal points. The user will not
@@ -49,7 +49,7 @@ interface ValueInputProps {
49
49
  * the component will throw an error.
50
50
  * @default 0
51
51
  */
52
- minPrecision : number | undefined;
52
+ minPrecision: number | undefined;
53
53
 
54
54
 
55
55
  /**
@@ -58,7 +58,7 @@ interface ValueInputProps {
58
58
  * the component will throw an error.
59
59
  * @default 3
60
60
  */
61
- maxPrecision : number | undefined;
61
+ maxPrecision: number | undefined;
62
62
 
63
63
 
64
64
  /**
@@ -67,7 +67,7 @@ interface ValueInputProps {
67
67
  * currency type using the currency property.
68
68
  * @default "decimal"
69
69
  */
70
- mode : "currency" | "decimal" | undefined;
70
+ mode: "currency" | "decimal" | undefined;
71
71
 
72
72
 
73
73
  /**
@@ -78,19 +78,19 @@ interface ValueInputProps {
78
78
  *
79
79
  * @default "USD"
80
80
  */
81
- currency : string;
82
-
81
+ currency: string;
82
+
83
83
  /**
84
84
  * An optional prefix before the value of the field.
85
85
  * Unlike the TextInput control, this prefix is internal to the field.
86
86
  */
87
- prefix : string | undefined;
87
+ prefix: string | undefined;
88
88
 
89
89
  /**
90
90
  * An optional suffix after the value of the field.
91
91
  * Unlike the TextInput control, this prefix is internal to the field.
92
92
  */
93
- suffix : string | undefined;
93
+ suffix: string | undefined;
94
94
 
95
95
  /**
96
96
  * Set true to display buttons to increment/decrement the value.
@@ -98,12 +98,12 @@ interface ValueInputProps {
98
98
  *
99
99
  * @default false
100
100
  */
101
- showButtons : boolean;
101
+ showButtons: boolean;
102
102
 
103
103
  /**
104
104
  * The amount clicking an increment/decrement buttion will change the value.
105
105
  */
106
- step : number | undefined;
106
+ step: number | undefined;
107
107
 
108
108
 
109
109
  /**
@@ -112,40 +112,41 @@ interface ValueInputProps {
112
112
  *
113
113
  * @default "en-US"
114
114
  */
115
- locale : string | undefined;
115
+ locale: string | undefined;
116
116
 
117
117
  /**
118
118
  * A small, advisory text below the field.
119
119
  */
120
- description : React.ReactNode | undefined;
120
+ description: React.ReactNode | undefined;
121
121
 
122
122
  /**
123
123
  * If true, all functions of the field will be disabled.
124
124
  */
125
- disabled : boolean | undefined;
126
-
125
+ disabled: boolean | undefined;
126
+
127
127
  /** Topic on which the value will be dispatched through the user interfave on successful data entry. */
128
- dispatchTopic : string | undefined;
128
+ dispatchTopic: string | undefined;
129
129
 
130
130
  /**
131
131
  * Placeholder string to display if the value is empty.
132
132
  */
133
- placeholder : string | undefined;
133
+ placeholder: string | undefined;
134
134
 
135
135
  /**
136
136
  * The user has accepted a new value.
137
137
  * @param newValue New value accepted by the user.
138
- */
139
- onValueChanged?(newValue: number) : void;
138
+ */
139
+ onValueChanged?(newValue: number): void;
140
140
  }
141
141
 
142
142
  /**
143
143
  * State variables of the ValueInput component.
144
144
  */
145
145
  interface ValueInputState {
146
-
147
- entryValue : number | null;
148
- editing : boolean;
146
+
147
+ entryValue: number | null;
148
+ currentValue: number | null;
149
+ editing: boolean;
149
150
  }
150
151
 
151
152
 
@@ -165,67 +166,97 @@ export class ValueInput extends React.Component<ValueInputProps, ValueInputState
165
166
  declare context: React.ContextType<typeof EventEmitterContext>;
166
167
 
167
168
 
168
-
169
+
169
170
 
170
171
  /**
171
172
  * Default properties for the component.
172
173
  */
173
174
  static defaultProps = {
174
- label : '',
175
- value : undefined,
176
- keyFilter : undefined,
177
- writeTopic : undefined,
178
- onValueChanged : undefined,
175
+ label: '',
176
+ value: undefined,
177
+ keyFilter: undefined,
178
+ writeTopic: undefined,
179
+ onValueChanged: undefined,
179
180
  description: undefined,
180
181
  prefix: undefined,
181
- suffix : undefined,
182
- disabled : false,
183
- dispatchTopic : undefined,
184
- placeholder : undefined,
185
- validator : undefined,
186
- min : undefined,
187
- max : undefined,
188
- minPrecision : 0,
189
- maxPrecision : 3,
190
- mode : "decimal",
191
- showButtons : false,
192
- step : 1,
193
- locale : "en-US",
194
- currency : "USD"
182
+ suffix: undefined,
183
+ disabled: false,
184
+ dispatchTopic: undefined,
185
+ placeholder: undefined,
186
+ validator: undefined,
187
+ min: undefined,
188
+ max: undefined,
189
+ minPrecision: 0,
190
+ maxPrecision: 3,
191
+ mode: "decimal",
192
+ showButtons: false,
193
+ step: 1,
194
+ locale: "en-US",
195
+ currency: "USD"
195
196
  };
197
+ inputRef: React.RefObject<HTMLInputElement>;
196
198
 
197
199
  /**
198
200
  *
199
201
  * @param {FooterViewProps} props
200
202
  */
201
- constructor(props : ValueInputProps) {
203
+ constructor(props: ValueInputProps) {
202
204
  super(props);
203
205
  this.state = {
204
- entryValue : props.value,
205
- editing : false
206
+ entryValue: props.value,
207
+ currentValue: props.value,
208
+ editing: false
206
209
  };
210
+
211
+ this.inputRef = createRef();
207
212
  }
208
213
 
209
214
  /**
210
215
  * The component has been loaded into the DOM.
211
216
  */
212
- componentDidMount() {
213
- }
217
+ componentDidMount() {
218
+ }
219
+
220
+
221
+ private onBufferValue(val: number | null) {
222
+ if (val === null)
223
+ return;
214
224
 
215
-
225
+ if (!this.state.editing) {
226
+ this.setState({
227
+ entryValue: this.state.currentValue,
228
+ editing: true
229
+ }, () => {
230
+
231
+ setTimeout(() => {
232
+ if (this.inputRef.current) {
233
+ this.inputRef.current.focus();
234
+ }
235
+
236
+ }, 0);
237
+
238
+ }
239
+ );
240
+ }
241
+ else {
242
+ this.setState({
243
+ entryValue: val
244
+ });
245
+ }
246
+ }
216
247
 
217
248
  /**
218
249
  * The user has elected to accept the input value. Validate and store, if valid.
219
250
  */
220
251
  private onAcceptValue() {
221
- if (this.state.entryValue !== null ) {
222
- this.setState({editing : false});
223
-
252
+ if (this.state.entryValue !== null) {
253
+ this.setState({ editing: false, currentValue: this.state.entryValue });
254
+
224
255
  if (this.props.onValueChanged)
225
256
  this.props.onValueChanged(this.state.entryValue);
226
257
 
227
258
  if (this.props.dispatchTopic !== undefined) {
228
- this.context.dispatch({topic: this.props.dispatchTopic, payload:this.state.entryValue});
259
+ this.context.dispatch({ topic: this.props.dispatchTopic, payload: this.state.entryValue });
229
260
  }
230
261
 
231
262
  }
@@ -235,22 +266,29 @@ export class ValueInput extends React.Component<ValueInputProps, ValueInputState
235
266
  * The user wishes to reset/cancel the previous value.
236
267
  */
237
268
  private onResetValue() {
238
- this.setState({
239
- entryValue: this.props.value,
240
- editing : false
241
- });
269
+
270
+ if (this.state.editing) {
271
+
272
+ this.setState({
273
+ currentValue: this.state.currentValue,
274
+ entryValue: this.state.currentValue,
275
+ editing: false
276
+ });
277
+ }
242
278
  }
243
279
 
244
280
 
245
281
  render() {
246
282
 
247
- return(
283
+ return (
248
284
  <div>
249
285
  <div className="p-inputgroup flex-1" >
250
286
  <span className="p-inputgroup-addon">
251
287
  {this.props.label}
252
- </span>
253
- <InputNumber
288
+ </span>
289
+ <InputNumber
290
+ inputRef={this.inputRef}
291
+ key={this.state.editing ? "editing" : "not-editing"}
254
292
  min={this.props.min}
255
293
  max={this.props.max}
256
294
  minFractionDigits={this.props.minPrecision}
@@ -260,14 +298,14 @@ export class ValueInput extends React.Component<ValueInputProps, ValueInputState
260
298
  suffix={this.props.suffix}
261
299
  showButtons={this.props.showButtons}
262
300
  step={this.props.step}
263
- placeholder={this.props.placeholder}
264
- value={this.state.entryValue}
265
- onChange={(e) => {this.setState({entryValue: e.value, editing : true})} }
301
+ placeholder={this.props.placeholder}
302
+ value={this.state.currentValue}
303
+ onChange={(e) => { this.onBufferValue(e.value) }}
266
304
 
267
305
  buttonLayout="horizontal"
268
- decrementButtonClassName="p-button-danger"
269
- incrementButtonClassName="p-button-success"
270
- incrementButtonIcon="pi pi-plus"
306
+ decrementButtonClassName="p-button-danger"
307
+ incrementButtonClassName="p-button-success"
308
+ incrementButtonIcon="pi pi-plus"
271
309
  decrementButtonIcon="pi pi-minus"
272
310
 
273
311
  locale="en-US"
@@ -280,32 +318,35 @@ export class ValueInput extends React.Component<ValueInputProps, ValueInputState
280
318
  else if (e.key === 'Escape') {
281
319
  this.onResetValue();
282
320
  }
283
- }}
284
- disabled={this.props.disabled}
285
- />
286
- <Button
287
- icon="pi pi-check"
321
+ }}
322
+ disabled={this.props.disabled}
323
+ autoFocus={false}
324
+ />
325
+ <Button
326
+ icon="pi pi-check"
288
327
  disabled={this.props.disabled || !this.state.editing}
289
- className="p-button-success"
290
- onClick={() => this.onAcceptValue()}
328
+ className="p-button-success"
329
+ onClick={() => this.onAcceptValue()}
291
330
  visible={this.state.editing}
292
331
  size="small"
332
+ autoFocus={false}
293
333
  />
294
334
 
295
- <Button
296
- icon="pi pi-times"
335
+ <Button
336
+ icon="pi pi-times"
297
337
  disabled={this.props.disabled || !this.state.editing}
298
- className="p-button-danger"
299
- onClickCapture={()=>this.onResetValue()}
338
+ className="p-button-danger"
339
+ onClickCapture={() => this.onResetValue()}
300
340
  visible={this.state.editing}
301
341
  size="small"
342
+ autoFocus={false}
302
343
  />
303
344
  </div>
304
-
345
+
305
346
  {this.props.description !== undefined &&
306
347
  <small>{this.props.description}</small>
307
348
  }
308
-
349
+
309
350
 
310
351
  </div>
311
352
 
@@ -2,7 +2,7 @@
2
2
  * Copyright (C) 2024 Automated Design Corp. All Rights Reserved.
3
3
  * Created Date: 2024-01-17 11:45:10
4
4
  * -----
5
- * Last Modified: 2024-03-22 14:25:08
5
+ * Last Modified: 2024-04-23 11:53:43
6
6
  * Modified By: ADC
7
7
  * -----
8
8
  *
@@ -103,7 +103,7 @@ export interface EventEmitterContextType {
103
103
  * Invoke/send a message to the back end.
104
104
  * This does NOT get published to the front end.
105
105
  */
106
- invoke(fname: string, payload? : object) : Promise<object>;
106
+ invoke( domain: string, fname: string, payload? : object) : Promise<object>;
107
107
 
108
108
  /**
109
109
  * Subscribe to events identified by the topic.
@@ -264,7 +264,8 @@ export const EventEmitterContext = createContext<EventEmitterContextType>({
264
264
  state: { subscriptions: {}, nextSubscriptionId : 1 },
265
265
  dispatch: () => { },
266
266
  subscribe: () => { return 0; }, // Placeholder for subscription logic
267
- invoke: async (fname: string, payload?: object) => {
267
+ invoke: async (domain: string, fname: string, payload?: object) => {
268
+ domain;
268
269
  fname;
269
270
  payload;
270
271
  // Placeholder for invoke logic
@@ -3,7 +3,7 @@
3
3
  * Created Date: 2023-12-15 14:21:33
4
4
  * Author: Thomas C. Bitsky Jr.
5
5
  * -----
6
- * Last Modified: 2024-03-08 07:09:11
6
+ * Last Modified: 2024-04-23 11:19:03
7
7
  * Modified By: ADC
8
8
  * -----
9
9
  *
@@ -122,23 +122,24 @@ export abstract class HubBase {
122
122
  * Invoke/send a message to the back end.
123
123
  * This does NOT get published to the front end.
124
124
  */
125
- abstract invoke(fname: string, payload? : object) : Promise<object>;
125
+ abstract invoke(domain: string, fname: string, payload? : object) : Promise<object>;
126
126
 
127
127
 
128
128
  /**
129
129
  * Convenience function to invoke a command with an optional topic and optional value.
130
130
  * This will invoke the specified command in the backend. It does not broadcast the
131
131
  * value within the local frontend.
132
+ * @param domain string The domain of the command that will be invoked.
132
133
  * @param fname string Function name
133
134
  * @param topic string Topic
134
135
  * @param value any data payload
135
136
  */
136
- invokeTopic( fname : string, topic : string | null, value? : any ) : Promise<object> {
137
+ invokeTopic( domain: string, fname : string, topic : string | null, value? : any ) : Promise<object> {
137
138
  const msg = {
138
139
  topic : topic,
139
140
  data : value
140
141
  };
141
- return this.invoke(fname, msg);
142
+ return this.invoke(domain, fname, msg);
142
143
  }
143
144
 
144
145
  /**
@@ -2,7 +2,7 @@
2
2
  * Copyright (C) 2023 Automated Design Corp. All Rights Reserved.
3
3
  * Created Date: 2023-12-17 10:38:21
4
4
  * -----
5
- * Last Modified: 2024-03-08 09:35:51
5
+ * Last Modified: 2024-04-23 11:23:27
6
6
  * Modified By: ADC
7
7
  * -----
8
8
  *
@@ -31,7 +31,8 @@ export class HubSimulate extends HubBase {
31
31
  * @param timeout Timeout in milliseconds after which the promise is rejected if no response is received.
32
32
  * @returns A Promise that resolves to the response from the backend or rejects if a timeout occurs.
33
33
  */
34
- invoke(fname: string, payload?: object, timeout: number = 20): Promise<object> {
34
+ invoke(domain : string, fname: string, payload?: object, timeout: number = 20): Promise<object> {
35
+ domain; // Not using for simulator
35
36
  fname; // Not using for simulator
36
37
  return new Promise((resolve, reject) => {
37
38
  reject; // not using for simulator
@@ -3,7 +3,7 @@
3
3
  * Created Date: 2023-12-17 09:50:23
4
4
  * Author: Thomas C. Bitsky Jr.
5
5
  * -----
6
- * Last Modified: 2024-03-07 13:20:46
6
+ * Last Modified: 2024-04-23 11:21:45
7
7
  * Modified By: ADC
8
8
  * -----
9
9
  *
@@ -128,13 +128,17 @@ export class HubTauri extends HubBase {
128
128
  * @param payload Optional data payload
129
129
  * @returns The return value of the backend method.
130
130
  */
131
- invoke(fname: string, payload?: object | undefined): Promise<object> {
131
+ invoke(domain : string, fname: string, payload?: object | undefined): Promise<object> {
132
132
 
133
133
  console.log(JSON.stringify(event));
134
134
 
135
135
  if (payload !== undefined && payload !== null) {
136
136
  console.log(`Payload: ${JSON.stringify(payload)}`);
137
- return invoke(fname, payload as InvokeArgs);
137
+
138
+ // todo! This should probably always been the same function, and then that function
139
+ // in the backend routes the command.
140
+ let topic = `${domain}_${fname}`;
141
+ return invoke(topic, payload as InvokeArgs);
138
142
  }
139
143
  else {
140
144
  return invoke(fname);
@@ -0,0 +1,153 @@
1
+ /*
2
+ * Copyright (C) 2024 Automated Design Corp.. All Rights Reserved.
3
+ * Created Date: 2024-04-17 09:13:07
4
+ * -----
5
+ * Last Modified: 2024-04-23 18:48:23
6
+ * -----
7
+ *
8
+ */
9
+
10
+ /** @file
11
+ *
12
+ * ## broadcast messages
13
+ * The result field of CommandMessages used for broadcast must have
14
+ * success set to true, and must contain the field "topic." The topic field, along
15
+ * with the domain field of the CommandMessage will be combined for the topic used
16
+ * by the websocket client.
17
+ *
18
+ * broadcast topic = `${CommandMessage.domain}/${CommandMessage.result["topic"]}
19
+ *
20
+ * So, to subscribe to a broadcast topic for GM.fReal that is served by the ADS client, the
21
+ * topic will be: "ADS/GM.fReal"
22
+ */
23
+
24
+ import { HubBase } from './HubBase';
25
+
26
+ /// The result portion of a CommandMessage. The server will
27
+ /// place the result of a command in this portion of the message.
28
+ interface CommandMessageResult {
29
+ /// The JSON object of the result.
30
+ data: any;
31
+ /// If true, the command was processed successfully.
32
+ success : boolean,
33
+ /// If success is false, this should contain a corresponding error message.
34
+ error_message: string;
35
+ }
36
+
37
+ /// CommandMessage is the object passed between the client and server for making requests and
38
+ /// responses.
39
+ interface CommandMessage {
40
+ /// An id to identify the request. The request ID is managed by the client and will be reflected back.
41
+ /// The server will never change the request ID.
42
+ request_id: number;
43
+ /// The command domain/servelet in which this command/function to invoke belongs.
44
+ domain: string;
45
+ /// The name of the function to invoke.
46
+ fname: string;
47
+ /// A JSON object of the arguments to the function.
48
+ args?: any;
49
+ /// The result of the command, reflected back from the server
50
+ result?: CommandMessageResult;
51
+ }
52
+
53
+ // Function to parse the JSON string received from the server into a CommandMessage object
54
+ // function parseCommandMessage(jsonString: string): CommandMessage {
55
+ // const parsed: CommandMessage = JSON.parse(jsonString);
56
+ // // Assuming the JSON structure directly matches the TypeScript interfaces
57
+ // return parsed;
58
+ // }
59
+
60
+
61
+ interface RequestRecord {
62
+ resolve: (value?: any) => void;
63
+ reject: (reason?: any) => void;
64
+ }
65
+
66
+
67
+ export class HubWebSocket extends HubBase {
68
+ private socket: WebSocket;
69
+ private requestId = 0;
70
+ private pendingRequests = new Map<number, RequestRecord>();
71
+
72
+ constructor() {
73
+ super();
74
+
75
+ const host = window.location.hostname; // Get the hostname from the address bar
76
+ const port = window.location.port; // Get the port from the address bar, if any
77
+ const proto = window.location.protocol === 'https:' ? 'wss://' : 'ws://'; // Determine the protocol
78
+ const wsUrl = `${proto}${host}${port ? ':' + port : ''}/ws/`; // Construct WebSocket URL
79
+
80
+ this.socket = new WebSocket(wsUrl);
81
+
82
+
83
+ let self = this;
84
+ this.socket.onopen = function() {
85
+ console.log("WebSocket connection established.");
86
+ self.publish("HUB/connected", true);
87
+ //ws.send("Hello, server!"); // Send a message to the server
88
+ };
89
+
90
+
91
+ this.socket.onmessage = (event) => {
92
+ const data: CommandMessage = JSON.parse(event.data);
93
+ if (data.request_id && this.pendingRequests.has(data.request_id)) {
94
+ const { resolve, reject } = this.pendingRequests.get(data.request_id)!;
95
+ if (!data.result?.success) {
96
+ reject(new Error(data.result?.error_message));
97
+ } else {
98
+ resolve(data);
99
+ }
100
+ this.pendingRequests.delete(data.request_id);
101
+ } else {
102
+ this.handleUnsolicitedMessage(data);
103
+ }
104
+ };
105
+
106
+ this.socket.onerror = (error: Event) => {
107
+ console.error('WebSocket error:', error);
108
+ };
109
+
110
+ this.socket.onclose = () => {
111
+ console.log('WebSocket connection closed.');
112
+ };
113
+ }
114
+
115
+ invoke = (domain : string, fname: string, payload?: object): Promise<object> => {
116
+
117
+ return new Promise((resolve, reject) => {
118
+ const id = ++this.requestId; // Increment and use the request ID
119
+ this.pendingRequests.set(id, { resolve, reject });
120
+
121
+ let cm : CommandMessage = {
122
+ request_id: id,
123
+ domain: domain,
124
+ fname: fname,
125
+ args: payload,
126
+ result: undefined
127
+ };
128
+
129
+ this.socket.send(JSON.stringify(cm));
130
+ });
131
+ }
132
+
133
+ handleUnsolicitedMessage = (msg: CommandMessage) => {
134
+ // Handle messages that do not correspond to a pending request
135
+
136
+ if (msg.fname === "BROADCAST"
137
+ && msg.domain.length >= 1
138
+ && msg.result !== undefined && msg.result !== null
139
+ && msg.result.data !== undefined && msg.result.data !== null
140
+ ) {
141
+ let topic = `${msg.domain}/${msg.result.data["topic"]}`;
142
+ this.publish(topic, msg.result.data);
143
+ }
144
+ }
145
+
146
+ disconnect= (): void => {
147
+ this.socket.close();
148
+ }
149
+
150
+ protected emit = (eventName: string, payload?: object): void => {
151
+ this.socket.send(JSON.stringify({ eventName, payload }));
152
+ }
153
+ }