@boarteam/boar-pack-common-frontend 4.0.0 → 4.1.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/dist/tools/safetyRun.d.ts +2 -1
- package/dist/tools/safetyRun.js +2 -1
- package/dist/tools/safetyRun.js.map +1 -1
- package/package.json +2 -3
- package/src/components/ChangesModal/ChangesModal.tsx +0 -260
- package/src/components/ChangesModal/ChangesTab.tsx +0 -51
- package/src/components/ChangesModal/ConflictsTab.tsx +0 -176
- package/src/components/ChangesModal/ErrorsTab.tsx +0 -49
- package/src/components/ChangesModal/NewRecordsTab.tsx +0 -48
- package/src/components/ChangesModal/ResultsTab.tsx +0 -28
- package/src/components/ChangesModal/index.ts +0 -1
- package/src/components/Comment/Comment.tsx +0 -53
- package/src/components/Comment/CommentAvatar.tsx +0 -34
- package/src/components/Comment/CommentForm.tsx +0 -36
- package/src/components/Comment/CommentFormModal.tsx +0 -31
- package/src/components/Comment/index.ts +0 -5
- package/src/components/Descriptions/Descriptions.tsx +0 -328
- package/src/components/Descriptions/DescriptionsCreateModal.tsx +0 -65
- package/src/components/Descriptions/descriptionTypes.ts +0 -73
- package/src/components/Descriptions/index.ts +0 -5
- package/src/components/Descriptions/useContentViewMode.tsx +0 -28
- package/src/components/Descriptions/useDescriptionColumns.ts +0 -42
- package/src/components/Inputs/DateRange.tsx +0 -75
- package/src/components/Inputs/MultiStringSelect.tsx +0 -20
- package/src/components/Inputs/NumberInputHandlingNewRecord.tsx +0 -11
- package/src/components/Inputs/NumberSwitcher.tsx +0 -27
- package/src/components/Inputs/Password.tsx +0 -33
- package/src/components/Inputs/RelationSelect.tsx +0 -108
- package/src/components/Inputs/SearchSelect.tsx +0 -93
- package/src/components/Inputs/filterDropdowns.tsx +0 -144
- package/src/components/Inputs/index.ts +0 -9
- package/src/components/Inputs/useCheckConnection.tsx +0 -79
- package/src/components/List/List.tsx +0 -266
- package/src/components/List/index.ts +0 -3
- package/src/components/List/listTypes.ts +0 -31
- package/src/components/QuestionMarkHint/QuestionMarkHint.tsx +0 -33
- package/src/components/QuestionMarkHint/index.ts +0 -1
- package/src/components/Table/BulkDeleteButton.tsx +0 -55
- package/src/components/Table/BulkEditButton.tsx +0 -160
- package/src/components/Table/CreateEntityModal.tsx +0 -85
- package/src/components/Table/DeleteButton.tsx +0 -44
- package/src/components/Table/Table.tsx +0 -294
- package/src/components/Table/getTableDataQueryParams.ts +0 -79
- package/src/components/Table/index.ts +0 -7
- package/src/components/Table/tableTools.ts +0 -244
- package/src/components/Table/tableTypes.ts +0 -148
- package/src/components/Table/useBulkEditing.tsx +0 -128
- package/src/components/Table/useColumnsSets.tsx +0 -142
- package/src/components/Table/useCreation.tsx +0 -114
- package/src/components/Table/useEditableTable.tsx +0 -86
- package/src/components/Table/useImportExport.tsx +0 -315
- package/src/components/index.ts +0 -6
- package/src/index.ts +0 -2
- package/src/tools/ApiError.ts +0 -18
- package/src/tools/WebsocketClient.ts +0 -146
- package/src/tools/index.ts +0 -6
- package/src/tools/numberTools.ts +0 -6
- package/src/tools/safetyRun.ts +0 -5
- package/src/tools/useFullscreen.tsx +0 -62
- package/src/tools/useTabs.ts +0 -17
|
@@ -1,315 +0,0 @@
|
|
|
1
|
-
import { Button, Tooltip } from 'antd';
|
|
2
|
-
import { DownloadOutlined, UploadOutlined } from "@ant-design/icons";
|
|
3
|
-
import { ChangeEvent, useCallback, useRef, useState } from "react";
|
|
4
|
-
import { TGetAllParams } from "./tableTypes";
|
|
5
|
-
import { Link } from "react-router-dom";
|
|
6
|
-
import * as XLSX from "xlsx";
|
|
7
|
-
import diff from "deep-diff";
|
|
8
|
-
import { ProColumns } from "@ant-design/pro-components";
|
|
9
|
-
import { keyBy } from "lodash";
|
|
10
|
-
import { TRelationalFields } from "../ChangesModal";
|
|
11
|
-
|
|
12
|
-
type TImportedJSON<Entity> = (Entity & { id: string })[]
|
|
13
|
-
|
|
14
|
-
export type TUpdatedDiffResult = {
|
|
15
|
-
id: string | number,
|
|
16
|
-
diff: diff.Diff<any, any>[],
|
|
17
|
-
[key: string]: any,
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
type TUpdatedResult = ({
|
|
21
|
-
id: string | number,
|
|
22
|
-
version: string | number,
|
|
23
|
-
[key: string]: any,
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
export type TDiffResult<Entity> = {
|
|
27
|
-
created: Entity[],
|
|
28
|
-
updated: TUpdatedResult[],
|
|
29
|
-
tableData: TUpdatedDiffResult[],
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export function useImportExport<Entity, TPathParams = {}>({
|
|
33
|
-
exportUrl,
|
|
34
|
-
exportParams,
|
|
35
|
-
columns,
|
|
36
|
-
changedRecordsColumnsConfig,
|
|
37
|
-
relationalFields,
|
|
38
|
-
}: {
|
|
39
|
-
exportUrl?: string;
|
|
40
|
-
exportParams?: {
|
|
41
|
-
[key: string]: string | number
|
|
42
|
-
},
|
|
43
|
-
columns: ProColumns<Entity>[],
|
|
44
|
-
changedRecordsColumnsConfig?: ProColumns<Entity>[],
|
|
45
|
-
relationalFields?: TRelationalFields,
|
|
46
|
-
}) {
|
|
47
|
-
const [isLoadingImport, setIsLoadingImport] = useState(false);
|
|
48
|
-
const [lastQueryParams, setLastQueryParams] = useState<TGetAllParams & TPathParams>();
|
|
49
|
-
const keyedColumns = keyBy(columns, 'dataIndex');
|
|
50
|
-
|
|
51
|
-
const [diffResult, setDiffResult] = useState<TDiffResult<Entity>>();
|
|
52
|
-
const fileInputRef = useRef<HTMLInputElement | null>(null);
|
|
53
|
-
|
|
54
|
-
const openFileDialog = useCallback(() => {
|
|
55
|
-
if (fileInputRef.current) {
|
|
56
|
-
fileInputRef.current.value = '';
|
|
57
|
-
fileInputRef.current.click();
|
|
58
|
-
}
|
|
59
|
-
}, []);
|
|
60
|
-
|
|
61
|
-
const trueBooleanValues = [true, "TRUE", "True", "true", "1", "yes", "on"];
|
|
62
|
-
|
|
63
|
-
const buildExportUrl = useCallback(() => {
|
|
64
|
-
const params = {
|
|
65
|
-
...(lastQueryParams && {
|
|
66
|
-
s: (lastQueryParams as any).s,
|
|
67
|
-
sort: (lastQueryParams as any).sort?.[0],
|
|
68
|
-
}),
|
|
69
|
-
...exportParams,
|
|
70
|
-
} as Record<string, string | number | undefined>;
|
|
71
|
-
|
|
72
|
-
const qp = Object.entries(params)
|
|
73
|
-
.filter(([, v]) => v !== undefined && v !== null && v !== '')
|
|
74
|
-
.reduce((acc, [k, v]) => {
|
|
75
|
-
acc.append(k, String(v));
|
|
76
|
-
return acc;
|
|
77
|
-
}, new URLSearchParams());
|
|
78
|
-
|
|
79
|
-
return exportUrl
|
|
80
|
-
? exportUrl + (qp.toString() ? `?${qp.toString()}` : '')
|
|
81
|
-
: undefined;
|
|
82
|
-
}, [exportUrl, exportParams, lastQueryParams]);
|
|
83
|
-
|
|
84
|
-
const fetchExportCsvArrayBuffer = useCallback(async (): Promise<ArrayBuffer> => {
|
|
85
|
-
const url = buildExportUrl();
|
|
86
|
-
if (!url) throw new Error('Specify exportUrl!');
|
|
87
|
-
|
|
88
|
-
const res = await fetch(url, {
|
|
89
|
-
method: 'GET',
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
if (!res.ok) {
|
|
93
|
-
throw new Error(`Can't get actual CSV data for comparing. HTTP ${res.status}`);
|
|
94
|
-
}
|
|
95
|
-
return await res.arrayBuffer();
|
|
96
|
-
}, [buildExportUrl]);
|
|
97
|
-
|
|
98
|
-
const normalizeRow = (row: any) => {
|
|
99
|
-
const normalizedRow = { ...row };
|
|
100
|
-
for (const key in normalizedRow) {
|
|
101
|
-
if (normalizedRow[key] === null) {
|
|
102
|
-
continue;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Relational fields handling
|
|
106
|
-
const relationalKey = key.replace('_id', '');
|
|
107
|
-
if (relationalFields?.has(relationalKey)) {
|
|
108
|
-
const relation = relationalFields.get(relationalKey);
|
|
109
|
-
const id = Number(normalizedRow[key]);
|
|
110
|
-
normalizedRow[relationalKey] = relation.data[id] || null;
|
|
111
|
-
continue;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Boolean values handling
|
|
115
|
-
if (keyedColumns[key]?.valueType === "switch") {
|
|
116
|
-
normalizedRow[key] = trueBooleanValues.includes(normalizedRow[key]);
|
|
117
|
-
continue;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Numeric values handling
|
|
121
|
-
if (keyedColumns[key]?.valueType === "digit") {
|
|
122
|
-
normalizedRow[key] = Number(normalizedRow[key]);
|
|
123
|
-
continue;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Empty values handling
|
|
127
|
-
if (['', 'null'].includes(normalizedRow[key])) {
|
|
128
|
-
normalizedRow[key] = null;
|
|
129
|
-
continue;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Text values handling
|
|
133
|
-
// Text by default if not specified
|
|
134
|
-
if (keyedColumns[key] && keyedColumns[key].valueType === undefined || 'text') {
|
|
135
|
-
normalizedRow[key] = String(normalizedRow[key]);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
return normalizedRow;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
const getRelationalValue = (relationField: {
|
|
143
|
-
id: any;
|
|
144
|
-
name?: string;
|
|
145
|
-
description?: string;
|
|
146
|
-
}) => {
|
|
147
|
-
return relationField?.name || relationField?.description || relationField?.id;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const handleFileAsync = async (e: ChangeEvent<HTMLInputElement>) => {
|
|
151
|
-
setIsLoadingImport(true);
|
|
152
|
-
|
|
153
|
-
const fileAfter = e.target.files[0];
|
|
154
|
-
|
|
155
|
-
if (!fileAfter) {
|
|
156
|
-
throw new Error('Choose CSV with changes.');
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
console.time('fetch actual export from api');
|
|
160
|
-
|
|
161
|
-
// File before now should be fetched from the same endpoint as the export button use
|
|
162
|
-
const dataBefore = await fetchExportCsvArrayBuffer();
|
|
163
|
-
|
|
164
|
-
console.timeEnd('fetch actual export from api');
|
|
165
|
-
|
|
166
|
-
const dataAfter = await fileAfter.arrayBuffer();
|
|
167
|
-
|
|
168
|
-
// Data is an ArrayBuffer
|
|
169
|
-
const workbookBefore = XLSX.read(dataBefore);
|
|
170
|
-
const workbookAfter = XLSX.read(dataAfter, {
|
|
171
|
-
type: 'array',
|
|
172
|
-
raw: true,
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
const jsonBefore: TImportedJSON<Entity> = XLSX.utils.sheet_to_json(workbookBefore.Sheets[workbookBefore.SheetNames[0]], {
|
|
176
|
-
defval: null
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
const jsonAfter: TImportedJSON<Entity> = XLSX.utils.sheet_to_json(workbookAfter.Sheets[workbookAfter.SheetNames[0]], {
|
|
180
|
-
defval: null
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
// TODO: Check JSON structure
|
|
184
|
-
// ...
|
|
185
|
-
|
|
186
|
-
const oldMap = Object.fromEntries(
|
|
187
|
-
jsonBefore.map(
|
|
188
|
-
(row) => {
|
|
189
|
-
return [row.id, normalizeRow(row)]
|
|
190
|
-
}
|
|
191
|
-
)
|
|
192
|
-
);
|
|
193
|
-
const newMap: { [key: string]: any } = {};
|
|
194
|
-
|
|
195
|
-
const diffResult: TDiffResult<Entity> = {
|
|
196
|
-
created: [],
|
|
197
|
-
updated: [],
|
|
198
|
-
tableData: [],
|
|
199
|
-
};
|
|
200
|
-
|
|
201
|
-
jsonAfter.map((row) => {
|
|
202
|
-
const normalizedRow = normalizeRow(row);
|
|
203
|
-
|
|
204
|
-
// New record
|
|
205
|
-
if (!normalizedRow.id) {
|
|
206
|
-
diffResult.created.push(normalizedRow);
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// Existing record
|
|
211
|
-
newMap[normalizedRow.id] = normalizedRow;
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
// Recognize added and changed records
|
|
215
|
-
for (const id in newMap) {
|
|
216
|
-
if (!oldMap[id]) {
|
|
217
|
-
continue;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
const differences = diff<any, any>(oldMap[id], newMap[id], {
|
|
221
|
-
normalize: (currentPath, key, lhs, rhs) => {
|
|
222
|
-
// We don't need to compare versions
|
|
223
|
-
if (key === 'version') {
|
|
224
|
-
return [true, true];
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// We don't care about relational ids. Skip them
|
|
228
|
-
if (key.endsWith('_id')) {
|
|
229
|
-
return [true, true];
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// If the key is a relational field (dictionary value), we need to compare only value fields
|
|
233
|
-
if (relationalFields && relationalFields.has(key)) {
|
|
234
|
-
return [
|
|
235
|
-
getRelationalValue(lhs),
|
|
236
|
-
getRelationalValue(rhs),
|
|
237
|
-
]
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
return [lhs, rhs];
|
|
241
|
-
}
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
if (differences) {
|
|
245
|
-
// console.log(differences);
|
|
246
|
-
const changedFields = differences.map((diff) => diff.path?.[0]).filter((field: string | undefined) => field);
|
|
247
|
-
const displayFields = changedRecordsColumnsConfig.map(column => column.dataIndex);
|
|
248
|
-
|
|
249
|
-
const payload: TUpdatedResult = {
|
|
250
|
-
id,
|
|
251
|
-
version: newMap[id].version,
|
|
252
|
-
};
|
|
253
|
-
|
|
254
|
-
const tableData: TUpdatedDiffResult = {
|
|
255
|
-
id,
|
|
256
|
-
diff: differences,
|
|
257
|
-
};
|
|
258
|
-
|
|
259
|
-
columns.forEach((column) => {
|
|
260
|
-
const key = String(column.dataIndex);
|
|
261
|
-
const value = newMap[id][key] === undefined ? newMap[id][key + "_id"] : newMap[id][key];
|
|
262
|
-
if (changedFields.includes(key)) {
|
|
263
|
-
payload[key] = value;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
if (displayFields.includes(key)) {
|
|
267
|
-
tableData[key] = value;
|
|
268
|
-
}
|
|
269
|
-
})
|
|
270
|
-
|
|
271
|
-
// Will be sent to the server
|
|
272
|
-
diffResult.updated.push(payload);
|
|
273
|
-
|
|
274
|
-
// Will be shown in the "changed values" tab
|
|
275
|
-
diffResult.tableData.push(tableData);
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// Store this value in the state
|
|
280
|
-
setDiffResult(diffResult);
|
|
281
|
-
setIsLoadingImport(false);
|
|
282
|
-
};
|
|
283
|
-
|
|
284
|
-
const exportButton = <Tooltip title="Export">
|
|
285
|
-
<Link to={buildExportUrl() ?? '#'} target={'_blank'}>
|
|
286
|
-
<Button icon={<UploadOutlined />} />
|
|
287
|
-
</Link>
|
|
288
|
-
</Tooltip>;
|
|
289
|
-
|
|
290
|
-
const importButton = <>
|
|
291
|
-
<Tooltip title="Import changes CSV file">
|
|
292
|
-
<Button
|
|
293
|
-
loading={isLoadingImport}
|
|
294
|
-
icon={<DownloadOutlined />}
|
|
295
|
-
onClick={openFileDialog}
|
|
296
|
-
/>
|
|
297
|
-
</Tooltip>
|
|
298
|
-
|
|
299
|
-
<input
|
|
300
|
-
type="file"
|
|
301
|
-
ref={fileInputRef}
|
|
302
|
-
style={{ display: 'none' }}
|
|
303
|
-
accept=".csv"
|
|
304
|
-
onChange={handleFileAsync}
|
|
305
|
-
/>
|
|
306
|
-
</>
|
|
307
|
-
|
|
308
|
-
return {
|
|
309
|
-
exportButton,
|
|
310
|
-
importButton,
|
|
311
|
-
setLastQueryParams,
|
|
312
|
-
diffResult,
|
|
313
|
-
setDiffResult,
|
|
314
|
-
};
|
|
315
|
-
}
|
package/src/components/index.ts
DELETED
package/src/index.ts
DELETED
package/src/tools/ApiError.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
// Should be synced with common-backend/src/tools/ApiError.ts
|
|
2
|
-
type TApiErrorBodyType = {
|
|
3
|
-
statusCode: number
|
|
4
|
-
message: string
|
|
5
|
-
errors: {
|
|
6
|
-
field: string,
|
|
7
|
-
message: string
|
|
8
|
-
}[]
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
// Copied from api-client/generated/core/ApiError.ts
|
|
12
|
-
export class ApiError extends Error {
|
|
13
|
-
public readonly url: string;
|
|
14
|
-
public readonly status: number;
|
|
15
|
-
public readonly statusText: string;
|
|
16
|
-
public readonly body: TApiErrorBodyType;
|
|
17
|
-
public readonly request: any;
|
|
18
|
-
}
|
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
import { message } from "antd";
|
|
2
|
-
|
|
3
|
-
export type TIncomeEvent = {
|
|
4
|
-
event: string;
|
|
5
|
-
data: {
|
|
6
|
-
message: string;
|
|
7
|
-
status?: WebSocket['readyState'];
|
|
8
|
-
[key: string]: any;
|
|
9
|
-
};
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
export enum WsErrorCodes {
|
|
13
|
-
ConnectionClosed = 1000,
|
|
14
|
-
InvalidJson = 4000,
|
|
15
|
-
ErrorMessage = 4001,
|
|
16
|
-
Unauthorized = 4003,
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export class WebsocketClient {
|
|
20
|
-
private socket: WebSocket | null;
|
|
21
|
-
private reconnectTimeout: number | undefined;
|
|
22
|
-
private readonly worker: null | string;
|
|
23
|
-
private serverSocketStatus: WebSocket['readyState'] = WebSocket.CLOSED;
|
|
24
|
-
|
|
25
|
-
private readonly closeHandler: (event: CloseEvent) => void;
|
|
26
|
-
private readonly openHandler: () => void;
|
|
27
|
-
private readonly messageHandler: (msg: TIncomeEvent) => void;
|
|
28
|
-
|
|
29
|
-
constructor({
|
|
30
|
-
worker,
|
|
31
|
-
onOpen,
|
|
32
|
-
onMessage,
|
|
33
|
-
onClose,
|
|
34
|
-
}: {
|
|
35
|
-
worker: null | string;
|
|
36
|
-
onOpen: () => void;
|
|
37
|
-
onMessage: (msg: TIncomeEvent) => void;
|
|
38
|
-
onClose?: (event: CloseEvent) => void;
|
|
39
|
-
}) {
|
|
40
|
-
this.worker = worker;
|
|
41
|
-
this.closeHandler = onClose || (() => {});
|
|
42
|
-
this.openHandler = onOpen;
|
|
43
|
-
this.messageHandler = onMessage;
|
|
44
|
-
|
|
45
|
-
this.connect();
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
get status() {
|
|
49
|
-
return this.serverSocketStatus === WebSocket.OPEN ? this.socket?.readyState : this.serverSocketStatus;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
private connect() {
|
|
53
|
-
const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
54
|
-
const url = `${wsProtocol}//${location.host}/ws/${this.worker || 'primary'}/ws`;
|
|
55
|
-
console.log(`QuotesDataSocket: connecting to ${url}...`);
|
|
56
|
-
this.serverSocketStatus = WebSocket.CONNECTING;
|
|
57
|
-
this.socket = new WebSocket(url);
|
|
58
|
-
this.socket.addEventListener("open", this.onOpen);
|
|
59
|
-
this.socket.addEventListener("message", this.onMessage);
|
|
60
|
-
this.socket.addEventListener("error", this.onError);
|
|
61
|
-
this.socket.addEventListener("close", this.onClose);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
private onError = (event: WebSocketEventMap['error']) => {
|
|
65
|
-
console.error(`QuotesDataSocket: error for ${this.worker || 'primary'} socket:`);
|
|
66
|
-
console.error(event);
|
|
67
|
-
this.socket?.close();
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
private onClose = (event: WebSocketEventMap['close']) => {
|
|
71
|
-
console.log(`QuotesDataSocket: ${this.worker || 'primary'} socket closed`);
|
|
72
|
-
this.socket?.removeEventListener("close", this.onClose);
|
|
73
|
-
this.socket?.removeEventListener("error", this.onError);
|
|
74
|
-
this.socket?.removeEventListener("message", this.onMessage);
|
|
75
|
-
this.socket?.removeEventListener("open", this.onOpen);
|
|
76
|
-
this.socket = null;
|
|
77
|
-
this.closeHandler(event);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
private onOpen = () => {
|
|
81
|
-
this.openHandler();
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
private onMessage = async (event: WebSocketEventMap['message']) => {
|
|
85
|
-
let msg: TIncomeEvent;
|
|
86
|
-
|
|
87
|
-
try {
|
|
88
|
-
msg = JSON.parse(event.data);
|
|
89
|
-
} catch (e) {
|
|
90
|
-
console.error(`Error, while parsing message from WS server ${event.data}`);
|
|
91
|
-
console.error(e);
|
|
92
|
-
this.socket?.close(WsErrorCodes.InvalidJson, "Invalid JSON");
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (msg.event === "error") {
|
|
97
|
-
console.error(`Error from WS server: ${msg.data.message}`);
|
|
98
|
-
this.socket?.close(WsErrorCodes.ErrorMessage, msg.data.message);
|
|
99
|
-
await message.error(`WS server error: ${msg.data.message}`);
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if (msg.event === "status") {
|
|
104
|
-
this.serverSocketStatus = msg.data?.status ?? this.serverSocketStatus;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
this.messageHandler(msg);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
public close(): Promise<void> {
|
|
111
|
-
clearTimeout(this.reconnectTimeout);
|
|
112
|
-
if (!this.socket || this.socket.readyState === WebSocket.CLOSED) {
|
|
113
|
-
return Promise.resolve();
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
return new Promise((resolve) => {
|
|
117
|
-
this.socket?.addEventListener("close", () => resolve(), { once: true });
|
|
118
|
-
this.socket?.close();
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
public reconnect(timeout: number) {
|
|
123
|
-
this.reconnectTimeout = setTimeout(() => {
|
|
124
|
-
this.connect();
|
|
125
|
-
}, timeout) as unknown as number;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
public send<T>(data: T) {
|
|
129
|
-
const send = () => {
|
|
130
|
-
this.socket?.send(JSON.stringify(data));
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
if (this.socket?.readyState !== WebSocket.OPEN) {
|
|
134
|
-
console.warn(`QuotesDataSocket: socket is not ready to send data`);
|
|
135
|
-
this.socket.addEventListener("open", send, { once: true });
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
try {
|
|
140
|
-
send();
|
|
141
|
-
} catch (e) {
|
|
142
|
-
console.error(`QuotesDataSocket: error, while sending data to WS server`);
|
|
143
|
-
console.error(e);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
package/src/tools/index.ts
DELETED
package/src/tools/numberTools.ts
DELETED
package/src/tools/safetyRun.ts
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { useEffect, useState } from "react";
|
|
2
|
-
import { FullscreenExitOutlined, FullscreenOutlined } from "@ant-design/icons";
|
|
3
|
-
import { Button } from "antd";
|
|
4
|
-
import { createStyles } from "antd-style";
|
|
5
|
-
|
|
6
|
-
function changeFullScreen(fullscreen: boolean) {
|
|
7
|
-
if (fullscreen) {
|
|
8
|
-
document.documentElement.requestFullscreen().catch((e) => {
|
|
9
|
-
console.log(e);
|
|
10
|
-
});
|
|
11
|
-
} else if (document.fullscreenElement) {
|
|
12
|
-
document.exitFullscreen().catch((e) => {
|
|
13
|
-
console.log(e);
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const useStyles = createStyles(({ token }) => {
|
|
19
|
-
return {
|
|
20
|
-
fullscreen: {
|
|
21
|
-
backgroundColor: token.colorBgLayout,
|
|
22
|
-
position: 'fixed',
|
|
23
|
-
top: 0,
|
|
24
|
-
left: 0,
|
|
25
|
-
zIndex: 100,
|
|
26
|
-
width: '100%',
|
|
27
|
-
height: '100%',
|
|
28
|
-
overflow: 'auto',
|
|
29
|
-
},
|
|
30
|
-
notFullscreen: {},
|
|
31
|
-
};
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
export function useFullscreen() {
|
|
35
|
-
const [isFullscreen, setIsFullscreen] = useState(false);
|
|
36
|
-
const { styles } = useStyles();
|
|
37
|
-
|
|
38
|
-
useEffect(() => {
|
|
39
|
-
const listener = () => {
|
|
40
|
-
setIsFullscreen(!!document.fullscreenElement);
|
|
41
|
-
};
|
|
42
|
-
document.addEventListener('fullscreenchange', listener);
|
|
43
|
-
return () => {
|
|
44
|
-
document.removeEventListener('fullscreenchange', listener);
|
|
45
|
-
}
|
|
46
|
-
}, []);
|
|
47
|
-
|
|
48
|
-
const button = <Button
|
|
49
|
-
key="fullscreen"
|
|
50
|
-
type="text"
|
|
51
|
-
onClick={() => changeFullScreen(!isFullscreen)}
|
|
52
|
-
>
|
|
53
|
-
{isFullscreen ? <FullscreenExitOutlined /> : <FullscreenOutlined />}
|
|
54
|
-
</Button>;
|
|
55
|
-
|
|
56
|
-
return {
|
|
57
|
-
isFullscreen,
|
|
58
|
-
setIsFullscreen,
|
|
59
|
-
fullscreenClassName: isFullscreen ? styles.fullscreen : styles.notFullscreen,
|
|
60
|
-
fullscreenButton: button,
|
|
61
|
-
};
|
|
62
|
-
}
|
package/src/tools/useTabs.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { useSearchParams } from "react-router-dom";
|
|
2
|
-
import React, { useEffect, useState } from "react";
|
|
3
|
-
|
|
4
|
-
export function useTabs<T extends string>(defaultTab: T): [T, React.Dispatch<React.SetStateAction<T>>] {
|
|
5
|
-
const [searchParams, setSearchParams] = useSearchParams();
|
|
6
|
-
const [tab, setTab] = useState<T>(() => {
|
|
7
|
-
return searchParams.get('tab') as T ?? defaultTab;
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
useEffect(() => {
|
|
11
|
-
const newSearchParams = new URLSearchParams(searchParams);
|
|
12
|
-
newSearchParams.set('tab', tab);
|
|
13
|
-
setSearchParams(newSearchParams, { replace: true });
|
|
14
|
-
}, [tab]);
|
|
15
|
-
|
|
16
|
-
return [tab, setTab];
|
|
17
|
-
}
|