@carbonorm/carbonreact 3.6.1 → 4.0.0
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/README.md +1 -1
- package/dist/CarbonReact.d.ts +31 -60
- package/dist/components/Alert/Alert.d.ts +5 -1
- package/dist/components/Errors/BackendThrowable.d.ts +4 -1
- package/dist/components/WebSocket/CarbonWebSocket.d.ts +3 -1
- package/dist/hoc/KeysMatching.d.ts +1 -1
- package/dist/hoc/SubsetMatching.d.ts +4 -0
- package/dist/hoc/deleteRestfulObjectArrays.d.ts +17 -3
- package/dist/hoc/updateRestfulObjectArrays.d.ts +25 -11
- package/dist/index.cjs.js +172 -179
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.esm.js +172 -179
- package/dist/index.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/CarbonReact.tsx +54 -31
- package/src/components/Alert/Alert.tsx +32 -29
- package/src/components/Errors/BackendThrowable.tsx +11 -8
- package/src/components/WebSocket/CarbonWebSocket.tsx +21 -15
- package/src/hoc/KeysMatching.ts +4 -2
- package/src/hoc/SubsetMatching.ts +6 -0
- package/src/hoc/deleteRestfulObjectArrays.tsx +51 -52
- package/src/hoc/updateRestfulObjectArrays.tsx +97 -97
- package/src/index.ts +1 -0
- package/src/variables/C6.tsx +1 -1
- package/dist/variables/C6.d.ts +0 -842
package/package.json
CHANGED
package/src/CarbonReact.tsx
CHANGED
|
@@ -9,8 +9,14 @@ import BackendThrowable from 'components/Errors/BackendThrowable';
|
|
|
9
9
|
import Nest from 'components/Nest/Nest';
|
|
10
10
|
import {initialRestfulObjectsState, iRestfulObjectArrayTypes} from "variables/C6";
|
|
11
11
|
import CarbonWebSocket, {iCarbonWebSocketProps} from "./components/WebSocket/CarbonWebSocket";
|
|
12
|
+
import updateRestfulObjectArrays, {iUpdateRestfulObjectArrays} from "./hoc/updateRestfulObjectArrays";
|
|
13
|
+
import deleteRestfulObjectArrays, {iDeleteRestfulObjectArrays} from "./hoc/deleteRestfulObjectArrays";
|
|
12
14
|
|
|
13
15
|
|
|
16
|
+
export type tStatefulApiData<T extends {
|
|
17
|
+
[key: string]: any
|
|
18
|
+
} = {}> = T[] | undefined | null;
|
|
19
|
+
|
|
14
20
|
// our central container, single page application
|
|
15
21
|
export interface iCarbonReactState {
|
|
16
22
|
alertsWaiting: any[],
|
|
@@ -42,22 +48,33 @@ export function isJsonString(str: string) {
|
|
|
42
48
|
return true;
|
|
43
49
|
}
|
|
44
50
|
|
|
51
|
+
const persistentStateMap = new Map<string, iCarbonReactState>();
|
|
45
52
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const CarbonReact = class<P = {}, S = {}> extends Component<{
|
|
53
|
+
abstract class CarbonReact<P = {}, S extends { [key: string]: any; } = {}> extends Component<{
|
|
49
54
|
children?: ReactNode | ReactNode[],
|
|
50
|
-
|
|
51
|
-
websocket?: iCarbonWebSocketProps | boolean
|
|
55
|
+
instanceId?: string,
|
|
56
|
+
websocket?: Omit<iCarbonWebSocketProps, "instance"> | boolean
|
|
52
57
|
} & P, S & iCarbonReactState> {
|
|
53
58
|
|
|
54
|
-
static instance: Component<{
|
|
55
|
-
children?: ReactNode | ReactNode[],
|
|
56
|
-
} & any, any & iCarbonReactState>;
|
|
57
|
-
|
|
58
59
|
context: Context<S & iCarbonReactState> = createContext(this.state);
|
|
59
60
|
|
|
60
|
-
|
|
61
|
+
// Private static member
|
|
62
|
+
// we actually implement this in the constructor todo - test this
|
|
63
|
+
protected static instance: CarbonReact;
|
|
64
|
+
|
|
65
|
+
protected target: typeof CarbonReact;
|
|
66
|
+
|
|
67
|
+
protected updateRestfulObjectArrays = <ObjectType extends { [key: string]: any; } = {}>
|
|
68
|
+
(rest: Omit<iUpdateRestfulObjectArrays<ObjectType, S, P>, "instance">) => updateRestfulObjectArrays<ObjectType, S, P>({
|
|
69
|
+
instance: this,
|
|
70
|
+
...rest
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
protected deleteRestfulObjectArrays = <ObjectType extends { [key: string]: any } = {}>
|
|
74
|
+
(rest: Omit<iDeleteRestfulObjectArrays<ObjectType, S, P>, "instance">) => deleteRestfulObjectArrays<ObjectType, S, P>({
|
|
75
|
+
instance: this,
|
|
76
|
+
...rest
|
|
77
|
+
});
|
|
61
78
|
|
|
62
79
|
static lastLocation = window.location.pathname;
|
|
63
80
|
|
|
@@ -65,27 +82,41 @@ const CarbonReact = class<P = {}, S = {}> extends Component<{
|
|
|
65
82
|
// noinspection JSUnusedGlobalSymbols
|
|
66
83
|
static whyDidYouRender = true;
|
|
67
84
|
|
|
68
|
-
|
|
85
|
+
|
|
86
|
+
protected constructor(props: {
|
|
87
|
+
children?: ReactNode | ReactNode[];
|
|
88
|
+
shouldStatePersist?: boolean | undefined;
|
|
89
|
+
websocket?: boolean | iCarbonWebSocketProps | undefined;
|
|
90
|
+
} & P) {
|
|
69
91
|
|
|
70
92
|
super(props);
|
|
71
93
|
|
|
94
|
+
this.target = new.target;
|
|
95
|
+
|
|
72
96
|
console.log('CarbonORM TSX CONSTRUCTOR');
|
|
73
97
|
|
|
74
|
-
|
|
98
|
+
// this is the magic that allows each class that's extends this to have a static instance - a singleton pattern
|
|
99
|
+
// new.target is a meta-property introduced in ES6 that references the constructor that was directly invoked with the new keyword.
|
|
100
|
+
Object.assign(new.target, {
|
|
101
|
+
instance: this
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
if (this.props.instanceId && persistentStateMap.has(this.props.instanceId)) {
|
|
75
105
|
|
|
76
|
-
this.state =
|
|
106
|
+
this.state = persistentStateMap.get(this.props.instanceId) as S & iCarbonReactState;
|
|
77
107
|
|
|
78
108
|
} else {
|
|
79
109
|
|
|
110
|
+
// This should only ever be done here, when the full state is being trashed.
|
|
111
|
+
// todo - does this suck in context of multiple instances?
|
|
112
|
+
clearCache({
|
|
113
|
+
ignoreWarning: true
|
|
114
|
+
});
|
|
115
|
+
|
|
80
116
|
this.state = initialCarbonReactState as unknown as S & iCarbonReactState;
|
|
81
117
|
|
|
82
118
|
}
|
|
83
119
|
|
|
84
|
-
// This should only ever be done here, when the full state is being trashed.
|
|
85
|
-
clearCache({
|
|
86
|
-
ignoreWarning: true
|
|
87
|
-
});
|
|
88
|
-
|
|
89
120
|
/** We can think of our app as having one state; this state.
|
|
90
121
|
* Long-term, I'd like us to store this state to local storage and only load updates on reload...
|
|
91
122
|
* Class based components are far easier to manage state in local storage and pass state down to children.
|
|
@@ -95,23 +126,14 @@ const CarbonReact = class<P = {}, S = {}> extends Component<{
|
|
|
95
126
|
|
|
96
127
|
}
|
|
97
128
|
|
|
98
|
-
static getState<S>(): S {
|
|
99
|
-
return CarbonReact.instance.state;
|
|
100
|
-
}
|
|
101
129
|
|
|
102
130
|
shouldComponentUpdate(
|
|
103
131
|
nextProps: Readonly<any>,
|
|
104
132
|
nextState: Readonly<iCarbonReactState>,
|
|
105
133
|
_nextContext: any): boolean {
|
|
106
134
|
|
|
107
|
-
if (this.props.
|
|
108
|
-
|
|
109
|
-
CarbonReact.persistentState = undefined;
|
|
110
|
-
|
|
111
|
-
} else {
|
|
112
|
-
|
|
113
|
-
CarbonReact.persistentState = nextState;
|
|
114
|
-
|
|
135
|
+
if (this.props.instanceId) {
|
|
136
|
+
persistentStateMap.set(this.props.instanceId, nextState);
|
|
115
137
|
}
|
|
116
138
|
|
|
117
139
|
changed(this.constructor.name + ' (C6Api)', 'props', this.props, nextProps);
|
|
@@ -147,7 +169,7 @@ const CarbonReact = class<P = {}, S = {}> extends Component<{
|
|
|
147
169
|
|
|
148
170
|
return <>
|
|
149
171
|
{nest}
|
|
150
|
-
<BackendThrowable/>
|
|
172
|
+
<BackendThrowable instance={CarbonReact.instance}/>
|
|
151
173
|
</>;
|
|
152
174
|
|
|
153
175
|
}
|
|
@@ -157,7 +179,8 @@ const CarbonReact = class<P = {}, S = {}> extends Component<{
|
|
|
157
179
|
return <>
|
|
158
180
|
<GlobalHistory/>
|
|
159
181
|
{this.props.websocket &&
|
|
160
|
-
<CarbonWebSocket {...(true === this.props.websocket ? {} : this.props.websocket)}
|
|
182
|
+
<CarbonWebSocket {...(true === this.props.websocket ? {} : this.props.websocket)}
|
|
183
|
+
instance={CarbonReact.instance}/>}
|
|
161
184
|
<Context value={this.state}>
|
|
162
185
|
{this.props.children}
|
|
163
186
|
</Context>
|
|
@@ -2,7 +2,7 @@ import classNames from "classnames";
|
|
|
2
2
|
import CarbonReact from "CarbonReact";
|
|
3
3
|
import {ReactNode} from "react";
|
|
4
4
|
import Popup from "components/Popup/Popup";
|
|
5
|
-
import
|
|
5
|
+
import getStyles from "hoc/getStyles";
|
|
6
6
|
import {faClose} from "@fortawesome/free-solid-svg-icons";
|
|
7
7
|
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
|
8
8
|
import isProduction from "variables/isProduction";
|
|
@@ -21,6 +21,7 @@ export interface iAlertButtonOptions {
|
|
|
21
21
|
export interface iAlert {
|
|
22
22
|
title: string,
|
|
23
23
|
text: string,
|
|
24
|
+
instance: CarbonReact,
|
|
24
25
|
component?: ReactNode,
|
|
25
26
|
icon?: "warning" | "error" | "success" | "info" | "question" | null,
|
|
26
27
|
buttons?: (iAlertButtonOptions)[] | undefined, //['No thanks!', 'Yes, Delete it'],
|
|
@@ -34,7 +35,7 @@ export interface iAlert {
|
|
|
34
35
|
|
|
35
36
|
export function addAlert(props: iAlert) {
|
|
36
37
|
|
|
37
|
-
|
|
38
|
+
props.instance.setState(previousState => ({
|
|
38
39
|
alertsWaiting: previousState.alertsWaiting.length === 0
|
|
39
40
|
? [props]
|
|
40
41
|
: [...previousState.alertsWaiting, props]
|
|
@@ -42,9 +43,11 @@ export function addAlert(props: iAlert) {
|
|
|
42
43
|
|
|
43
44
|
}
|
|
44
45
|
|
|
45
|
-
export default function Alert(
|
|
46
|
+
export default function Alert({
|
|
47
|
+
instance
|
|
48
|
+
}: { instance: CarbonReact }) {
|
|
46
49
|
|
|
47
|
-
const {alertsWaiting, backendThrowable} =
|
|
50
|
+
const {alertsWaiting, backendThrowable} = instance.state
|
|
48
51
|
|
|
49
52
|
let alert: iAlert | undefined = undefined;
|
|
50
53
|
|
|
@@ -66,9 +69,10 @@ export default function Alert() {
|
|
|
66
69
|
})
|
|
67
70
|
}
|
|
68
71
|
|
|
69
|
-
const backendThrowable =
|
|
72
|
+
const backendThrowable = instance.state.backendThrowable[0]
|
|
70
73
|
|
|
71
74
|
alert = {
|
|
75
|
+
instance: instance,
|
|
72
76
|
title: "Oh no! An issue occurred!",
|
|
73
77
|
text: backendThrowable?.['DropInGaming\\PHP\\Errors\\DropException'] ?? 'An unknown issue occurred. Please try again.',
|
|
74
78
|
timeout: 0,
|
|
@@ -81,7 +85,7 @@ export default function Alert() {
|
|
|
81
85
|
|
|
82
86
|
if (value === 'Expand') {
|
|
83
87
|
|
|
84
|
-
|
|
88
|
+
instance.setState(previousState => {
|
|
85
89
|
|
|
86
90
|
let backendThrowable = previousState.backendThrowable.pop()
|
|
87
91
|
|
|
@@ -99,7 +103,7 @@ export default function Alert() {
|
|
|
99
103
|
})
|
|
100
104
|
|
|
101
105
|
} else {
|
|
102
|
-
|
|
106
|
+
instance.setState(previousState => ({
|
|
103
107
|
backendThrowable: previousState.backendThrowable.slice(1)
|
|
104
108
|
}))
|
|
105
109
|
}
|
|
@@ -120,9 +124,7 @@ export default function Alert() {
|
|
|
120
124
|
|
|
121
125
|
const timeout = alert?.timeout || 15000
|
|
122
126
|
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
const dig = getStyles()
|
|
127
|
+
const styles = getStyles()
|
|
126
128
|
|
|
127
129
|
let cancelTimeout: any = null
|
|
128
130
|
|
|
@@ -132,7 +134,7 @@ export default function Alert() {
|
|
|
132
134
|
}
|
|
133
135
|
|
|
134
136
|
if (alert?.backendThrowable === undefined) {
|
|
135
|
-
|
|
137
|
+
instance.setState(previousState => ({
|
|
136
138
|
alertsWaiting: previousState.alertsWaiting.slice(1)
|
|
137
139
|
}))
|
|
138
140
|
}
|
|
@@ -153,19 +155,19 @@ export default function Alert() {
|
|
|
153
155
|
}
|
|
154
156
|
|
|
155
157
|
return <Popup handleClose={handleClose}>
|
|
156
|
-
<div className={classNames("model-content",
|
|
158
|
+
<div className={classNames("model-content", styles.rounded0, styles.border0)} style={{
|
|
157
159
|
maxWidth: '75vw',
|
|
158
160
|
maxHeight: '75vh',
|
|
159
161
|
}}>
|
|
160
|
-
<div className={classNames(
|
|
162
|
+
<div className={classNames(styles.modalHeader, styles.rounded0, styles.border0, {
|
|
161
163
|
// icon?: "warning" | "error" | "success" | "info" | "question"
|
|
162
|
-
[
|
|
163
|
-
[
|
|
164
|
-
[
|
|
165
|
-
[
|
|
166
|
-
[
|
|
164
|
+
[styles.bg_primary]: "info" === alert.icon || alert.icon === undefined || alert.icon === null,
|
|
165
|
+
[styles.bg_success]: "success" === alert.icon,
|
|
166
|
+
[styles.bg_warning]: "warning" === alert.icon,
|
|
167
|
+
[styles.bg_danger]: "error" === alert.icon, // TODO - change to red
|
|
168
|
+
[styles.bgPrimary]: "question" === alert.icon,
|
|
167
169
|
})}>
|
|
168
|
-
<h3 className={classNames(
|
|
170
|
+
<h3 className={classNames(styles.modalTitle, styles.textDark)} id="staticBackdropLabel">
|
|
169
171
|
#{alertWaiting} {alert.title}
|
|
170
172
|
</h3>
|
|
171
173
|
<div onClick={handleClose}>
|
|
@@ -174,26 +176,27 @@ export default function Alert() {
|
|
|
174
176
|
size={'xl'}/>
|
|
175
177
|
</div>
|
|
176
178
|
</div>
|
|
177
|
-
<div className={classNames(
|
|
178
|
-
<div className={
|
|
179
|
+
<div className={classNames(styles.modalBody, styles.border0, styles.textWhite)}>
|
|
180
|
+
<div className={styles.textCenter}>
|
|
179
181
|
{alert.text}
|
|
180
182
|
{alert.component}
|
|
181
183
|
</div>
|
|
182
184
|
</div>
|
|
183
185
|
{undefined !== alert.buttons &&
|
|
184
|
-
<div className={classNames(
|
|
185
|
-
{alert.footerText &&
|
|
186
|
+
<div className={classNames(styles.modalFooter, styles.border0, styles.rounded0)}>
|
|
187
|
+
{alert.footerText &&
|
|
188
|
+
<div className={classNames(styles.textCenter, styles.textWhite)}>{alert.footerText}</div>}
|
|
186
189
|
|
|
187
190
|
{alert.buttons?.map((button: iAlertButtonOptions, index: number) => {
|
|
188
191
|
|
|
189
192
|
return <button key={index}
|
|
190
|
-
className={classNames(
|
|
193
|
+
className={classNames(styles.btn, styles.btnLg, {
|
|
191
194
|
// todo - color: "default" | "primary" | "secondary" | "inherit" | "danger" | "info" | "success" | "warning" | undefined,
|
|
192
|
-
[
|
|
193
|
-
[
|
|
194
|
-
[
|
|
195
|
-
[
|
|
196
|
-
}, "btn-Yes",
|
|
195
|
+
[styles.bg_success]: "success" === button.color,
|
|
196
|
+
[styles.bg_danger]: "danger" === button.color,
|
|
197
|
+
[styles.bg_primary]: "primary" === button.color,
|
|
198
|
+
[styles.bg_warning]: "warning" === button.color,
|
|
199
|
+
}, "btn-Yes", styles.rounded0)}
|
|
197
200
|
onClick={() => {
|
|
198
201
|
handleClose()
|
|
199
202
|
alert?.then?.(button.value ?? button.text)
|
|
@@ -3,18 +3,20 @@ import OutsideClickHandler from 'react-outside-click-handler';
|
|
|
3
3
|
import CarbonReact from "../../CarbonReact";
|
|
4
4
|
import {ReactElement} from "react";
|
|
5
5
|
|
|
6
|
-
export default (
|
|
6
|
+
export default (props: {
|
|
7
|
+
instance: CarbonReact,
|
|
8
|
+
}): ReactElement => {
|
|
7
9
|
|
|
8
|
-
const
|
|
10
|
+
const {instance} = props;
|
|
9
11
|
|
|
10
|
-
const currentThrowable =
|
|
12
|
+
const currentThrowable = instance.state.backendThrowable[0];
|
|
11
13
|
|
|
12
|
-
console.log([
|
|
14
|
+
console.log([instance.state.backendThrowable, currentThrowable]);
|
|
13
15
|
|
|
14
16
|
return <div className={styles.maintenanceHero}>
|
|
15
17
|
<h1 className={styles.httpStatusCode}>{currentThrowable?.status || 500}</h1>
|
|
16
18
|
<OutsideClickHandler
|
|
17
|
-
onOutsideClick={() =>
|
|
19
|
+
onOutsideClick={() => instance.setState(currentState => ({backendThrowable: currentState.backendThrowable.slice(1)}))}>
|
|
18
20
|
<div className={styles.centeredContainer}>
|
|
19
21
|
{Object.keys(currentThrowable).map((key, index) => {
|
|
20
22
|
|
|
@@ -26,14 +28,15 @@ export default () : ReactElement => {
|
|
|
26
28
|
<div className={styles.errorTextGeneral}> > <span className={styles.errorKeys}>{key}</span>:
|
|
27
29
|
{valueIsString
|
|
28
30
|
? (valueIsCode ? <div
|
|
29
|
-
style={{
|
|
30
|
-
dangerouslySetInnerHTML={{
|
|
31
|
+
style={{backgroundColor: 'black', fontSize: 'xx-small'}}
|
|
32
|
+
dangerouslySetInnerHTML={{__html: currentThrowable[key]}}/> :
|
|
31
33
|
<i className={styles.errorValues}>"{currentThrowable[key]}"</i>)
|
|
32
34
|
: ''}
|
|
33
35
|
</div>
|
|
34
36
|
{valueIsString
|
|
35
37
|
? ''
|
|
36
|
-
: <pre
|
|
38
|
+
: <pre
|
|
39
|
+
className={styles.errorPre}>{JSON.stringify(currentThrowable[key], undefined, 4)}</pre>}
|
|
37
40
|
</div>;
|
|
38
41
|
})}
|
|
39
42
|
</div>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import CarbonReact, {isJsonString} from "CarbonReact";
|
|
2
2
|
import {addAlert} from "../Alert/Alert";
|
|
3
3
|
import {useEffectOnce} from "../../api/hoc/useEffectOnce";
|
|
4
4
|
import {tC6Tables, tC6RestApi} from "@carbonorm/carbonnode";
|
|
@@ -8,6 +8,7 @@ export interface iCarbonWebSocketProps {
|
|
|
8
8
|
url?: string,
|
|
9
9
|
timeoutSeconds?: number,
|
|
10
10
|
heartbeatSeconds?: number,
|
|
11
|
+
instance: CarbonReact,
|
|
11
12
|
TABLES?: tC6Tables,
|
|
12
13
|
WsLiveUpdates?: tC6RestApi,
|
|
13
14
|
}
|
|
@@ -16,15 +17,19 @@ export interface iCarbonWebSocketProps {
|
|
|
16
17
|
* @function connect
|
|
17
18
|
* This function establishes a connection with the websocket and also ensures constant reconnection if connection closes
|
|
18
19
|
**/
|
|
19
|
-
export function initiateWebsocket({
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
export function initiateWebsocket(props: iCarbonWebSocketProps) {
|
|
21
|
+
|
|
22
|
+
let {
|
|
23
|
+
instance,
|
|
24
|
+
TABLES = undefined,
|
|
25
|
+
WsLiveUpdates = undefined,
|
|
26
|
+
url = 'ws' + (window.location.protocol === 'https:' ? 's' : '') + '://' + window.location.host + '/carbonorm/websocket',
|
|
27
|
+
timeoutSeconds = 250,
|
|
28
|
+
heartbeatSeconds = 60
|
|
29
|
+
} = props;
|
|
30
|
+
|
|
31
|
+
const {websocket} = instance.state;
|
|
26
32
|
|
|
27
|
-
const {websocket} = CarbonReact.instance.state;
|
|
28
33
|
|
|
29
34
|
if (!("WebSocket" in window)) {
|
|
30
35
|
|
|
@@ -33,6 +38,7 @@ export function initiateWebsocket({
|
|
|
33
38
|
title: 'Browser does not support websockets, live updates will fail. You may need to refresh the page to see the newest content.',
|
|
34
39
|
text: 'Please use a modern browser.',
|
|
35
40
|
icon: 'warning',
|
|
41
|
+
instance
|
|
36
42
|
})
|
|
37
43
|
|
|
38
44
|
}
|
|
@@ -50,7 +56,7 @@ export function initiateWebsocket({
|
|
|
50
56
|
|
|
51
57
|
console.log("Connecting websocket url", url);
|
|
52
58
|
|
|
53
|
-
|
|
59
|
+
instance.setState({
|
|
54
60
|
websocket: connection
|
|
55
61
|
}, () => {
|
|
56
62
|
|
|
@@ -62,7 +68,7 @@ export function initiateWebsocket({
|
|
|
62
68
|
|
|
63
69
|
function heartbeat() {
|
|
64
70
|
|
|
65
|
-
const {websocket} =
|
|
71
|
+
const {websocket} = instance.state;
|
|
66
72
|
|
|
67
73
|
if (!websocket) return;
|
|
68
74
|
|
|
@@ -86,7 +92,7 @@ export function initiateWebsocket({
|
|
|
86
92
|
return;
|
|
87
93
|
}
|
|
88
94
|
|
|
89
|
-
|
|
95
|
+
instance.setState((prevState: Readonly<any>) => ({
|
|
90
96
|
websocketEvents: prevState.websocketEvents.concat(message),
|
|
91
97
|
websocketData: prevState.websocketData.concat(parsedData), // JSON.parse no good - base64?
|
|
92
98
|
}), () => {
|
|
@@ -133,7 +139,7 @@ export function initiateWebsocket({
|
|
|
133
139
|
|
|
134
140
|
const TABLE_NAME_SHORT = TABLE_NAME.substring(TABLE_PREFIX.length);
|
|
135
141
|
|
|
136
|
-
const currentCache: [] =
|
|
142
|
+
const currentCache: [] = instance.state[TABLE_NAME_SHORT]
|
|
137
143
|
|
|
138
144
|
// just because we have a websocket update, doesn't mean we need the update
|
|
139
145
|
// check to see if the primary key is in the current cache
|
|
@@ -196,7 +202,7 @@ export function initiateWebsocket({
|
|
|
196
202
|
|
|
197
203
|
};
|
|
198
204
|
|
|
199
|
-
window.addEventListener("focus", () => initiateWebsocket());
|
|
205
|
+
window.addEventListener("focus", () => initiateWebsocket(props));
|
|
200
206
|
|
|
201
207
|
// websocket onclose event listener
|
|
202
208
|
connection.addEventListener('close', event => {
|
|
@@ -215,7 +221,7 @@ export function initiateWebsocket({
|
|
|
215
221
|
|
|
216
222
|
console.log(`WebSocket reconnect will be attempted in ${retrySeconds} second(s).`)
|
|
217
223
|
|
|
218
|
-
connectInterval = setTimeout(() => initiateWebsocket(), retrySeconds);
|
|
224
|
+
connectInterval = setTimeout(() => initiateWebsocket(props), retrySeconds);
|
|
219
225
|
|
|
220
226
|
}
|
|
221
227
|
|
package/src/hoc/KeysMatching.ts
CHANGED
|
@@ -1,36 +1,54 @@
|
|
|
1
|
-
import CarbonReact from "CarbonReact";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import CarbonReact, { iCarbonReactState, tStatefulApiData } from "CarbonReact";
|
|
2
|
+
import { KeysMatching } from "./KeysMatching";
|
|
3
|
+
|
|
4
|
+
export interface iDeleteRestfulObjectArrays<
|
|
5
|
+
ObjectType extends {
|
|
6
|
+
[key: string]: any
|
|
7
|
+
} = {},
|
|
8
|
+
S extends { [key: string]: any; } = CarbonReact['state'],
|
|
9
|
+
P = CarbonReact['props']
|
|
10
|
+
> {
|
|
11
|
+
instance: CarbonReact<P, S>,
|
|
12
|
+
dataOrCallback: ObjectType[] | ((state: Readonly<S>, props: Readonly<P>) => ObjectType[] | null),
|
|
13
|
+
stateKey: KeysMatching<S, tStatefulApiData<ObjectType>>,
|
|
14
|
+
uniqueObjectId: keyof ObjectType | (keyof ObjectType)[],
|
|
15
|
+
callback?: () => void
|
|
16
|
+
}
|
|
5
17
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
18
|
+
export default function deleteRestfulObjectArrays<
|
|
19
|
+
ObjectType extends {
|
|
20
|
+
[key: string]: any
|
|
21
|
+
} = {},
|
|
22
|
+
S extends { [key: string]: any; } = CarbonReact['state'],
|
|
23
|
+
P = CarbonReact['props']
|
|
24
|
+
>({
|
|
25
|
+
instance,
|
|
26
|
+
dataOrCallback,
|
|
27
|
+
stateKey,
|
|
28
|
+
uniqueObjectId,
|
|
29
|
+
callback
|
|
30
|
+
}: iDeleteRestfulObjectArrays<ObjectType, S, P>): void {
|
|
16
31
|
|
|
17
|
-
const uniqueObjectIds = uniqueObjectId
|
|
32
|
+
const uniqueObjectIds = Array.isArray(uniqueObjectId) ? uniqueObjectId : [uniqueObjectId];
|
|
18
33
|
|
|
19
|
-
|
|
34
|
+
instance.setState((
|
|
35
|
+
previousBootstrapState: Readonly<S & iCarbonReactState>,
|
|
36
|
+
props: Readonly<P>
|
|
37
|
+
): Pick<S & iCarbonReactState, keyof S> | null => {
|
|
20
38
|
|
|
21
|
-
let newOrReplacementData: ObjectType[]
|
|
39
|
+
let newOrReplacementData: ObjectType[] = [];
|
|
22
40
|
|
|
23
|
-
if (dataOrCallback
|
|
41
|
+
if (Array.isArray(dataOrCallback)) {
|
|
24
42
|
|
|
25
|
-
newOrReplacementData = dataOrCallback
|
|
43
|
+
newOrReplacementData = dataOrCallback;
|
|
26
44
|
|
|
27
|
-
} else if (dataOrCallback
|
|
45
|
+
} else if (typeof dataOrCallback === 'function') {
|
|
28
46
|
|
|
29
|
-
|
|
47
|
+
const callbackReturn = dataOrCallback(previousBootstrapState, props);
|
|
30
48
|
|
|
31
|
-
if (
|
|
49
|
+
if (callbackReturn === null) {
|
|
32
50
|
|
|
33
|
-
return ;
|
|
51
|
+
return null; // No updates needed (noop)
|
|
34
52
|
|
|
35
53
|
}
|
|
36
54
|
|
|
@@ -38,40 +56,21 @@ export default function deleteRestfulObjectArrays<ObjectType = tRestfulObjectArr
|
|
|
38
56
|
|
|
39
57
|
} else {
|
|
40
58
|
|
|
41
|
-
throw Error('The dataOrCallback parameter was not an array or function')
|
|
59
|
+
throw new Error('The dataOrCallback parameter was not an array or function');
|
|
42
60
|
|
|
43
61
|
}
|
|
44
62
|
|
|
45
|
-
const previousStateProperty
|
|
46
|
-
|
|
47
|
-
return {
|
|
48
|
-
[stateKey]: [
|
|
49
|
-
|
|
50
|
-
...previousStateProperty?.filter(item => false === (newOrReplacementData?.find(value => {
|
|
51
|
-
|
|
52
|
-
let isMatch = true;
|
|
53
|
-
|
|
54
|
-
uniqueObjectIds.find(uniqueObjectId => {
|
|
55
|
-
|
|
56
|
-
if (value[uniqueObjectId] !== item[uniqueObjectId]) {
|
|
57
|
-
|
|
58
|
-
isMatch = false;
|
|
63
|
+
const previousStateProperty: tStatefulApiData<ObjectType> = previousBootstrapState[stateKey] as tStatefulApiData<ObjectType>;
|
|
59
64
|
|
|
60
|
-
|
|
65
|
+
const updatedStateProperty = previousStateProperty?.filter(item =>
|
|
66
|
+
!newOrReplacementData.some(value =>
|
|
67
|
+
uniqueObjectIds.every(uniqueId => value[uniqueId] === item[uniqueId])
|
|
68
|
+
)
|
|
69
|
+
) ?? [];
|
|
61
70
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
return isMatch;
|
|
69
|
-
|
|
70
|
-
}) || false)) || []
|
|
71
|
+
return {
|
|
72
|
+
[stateKey]: updatedStateProperty
|
|
73
|
+
} as Pick<S & iCarbonReactState, keyof S>;
|
|
71
74
|
|
|
72
|
-
]
|
|
73
|
-
}
|
|
74
75
|
}, callback);
|
|
75
|
-
|
|
76
76
|
}
|
|
77
|
-
|