@vuu-ui/vuu-data-react 0.13.6 → 0.13.8
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/cjs/index.js +1556 -40
- package/cjs/index.js.map +1 -1
- package/esm/index.js +1535 -15
- package/esm/index.js.map +1 -1
- package/package.json +14 -14
- package/cjs/data-editing/EditForm.css.js +0 -6
- package/cjs/data-editing/EditForm.css.js.map +0 -1
- package/cjs/data-editing/EditForm.js +0 -90
- package/cjs/data-editing/EditForm.js.map +0 -1
- package/cjs/data-editing/UnsavedChangesReport.css.js +0 -6
- package/cjs/data-editing/UnsavedChangesReport.css.js.map +0 -1
- package/cjs/data-editing/UnsavedChangesReport.js +0 -29
- package/cjs/data-editing/UnsavedChangesReport.js.map +0 -1
- package/cjs/data-editing/edit-rule-validation-checker.js +0 -41
- package/cjs/data-editing/edit-rule-validation-checker.js.map +0 -1
- package/cjs/data-editing/edit-validation-rules.js +0 -52
- package/cjs/data-editing/edit-validation-rules.js.map +0 -1
- package/cjs/data-editing/form-edit-state.js +0 -26
- package/cjs/data-editing/form-edit-state.js.map +0 -1
- package/cjs/data-editing/get-data-item-edit-control.js +0 -56
- package/cjs/data-editing/get-data-item-edit-control.js.map +0 -1
- package/cjs/data-editing/useEditForm.js +0 -249
- package/cjs/data-editing/useEditForm.js.map +0 -1
- package/cjs/datasource-provider/RestDataSourceProvider.js +0 -78
- package/cjs/datasource-provider/RestDataSourceProvider.js.map +0 -1
- package/cjs/datasource-provider/VuuDataSourceProvider.js +0 -34
- package/cjs/datasource-provider/VuuDataSourceProvider.js.map +0 -1
- package/cjs/datasource-provider/useAutoLoginToVuuServer.js +0 -54
- package/cjs/datasource-provider/useAutoLoginToVuuServer.js.map +0 -1
- package/cjs/hooks/useLookupValues.js +0 -100
- package/cjs/hooks/useLookupValues.js.map +0 -1
- package/cjs/hooks/useSessionDataSource.js +0 -72
- package/cjs/hooks/useSessionDataSource.js.map +0 -1
- package/cjs/hooks/useTypeaheadSuggestions.js +0 -41
- package/cjs/hooks/useTypeaheadSuggestions.js.map +0 -1
- package/cjs/hooks/useVisualLinks.js +0 -83
- package/cjs/hooks/useVisualLinks.js.map +0 -1
- package/cjs/hooks/useVuuMenuActions.js +0 -362
- package/cjs/hooks/useVuuMenuActions.js.map +0 -1
- package/cjs/hooks/useVuuTables.js +0 -38
- package/cjs/hooks/useVuuTables.js.map +0 -1
- package/cjs/session-editing-form/SessionEditingForm.css.js +0 -6
- package/cjs/session-editing-form/SessionEditingForm.css.js.map +0 -1
- package/cjs/session-editing-form/SessionEditingForm.js +0 -269
- package/cjs/session-editing-form/SessionEditingForm.js.map +0 -1
- package/esm/data-editing/EditForm.css.js +0 -4
- package/esm/data-editing/EditForm.css.js.map +0 -1
- package/esm/data-editing/EditForm.js +0 -88
- package/esm/data-editing/EditForm.js.map +0 -1
- package/esm/data-editing/UnsavedChangesReport.css.js +0 -4
- package/esm/data-editing/UnsavedChangesReport.css.js.map +0 -1
- package/esm/data-editing/UnsavedChangesReport.js +0 -27
- package/esm/data-editing/UnsavedChangesReport.js.map +0 -1
- package/esm/data-editing/edit-rule-validation-checker.js +0 -37
- package/esm/data-editing/edit-rule-validation-checker.js.map +0 -1
- package/esm/data-editing/edit-validation-rules.js +0 -50
- package/esm/data-editing/edit-validation-rules.js.map +0 -1
- package/esm/data-editing/form-edit-state.js +0 -23
- package/esm/data-editing/form-edit-state.js.map +0 -1
- package/esm/data-editing/get-data-item-edit-control.js +0 -54
- package/esm/data-editing/get-data-item-edit-control.js.map +0 -1
- package/esm/data-editing/useEditForm.js +0 -247
- package/esm/data-editing/useEditForm.js.map +0 -1
- package/esm/datasource-provider/RestDataSourceProvider.js +0 -75
- package/esm/datasource-provider/RestDataSourceProvider.js.map +0 -1
- package/esm/datasource-provider/VuuDataSourceProvider.js +0 -32
- package/esm/datasource-provider/VuuDataSourceProvider.js.map +0 -1
- package/esm/datasource-provider/useAutoLoginToVuuServer.js +0 -52
- package/esm/datasource-provider/useAutoLoginToVuuServer.js.map +0 -1
- package/esm/hooks/useLookupValues.js +0 -98
- package/esm/hooks/useLookupValues.js.map +0 -1
- package/esm/hooks/useSessionDataSource.js +0 -70
- package/esm/hooks/useSessionDataSource.js.map +0 -1
- package/esm/hooks/useTypeaheadSuggestions.js +0 -38
- package/esm/hooks/useTypeaheadSuggestions.js.map +0 -1
- package/esm/hooks/useVisualLinks.js +0 -81
- package/esm/hooks/useVisualLinks.js.map +0 -1
- package/esm/hooks/useVuuMenuActions.js +0 -358
- package/esm/hooks/useVuuMenuActions.js.map +0 -1
- package/esm/hooks/useVuuTables.js +0 -36
- package/esm/hooks/useVuuTables.js.map +0 -1
- package/esm/session-editing-form/SessionEditingForm.css.js +0 -4
- package/esm/session-editing-form/SessionEditingForm.css.js.map +0 -1
- package/esm/session-editing-form/SessionEditingForm.js +0 -267
- package/esm/session-editing-form/SessionEditingForm.js.map +0 -1
package/cjs/index.js
CHANGED
|
@@ -1,43 +1,1559 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
var
|
|
5
|
-
var
|
|
6
|
-
var
|
|
7
|
-
var
|
|
8
|
-
var
|
|
9
|
-
var
|
|
10
|
-
var
|
|
11
|
-
var
|
|
12
|
-
var
|
|
13
|
-
var
|
|
14
|
-
var
|
|
15
|
-
var
|
|
16
|
-
var
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
3
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
4
|
+
var vuuUiControls = require('@vuu-ui/vuu-ui-controls');
|
|
5
|
+
var vuuUtils = require('@vuu-ui/vuu-utils');
|
|
6
|
+
var core = require('@salt-ds/core');
|
|
7
|
+
var styles = require('@salt-ds/styles');
|
|
8
|
+
var window = require('@salt-ds/window');
|
|
9
|
+
var cx = require('clsx');
|
|
10
|
+
var vuuPopups = require('@vuu-ui/vuu-popups');
|
|
11
|
+
var react = require('react');
|
|
12
|
+
var vuuDataRemote = require('@vuu-ui/vuu-data-remote');
|
|
13
|
+
var vuuLayout = require('@vuu-ui/vuu-layout');
|
|
14
|
+
var vuuContextMenu = require('@vuu-ui/vuu-context-menu');
|
|
15
|
+
var vuuFilterParser = require('@vuu-ui/vuu-filter-parser');
|
|
16
|
+
var vuuTable = require('@vuu-ui/vuu-table');
|
|
17
|
+
|
|
18
|
+
const getDataItemEditControl = ({
|
|
19
|
+
InputProps: InputProps2,
|
|
20
|
+
TypeaheadProps,
|
|
21
|
+
commitWhenCleared,
|
|
22
|
+
dataDescriptor,
|
|
23
|
+
errorMessage,
|
|
24
|
+
onCommit,
|
|
25
|
+
table
|
|
26
|
+
}) => {
|
|
27
|
+
const handleCommitNumber = (evt, value) => {
|
|
28
|
+
onCommit(evt, value.toString());
|
|
29
|
+
};
|
|
30
|
+
if (dataDescriptor.editable === false) {
|
|
31
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
32
|
+
vuuUiControls.VuuInput,
|
|
33
|
+
{
|
|
34
|
+
variant: "secondary",
|
|
35
|
+
...InputProps2,
|
|
36
|
+
onCommit,
|
|
37
|
+
readOnly: true
|
|
38
|
+
}
|
|
39
|
+
);
|
|
40
|
+
} else if (vuuUtils.isDateTimeDataValue(dataDescriptor)) {
|
|
41
|
+
return /* @__PURE__ */ jsxRuntime.jsx(vuuUiControls.VuuDatePicker, { onCommit: handleCommitNumber });
|
|
42
|
+
} else if (dataDescriptor.serverDataType === "string" && table) {
|
|
43
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
44
|
+
vuuUiControls.VuuTypeaheadInput,
|
|
45
|
+
{
|
|
46
|
+
...InputProps2,
|
|
47
|
+
...TypeaheadProps,
|
|
48
|
+
column: dataDescriptor.name,
|
|
49
|
+
onCommit,
|
|
50
|
+
table
|
|
51
|
+
}
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
55
|
+
vuuUiControls.VuuInput,
|
|
56
|
+
{
|
|
57
|
+
variant: "secondary",
|
|
58
|
+
...InputProps2,
|
|
59
|
+
commitWhenCleared,
|
|
60
|
+
onCommit,
|
|
61
|
+
errorMessage
|
|
62
|
+
}
|
|
63
|
+
);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const OK = { ok: true };
|
|
67
|
+
const NO_VALIDATION_RULES = [];
|
|
68
|
+
function getEditValidationRules(descriptor, editPhase) {
|
|
69
|
+
if (vuuUtils.isTypeDescriptor(descriptor.type)) {
|
|
70
|
+
return editPhase === "*" ? descriptor.type.rules ?? [] : descriptor.type.rules?.filter(
|
|
71
|
+
({ phase: a = "commit" }) => a === editPhase
|
|
72
|
+
) ?? NO_VALIDATION_RULES;
|
|
73
|
+
}
|
|
74
|
+
return NO_VALIDATION_RULES;
|
|
75
|
+
}
|
|
76
|
+
const buildValidationChecker = (rules) => (value, editPhase) => applyRules(rules, value, editPhase);
|
|
77
|
+
function applyRules(rules, value, editPhase = "commit") {
|
|
78
|
+
const result = { ok: true };
|
|
79
|
+
for (const rule of rules) {
|
|
80
|
+
const { phase = "commit" } = rule;
|
|
81
|
+
if (editPhase === "*" || phase === editPhase) {
|
|
82
|
+
const applyRuleToValue = vuuUtils.getEditRuleValidator(rule.name);
|
|
83
|
+
if (applyRuleToValue) {
|
|
84
|
+
const res = applyRuleToValue(rule, value);
|
|
85
|
+
if (!res.ok) {
|
|
86
|
+
result.ok = false;
|
|
87
|
+
(result.messages ?? (result.messages = [])).push(res.message);
|
|
88
|
+
}
|
|
89
|
+
} else {
|
|
90
|
+
throw Error(
|
|
91
|
+
`editable-utils applyRules, no validator registered for rule '${rule.name}'`
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const isString = (value) => typeof value === "string";
|
|
100
|
+
const NUMERIC = /^(?:[0-9]|\.)+$/;
|
|
101
|
+
const CharValidatorNumeric = (rule, value) => {
|
|
102
|
+
if (isString(value)) {
|
|
103
|
+
if (value.trim() === "") {
|
|
104
|
+
return OK;
|
|
105
|
+
} else if (value.match(NUMERIC)) {
|
|
106
|
+
return OK;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return { ok: false, message: "only numeric characters are permitted" };
|
|
110
|
+
};
|
|
111
|
+
const ValueValidatorInteger = (rule, value) => {
|
|
112
|
+
if (isString(value)) {
|
|
113
|
+
if (value.trim() === "") {
|
|
114
|
+
return OK;
|
|
115
|
+
} else {
|
|
116
|
+
if (!value.match(NUMERIC)) {
|
|
117
|
+
return {
|
|
118
|
+
ok: false,
|
|
119
|
+
message: "value must be an integer, invalid character"
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
if (parseFloat(value) === parseInt(value)) {
|
|
123
|
+
return OK;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return { ok: false, message: "must be an integer value" };
|
|
128
|
+
};
|
|
129
|
+
const registerRules = () => {
|
|
130
|
+
vuuUtils.registerComponent(
|
|
131
|
+
"char-numeric",
|
|
132
|
+
CharValidatorNumeric,
|
|
133
|
+
"data-edit-validator",
|
|
134
|
+
{}
|
|
135
|
+
);
|
|
136
|
+
vuuUtils.registerComponent(
|
|
137
|
+
"value-integer",
|
|
138
|
+
ValueValidatorInteger,
|
|
139
|
+
"data-edit-validator",
|
|
140
|
+
{}
|
|
141
|
+
);
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const CLEAN_FORM = {
|
|
145
|
+
isClean: true,
|
|
146
|
+
editedFields: []
|
|
147
|
+
};
|
|
148
|
+
const buildFormEditState = (entity, newEntity) => {
|
|
149
|
+
if (entity === void 0) {
|
|
150
|
+
return CLEAN_FORM;
|
|
151
|
+
} else {
|
|
152
|
+
const editedFields = [];
|
|
153
|
+
for (const [fieldName, value] of Object.entries(entity)) {
|
|
154
|
+
if (value !== newEntity[fieldName]) {
|
|
155
|
+
editedFields.push(fieldName);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
isClean: editedFields.length === 0,
|
|
160
|
+
editedFields
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
var unsavedChangesCss = ".vuuUnsavedChanges-table {\n border-collapse: collapse;\n width: 100%;\n}\n\n.vuuUnsavedChanges-row {\n box-sizing: content-box;\n border-bottom: solid 1px var(--salt-separable-secondary-borderColor);\n height: 32px;\n\n td {\n padding: 0 var(--salt-spacing-200);\n }\n}\n\n.vuuUnsavedChanges-fieldName {\n text-transform: capitalize;\n}\n\n.vuuUnsavedChanges-new {\n font-weight: bold;\n}\n";
|
|
166
|
+
|
|
167
|
+
const classBase$2 = "vuuUnsavedChanges";
|
|
168
|
+
const UnsavedChangesReport = ({
|
|
169
|
+
entity,
|
|
170
|
+
editedEntity
|
|
171
|
+
}) => {
|
|
172
|
+
const targetWindow = window.useWindow();
|
|
173
|
+
styles.useComponentCssInjection({
|
|
174
|
+
testId: "vuu-unsaved-changes-report",
|
|
175
|
+
css: unsavedChangesCss,
|
|
176
|
+
window: targetWindow
|
|
177
|
+
});
|
|
178
|
+
const { editedFields } = buildFormEditState(entity, editedEntity);
|
|
179
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: classBase$2, children: /* @__PURE__ */ jsxRuntime.jsx("table", { className: `${classBase$2}-table`, children: /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: editedFields.map((fieldName, i) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { className: `${classBase$2}-row`, children: [
|
|
180
|
+
/* @__PURE__ */ jsxRuntime.jsx("td", { className: `${classBase$2}-fieldName`, children: fieldName }),
|
|
181
|
+
/* @__PURE__ */ jsxRuntime.jsx("td", { className: `${classBase$2}-old`, children: entity[fieldName] }),
|
|
182
|
+
/* @__PURE__ */ jsxRuntime.jsx("td", { className: `${classBase$2}-new`, children: editedEntity[fieldName] })
|
|
183
|
+
] }, i)) }) }) });
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const CLEAN_VALIDATION = {
|
|
187
|
+
ok: true,
|
|
188
|
+
messages: {}
|
|
189
|
+
};
|
|
190
|
+
const getValidationChecker = (descriptor, editPhase) => {
|
|
191
|
+
const rules = getEditValidationRules(descriptor, editPhase) ?? [];
|
|
192
|
+
return buildValidationChecker(rules);
|
|
193
|
+
};
|
|
194
|
+
const nextValidationState = (state, dataDescriptor, value) => {
|
|
195
|
+
const check = getValidationChecker(dataDescriptor, "change");
|
|
196
|
+
const result = check(value, "change");
|
|
197
|
+
const { name } = dataDescriptor;
|
|
198
|
+
const { ok: wasOk, messages: existingMessages } = state;
|
|
199
|
+
if (result.ok) {
|
|
200
|
+
if (!wasOk) {
|
|
201
|
+
const fieldsInError = Object.keys(existingMessages);
|
|
202
|
+
if (fieldsInError.includes(name)) {
|
|
203
|
+
if (fieldsInError.length === 1) {
|
|
204
|
+
return { ok: true, messages: {} };
|
|
205
|
+
} else {
|
|
206
|
+
const messages = { ...existingMessages };
|
|
207
|
+
delete messages[name];
|
|
208
|
+
return { ok: false, messages };
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
} else {
|
|
213
|
+
return {
|
|
214
|
+
ok: false,
|
|
215
|
+
messages: {
|
|
216
|
+
...existingMessages,
|
|
217
|
+
[name]: result.messages.join("\n")
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
return state;
|
|
222
|
+
};
|
|
223
|
+
function find(descriptors, fieldname) {
|
|
224
|
+
const d = descriptors.find(({ name }) => name === fieldname);
|
|
225
|
+
if (d) {
|
|
226
|
+
return d;
|
|
227
|
+
}
|
|
228
|
+
throw Error(`DataValueDescriptor not found for field ${fieldname}`);
|
|
229
|
+
}
|
|
230
|
+
const getField$1 = (target) => {
|
|
231
|
+
const fieldElement = vuuUtils.queryClosest(target, "[data-field]");
|
|
232
|
+
if (fieldElement) {
|
|
233
|
+
return fieldElement.dataset.field;
|
|
234
|
+
} else {
|
|
235
|
+
throw Error("no field ");
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
const useEditForm = ({
|
|
239
|
+
dataSource,
|
|
240
|
+
formFieldDescriptors,
|
|
241
|
+
onSubmit
|
|
242
|
+
}) => {
|
|
243
|
+
const { showDialog, closeDialog } = vuuPopups.useDialogContext();
|
|
244
|
+
const currentDataSource = react.useRef(void 0);
|
|
245
|
+
const formFieldsContainerRef = react.useRef(null);
|
|
246
|
+
const entityRef = react.useRef(void 0);
|
|
247
|
+
const focusedFieldRef = react.useRef("");
|
|
248
|
+
const originalEntityRef = react.useRef(void 0);
|
|
249
|
+
const formEditStateRef = react.useRef(CLEAN_FORM);
|
|
250
|
+
const validationStateRef = react.useRef({
|
|
251
|
+
ok: true,
|
|
252
|
+
messages: {}
|
|
253
|
+
});
|
|
254
|
+
const [entity, _setEntity] = react.useState();
|
|
255
|
+
const [, forceUpdate] = react.useState({});
|
|
256
|
+
const setFormEditState = react.useCallback((newState) => {
|
|
257
|
+
formEditStateRef.current = newState;
|
|
258
|
+
}, []);
|
|
259
|
+
const setEntity = react.useCallback(
|
|
260
|
+
(newEntity) => {
|
|
261
|
+
setFormEditState(
|
|
262
|
+
buildFormEditState(originalEntityRef.current, newEntity)
|
|
263
|
+
);
|
|
264
|
+
entityRef.current = newEntity;
|
|
265
|
+
_setEntity(newEntity);
|
|
266
|
+
},
|
|
267
|
+
[setFormEditState]
|
|
268
|
+
);
|
|
269
|
+
const submitChanges = react.useCallback(async () => {
|
|
270
|
+
const rpcResponse = await currentDataSource.current?.rpcCall?.(
|
|
271
|
+
vuuUtils.viewportRpcRequest("VP_BULK_EDIT_SUBMIT_RPC")
|
|
272
|
+
);
|
|
273
|
+
console.log({ rpcResponse });
|
|
274
|
+
}, []);
|
|
275
|
+
const showSaveOrDiscardPrompt = react.useCallback(async () => {
|
|
276
|
+
const { current: currentEntity } = entityRef;
|
|
277
|
+
const { current: originalEntity } = originalEntityRef;
|
|
278
|
+
let resolver = void 0;
|
|
279
|
+
const save = async () => {
|
|
280
|
+
await submitChanges();
|
|
281
|
+
closeDialog();
|
|
282
|
+
resolver?.("saved");
|
|
283
|
+
};
|
|
284
|
+
const discard = () => {
|
|
285
|
+
closeDialog();
|
|
286
|
+
resolver?.("discarded");
|
|
287
|
+
};
|
|
288
|
+
requestAnimationFrame(() => {
|
|
289
|
+
showDialog(
|
|
290
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
291
|
+
UnsavedChangesReport,
|
|
292
|
+
{
|
|
293
|
+
entity: originalEntity,
|
|
294
|
+
editedEntity: currentEntity
|
|
295
|
+
}
|
|
296
|
+
),
|
|
297
|
+
"Unsaved Changes",
|
|
298
|
+
[
|
|
299
|
+
/* @__PURE__ */ jsxRuntime.jsx(core.Button, { onClick: discard, children: "Discard Changes" }, "cancel"),
|
|
300
|
+
/* @__PURE__ */ jsxRuntime.jsx(core.Button, { onClick: save, children: "Save Changes" }, "submit")
|
|
301
|
+
],
|
|
302
|
+
true
|
|
303
|
+
// hideCloseButton
|
|
304
|
+
);
|
|
305
|
+
});
|
|
306
|
+
return new Promise((resolve) => {
|
|
307
|
+
resolver = resolve;
|
|
308
|
+
});
|
|
309
|
+
}, [closeDialog, showDialog, submitChanges]);
|
|
310
|
+
react.useMemo(async () => {
|
|
311
|
+
if (dataSource) {
|
|
312
|
+
if (formEditStateRef.current.isClean === false) {
|
|
313
|
+
await showSaveOrDiscardPrompt();
|
|
314
|
+
}
|
|
315
|
+
currentDataSource.current = dataSource;
|
|
316
|
+
originalEntityRef.current = void 0;
|
|
317
|
+
const columnMap = vuuUtils.buildColumnMap(dataSource.columns);
|
|
318
|
+
dataSource?.subscribe({ range: vuuUtils.Range(0, 1) }, (message) => {
|
|
319
|
+
if (vuuUtils.messageHasDataRows(message)) {
|
|
320
|
+
const [row] = message.rows;
|
|
321
|
+
if (row) {
|
|
322
|
+
const entity2 = vuuUtils.dataSourceRowToEntity(row, columnMap);
|
|
323
|
+
if (originalEntityRef.current === void 0) {
|
|
324
|
+
originalEntityRef.current = entity2;
|
|
325
|
+
setEntity(entity2);
|
|
326
|
+
}
|
|
327
|
+
const { editedFields: editedFields2 } = buildFormEditState(
|
|
328
|
+
entityRef.current,
|
|
329
|
+
entity2
|
|
330
|
+
);
|
|
331
|
+
if (editedFields2.length === 1) {
|
|
332
|
+
setEntity(entity2);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
}, [dataSource, setEntity, showSaveOrDiscardPrompt]);
|
|
339
|
+
const setValidationState = react.useCallback((state) => {
|
|
340
|
+
validationStateRef.current = state;
|
|
341
|
+
forceUpdate({});
|
|
342
|
+
}, []);
|
|
343
|
+
const handleFieldCommit = react.useCallback(
|
|
344
|
+
(_, value) => {
|
|
345
|
+
const { current: fieldName } = focusedFieldRef;
|
|
346
|
+
const dataDescriptor = find(formFieldDescriptors, fieldName);
|
|
347
|
+
const { current: state } = validationStateRef;
|
|
348
|
+
const newState = nextValidationState(state, dataDescriptor, value);
|
|
349
|
+
if (newState !== state) {
|
|
350
|
+
setValidationState(newState);
|
|
351
|
+
}
|
|
352
|
+
if (newState.ok && dataSource?.tableSchema) {
|
|
353
|
+
const { key } = dataSource.tableSchema;
|
|
354
|
+
const keyValue = entity?.[key];
|
|
355
|
+
dataSource?.applyEdit(keyValue, fieldName, value).then((rpcResponse) => {
|
|
356
|
+
console.log({ rpcResponse });
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
},
|
|
360
|
+
[dataSource, entity, formFieldDescriptors, setValidationState]
|
|
361
|
+
);
|
|
362
|
+
const handleFieldChange = react.useCallback(
|
|
363
|
+
(evt) => {
|
|
364
|
+
const { current: fieldName } = focusedFieldRef;
|
|
365
|
+
if (fieldName) {
|
|
366
|
+
const input = vuuUtils.queryClosest(evt.target, "input", true);
|
|
367
|
+
const dataDescriptor = find(formFieldDescriptors, fieldName);
|
|
368
|
+
const value = input.value;
|
|
369
|
+
const { current: state } = validationStateRef;
|
|
370
|
+
const newState = nextValidationState(state, dataDescriptor, value);
|
|
371
|
+
if (newState !== state) {
|
|
372
|
+
setValidationState(newState);
|
|
373
|
+
}
|
|
374
|
+
setEntity({ ...entity, [fieldName]: value });
|
|
375
|
+
}
|
|
376
|
+
},
|
|
377
|
+
[entity, formFieldDescriptors, setEntity, setValidationState]
|
|
378
|
+
);
|
|
379
|
+
const handleFormSubmit = react.useCallback(async () => {
|
|
380
|
+
submitChanges();
|
|
381
|
+
setFormEditState(CLEAN_FORM);
|
|
382
|
+
originalEntityRef.current = entity;
|
|
383
|
+
onSubmit?.();
|
|
384
|
+
forceUpdate({});
|
|
385
|
+
}, [entity, onSubmit, setFormEditState, submitChanges]);
|
|
386
|
+
const handleFormCancel = react.useCallback(async () => {
|
|
387
|
+
setFormEditState(CLEAN_FORM);
|
|
388
|
+
setValidationState(CLEAN_VALIDATION);
|
|
389
|
+
setEntity(originalEntityRef.current);
|
|
390
|
+
}, [setEntity, setFormEditState, setValidationState]);
|
|
391
|
+
const handleFocus = react.useCallback((evt) => {
|
|
392
|
+
if (formFieldsContainerRef.current?.contains(evt.target)) {
|
|
393
|
+
const fieldName = getField$1(evt.target);
|
|
394
|
+
if (fieldName) {
|
|
395
|
+
if (fieldName) {
|
|
396
|
+
focusedFieldRef.current = fieldName;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}, []);
|
|
401
|
+
const {
|
|
402
|
+
current: { ok, messages: errorMessages }
|
|
403
|
+
} = validationStateRef;
|
|
404
|
+
const {
|
|
405
|
+
current: { isClean, editedFields }
|
|
406
|
+
} = formEditStateRef;
|
|
407
|
+
return {
|
|
408
|
+
editedFields,
|
|
409
|
+
editEntity: entity,
|
|
410
|
+
errorMessages,
|
|
411
|
+
formFieldsContainerRef,
|
|
412
|
+
isClean,
|
|
413
|
+
ok,
|
|
414
|
+
onCancel: handleFormCancel,
|
|
415
|
+
onChange: handleFieldChange,
|
|
416
|
+
onCommit: handleFieldCommit,
|
|
417
|
+
onFocus: handleFocus,
|
|
418
|
+
onSubmit: handleFormSubmit
|
|
419
|
+
};
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
var editFormCss = ".EditForm {\n display: flex;\n flex-direction: column;\n gap: var(--salt-spacing-400);\n height: 100%;\n padding: var(--salt-spacing-200) var(--salt-spacing-200)\n var(--salt-spacing-200) var(--salt-spacing-400);\n width: 100%;\n}\n\n.EditForm-form-fields {\n display: flex;\n flex-direction: column;\n gap: var(--salt-spacing-400);\n}\n\n.EditForm-buttons {\n align-items: center;\n display: flex;\n gap: var(--salt-spacing-200);\n justify-content: flex-end;\n}\n\n.EditForm-field {\n display: flex;\n\n .saltFormField {\n flex: 1 1 auto;\n }\n\n .EditForm-edit-indicator {\n flex: 0 0 14px;\n position: relative;\n }\n\n &[data-edited=\"true\"] {\n .EditForm-edit-indicator:after {\n background-color: var(--salt-content-secondary-foreground);\n border-radius: 5px;\n content: \"\";\n height: 10px;\n position: absolute;\n top: 50%;\n right: -3px;\n width: 10px;\n }\n }\n}\n";
|
|
423
|
+
|
|
424
|
+
const classBase$1 = "EditForm";
|
|
425
|
+
registerRules();
|
|
426
|
+
const EditForm = ({
|
|
427
|
+
className,
|
|
428
|
+
dataSource,
|
|
429
|
+
formFieldDescriptors,
|
|
430
|
+
onSubmit: onSubmitProp,
|
|
431
|
+
...htmlAttributes
|
|
432
|
+
}) => {
|
|
433
|
+
const targetWindow = window.useWindow();
|
|
434
|
+
styles.useComponentCssInjection({
|
|
435
|
+
testId: "vuu-edit-form",
|
|
436
|
+
css: editFormCss,
|
|
437
|
+
window: targetWindow
|
|
438
|
+
});
|
|
439
|
+
const {
|
|
440
|
+
editedFields,
|
|
441
|
+
editEntity,
|
|
442
|
+
errorMessages,
|
|
443
|
+
formFieldsContainerRef,
|
|
444
|
+
isClean,
|
|
445
|
+
ok,
|
|
446
|
+
onCancel,
|
|
447
|
+
onChange,
|
|
448
|
+
onCommit,
|
|
449
|
+
onFocus,
|
|
450
|
+
onSubmit
|
|
451
|
+
} = useEditForm({
|
|
452
|
+
dataSource,
|
|
453
|
+
formFieldDescriptors,
|
|
454
|
+
onSubmit: onSubmitProp
|
|
455
|
+
});
|
|
456
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
457
|
+
"div",
|
|
458
|
+
{
|
|
459
|
+
...htmlAttributes,
|
|
460
|
+
className: cx(classBase$1, className),
|
|
461
|
+
onFocus,
|
|
462
|
+
children: [
|
|
463
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: `${classBase$1}-form-fields`, ref: formFieldsContainerRef, children: formFieldDescriptors.map((dataDescriptor) => {
|
|
464
|
+
const { name, label = name } = dataDescriptor;
|
|
465
|
+
const errorMessage = errorMessages[name];
|
|
466
|
+
const isEdited = !isClean && editedFields.includes(name);
|
|
467
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
468
|
+
"div",
|
|
469
|
+
{
|
|
470
|
+
className: `${classBase$1}-field`,
|
|
471
|
+
"data-edited": isEdited,
|
|
472
|
+
children: [
|
|
473
|
+
/* @__PURE__ */ jsxRuntime.jsxs(core.FormField, { "data-field": name, children: [
|
|
474
|
+
/* @__PURE__ */ jsxRuntime.jsx(core.FormFieldLabel, { children: label }),
|
|
475
|
+
getDataItemEditControl({
|
|
476
|
+
InputProps: {
|
|
477
|
+
onChange,
|
|
478
|
+
value: editEntity?.[name]?.toString() ?? ""
|
|
479
|
+
},
|
|
480
|
+
dataDescriptor,
|
|
481
|
+
errorMessage,
|
|
482
|
+
onCommit
|
|
483
|
+
})
|
|
484
|
+
] }),
|
|
485
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: `${classBase$1}-edit-indicator` })
|
|
486
|
+
]
|
|
487
|
+
},
|
|
488
|
+
name
|
|
489
|
+
);
|
|
490
|
+
}) }),
|
|
491
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: `${classBase$1}-buttons`, children: [
|
|
492
|
+
/* @__PURE__ */ jsxRuntime.jsx(core.Button, { disabled: isClean, onClick: onCancel, children: "Cancel" }),
|
|
493
|
+
/* @__PURE__ */ jsxRuntime.jsx(core.Button, { onClick: onSubmit, disabled: !ok || isClean, children: "Save" })
|
|
494
|
+
] })
|
|
495
|
+
]
|
|
496
|
+
}
|
|
497
|
+
);
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
const serverAPI = (schemas) => ({
|
|
501
|
+
getTableList: async () => {
|
|
502
|
+
if (schemas) {
|
|
503
|
+
return {
|
|
504
|
+
tables: Object.keys(schemas).map((key) => {
|
|
505
|
+
const [module, table] = key.split(":");
|
|
506
|
+
return { module, table };
|
|
507
|
+
})
|
|
508
|
+
};
|
|
509
|
+
} else {
|
|
510
|
+
console.log(`Rest data source does not yet support table list`);
|
|
511
|
+
return { tables: [] };
|
|
512
|
+
}
|
|
513
|
+
},
|
|
514
|
+
getTableSchema: async ({ module, table }) => {
|
|
515
|
+
const schema = schemas?.[`${module}:${table}`];
|
|
516
|
+
if (schema) {
|
|
517
|
+
return schema;
|
|
518
|
+
} else {
|
|
519
|
+
throw Error(
|
|
520
|
+
`Rest data source does not yet support table schema (${table})`
|
|
521
|
+
);
|
|
522
|
+
}
|
|
523
|
+
},
|
|
524
|
+
rpcCall: async (message) => Promise.reject(
|
|
525
|
+
Error(`Rest data source does not yet support RPC (${message.type})`)
|
|
526
|
+
)
|
|
527
|
+
});
|
|
528
|
+
const getServerAPI$1 = (schemas) => async () => serverAPI(schemas);
|
|
529
|
+
const isRestDataSourceExtension = (o) => {
|
|
530
|
+
return vuuUtils.isObject(o) && "createHttpHeaders" in o && typeof o["createHttpHeaders"] === "function";
|
|
531
|
+
};
|
|
532
|
+
const getRestDataSourceClass = ({
|
|
533
|
+
createHttpHeaders
|
|
534
|
+
}) => {
|
|
535
|
+
if (createHttpHeaders) {
|
|
536
|
+
return class ExtendedClass extends vuuDataRemote.RestDataSource {
|
|
537
|
+
constructor(props) {
|
|
538
|
+
super(props);
|
|
539
|
+
}
|
|
540
|
+
get httpHeaders() {
|
|
541
|
+
return createHttpHeaders();
|
|
542
|
+
}
|
|
543
|
+
};
|
|
544
|
+
} else {
|
|
545
|
+
return vuuDataRemote.RestDataSource;
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
const RestDataSourceProvider = ({
|
|
549
|
+
children,
|
|
550
|
+
createHttpHeaders,
|
|
551
|
+
tableSchemas,
|
|
552
|
+
url
|
|
553
|
+
}) => {
|
|
554
|
+
vuuDataRemote.RestDataSource.api = url;
|
|
555
|
+
const restDataSourceClass = getRestDataSourceClass({ createHttpHeaders });
|
|
556
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
557
|
+
vuuUtils.DataProvider,
|
|
558
|
+
{
|
|
559
|
+
VuuDataSource: restDataSourceClass,
|
|
560
|
+
dataSourceExtensions: { createHttpHeaders },
|
|
561
|
+
getServerAPI: getServerAPI$1(tableSchemas),
|
|
562
|
+
isLocalData: false,
|
|
563
|
+
tableSchemas,
|
|
564
|
+
children
|
|
565
|
+
}
|
|
566
|
+
);
|
|
567
|
+
};
|
|
568
|
+
|
|
569
|
+
const useAutoLoginToVuuServer = ({
|
|
570
|
+
authenticate = true,
|
|
571
|
+
autoConnect = true,
|
|
572
|
+
// autoLogin = true,
|
|
573
|
+
secure = true,
|
|
574
|
+
websocketUrl
|
|
575
|
+
} = {}) => {
|
|
576
|
+
const [errorMessage, setErrorMessage] = react.useState("");
|
|
577
|
+
react.useEffect(() => {
|
|
578
|
+
const connect = async () => {
|
|
579
|
+
try {
|
|
580
|
+
let token = "no-token";
|
|
581
|
+
if (authenticate) {
|
|
582
|
+
token = await vuuDataRemote.authenticate(
|
|
583
|
+
"steve",
|
|
584
|
+
"xyz",
|
|
585
|
+
"/api/authn"
|
|
586
|
+
);
|
|
587
|
+
}
|
|
588
|
+
const url = websocketUrl ?? `${secure ? "wss" : "ws"}://localhost/8090/websocket`;
|
|
589
|
+
vuuDataRemote.ConnectionManager.connect({
|
|
590
|
+
url,
|
|
591
|
+
token,
|
|
592
|
+
username: "steve"
|
|
593
|
+
});
|
|
594
|
+
} catch (e) {
|
|
595
|
+
if (e instanceof Error) {
|
|
596
|
+
console.error(e.message);
|
|
597
|
+
setErrorMessage(e.message);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
};
|
|
601
|
+
if (autoConnect) {
|
|
602
|
+
connect();
|
|
603
|
+
}
|
|
604
|
+
return () => {
|
|
605
|
+
if (autoConnect) {
|
|
606
|
+
vuuDataRemote.ConnectionManager.disconnect();
|
|
607
|
+
}
|
|
608
|
+
};
|
|
609
|
+
}, [authenticate, autoConnect, secure, websocketUrl]);
|
|
610
|
+
if (errorMessage) {
|
|
611
|
+
return /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Unable to authenticate against Vuu Server A Vuu Server instance must be running to show this example." });
|
|
612
|
+
}
|
|
613
|
+
};
|
|
614
|
+
|
|
615
|
+
const getServerAPI = () => vuuDataRemote.ConnectionManager.serverAPI;
|
|
616
|
+
const VuuDataSourceProvider = ({
|
|
617
|
+
authenticate,
|
|
618
|
+
autoConnect = false,
|
|
619
|
+
autoLogin = false,
|
|
620
|
+
children,
|
|
621
|
+
websocketUrl
|
|
622
|
+
}) => {
|
|
623
|
+
useAutoLoginToVuuServer({
|
|
624
|
+
authenticate,
|
|
625
|
+
autoConnect,
|
|
626
|
+
autoLogin,
|
|
627
|
+
websocketUrl
|
|
628
|
+
});
|
|
629
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
630
|
+
vuuUtils.DataProvider,
|
|
631
|
+
{
|
|
632
|
+
VuuDataSource: vuuDataRemote.VuuDataSource,
|
|
633
|
+
getServerAPI,
|
|
634
|
+
isLocalData: false,
|
|
635
|
+
children
|
|
636
|
+
}
|
|
637
|
+
);
|
|
638
|
+
};
|
|
639
|
+
|
|
640
|
+
const NO_VALUES = [];
|
|
641
|
+
const toListOption = (value) => ({
|
|
642
|
+
label: value,
|
|
643
|
+
value
|
|
644
|
+
});
|
|
645
|
+
const lookupValueMap = /* @__PURE__ */ new Map();
|
|
646
|
+
const loadLookupValues = ({
|
|
647
|
+
labelColumn,
|
|
648
|
+
table,
|
|
649
|
+
valueColumn
|
|
650
|
+
}) => {
|
|
651
|
+
const tableKey = `${table.module}:${table.table}`;
|
|
652
|
+
const lookupValues = lookupValueMap.get(tableKey);
|
|
653
|
+
if (lookupValues) {
|
|
654
|
+
return lookupValues;
|
|
655
|
+
} else {
|
|
656
|
+
const promise = new Promise((resolve) => {
|
|
657
|
+
const columns = [valueColumn, labelColumn];
|
|
658
|
+
const columnMap = vuuUtils.buildColumnMap(columns);
|
|
659
|
+
const dataSource = new vuuDataRemote.VuuDataSource({
|
|
660
|
+
bufferSize: 0,
|
|
661
|
+
table
|
|
662
|
+
});
|
|
663
|
+
dataSource.subscribe(
|
|
664
|
+
{
|
|
665
|
+
columns,
|
|
666
|
+
range: vuuUtils.Range(0, 100)
|
|
667
|
+
},
|
|
668
|
+
(message) => {
|
|
669
|
+
if (message.type === "viewport-update") {
|
|
670
|
+
if (message.rows) {
|
|
671
|
+
const listOptions = message.rows.map((row) => ({
|
|
672
|
+
value: row[columnMap[valueColumn]],
|
|
673
|
+
label: row[columnMap[labelColumn]]
|
|
674
|
+
}));
|
|
675
|
+
resolve(listOptions);
|
|
676
|
+
dataSource.unsubscribe();
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
);
|
|
681
|
+
});
|
|
682
|
+
lookupValueMap.set(tableKey, promise);
|
|
683
|
+
return promise;
|
|
684
|
+
}
|
|
685
|
+
};
|
|
686
|
+
const getLookupDetails = ({ name, type }) => {
|
|
687
|
+
if (vuuUtils.isTypeDescriptor(type) && vuuUtils.isLookupRenderer(type.renderer)) {
|
|
688
|
+
return type.renderer.lookup;
|
|
689
|
+
} else {
|
|
690
|
+
throw Error(
|
|
691
|
+
`useLookupValues column ${name} is not configured to use lookup values`
|
|
692
|
+
);
|
|
693
|
+
}
|
|
694
|
+
};
|
|
695
|
+
const useLookupValues = (column, initialValueProp) => {
|
|
696
|
+
const { type: columnType } = column;
|
|
697
|
+
const { getLookupValues } = vuuUtils.useShellContext();
|
|
698
|
+
const initialState = react.useMemo(() => {
|
|
699
|
+
if (vuuUtils.isTypeDescriptor(columnType) && vuuUtils.isValueListRenderer(columnType?.renderer)) {
|
|
700
|
+
const values2 = columnType.renderer.values.map(toListOption);
|
|
701
|
+
return {
|
|
702
|
+
initialValue: vuuUtils.getSelectedOption(values2, initialValueProp) ?? null,
|
|
703
|
+
values: values2
|
|
704
|
+
};
|
|
705
|
+
} else {
|
|
706
|
+
const lookupDetails = getLookupDetails(column);
|
|
707
|
+
const values2 = getLookupValues?.(lookupDetails.table) ?? NO_VALUES;
|
|
708
|
+
return {
|
|
709
|
+
initialValue: vuuUtils.getSelectedOption(values2, initialValueProp) ?? null,
|
|
710
|
+
values: values2
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
}, [column, columnType, getLookupValues, initialValueProp]);
|
|
714
|
+
const [{ initialValue, values }, setLookupState] = react.useState(initialState);
|
|
715
|
+
react.useMemo(() => {
|
|
716
|
+
if (values === NO_VALUES) {
|
|
717
|
+
const lookupDetails = getLookupDetails(column);
|
|
718
|
+
loadLookupValues(lookupDetails).then(
|
|
719
|
+
(values2) => setLookupState({
|
|
720
|
+
initialValue: vuuUtils.getSelectedOption(values2, initialValueProp) ?? null,
|
|
721
|
+
values: values2
|
|
722
|
+
})
|
|
723
|
+
);
|
|
724
|
+
}
|
|
725
|
+
}, [values, column, initialValueProp]);
|
|
726
|
+
return {
|
|
727
|
+
initialValue,
|
|
728
|
+
values
|
|
729
|
+
};
|
|
730
|
+
};
|
|
731
|
+
|
|
732
|
+
const NO_CONFIG = {};
|
|
733
|
+
const useSessionDataSource = ({
|
|
734
|
+
dataSourceSessionKey = "data-source",
|
|
735
|
+
tableSchema
|
|
736
|
+
}) => {
|
|
737
|
+
const { id, load, save, loadSession, saveSession, title } = vuuLayout.useViewContext();
|
|
738
|
+
const { VuuDataSource } = vuuUtils.useData();
|
|
739
|
+
const { "datasource-config": dataSourceConfigFromState } = react.useMemo(() => load?.() ?? NO_CONFIG, [load]);
|
|
740
|
+
const dataSourceConfigRef = react.useRef(
|
|
741
|
+
dataSourceConfigFromState
|
|
742
|
+
);
|
|
743
|
+
const handleDataSourceConfigChange = react.useCallback(
|
|
744
|
+
(config, _range, confirmed) => {
|
|
745
|
+
if (confirmed !== false) {
|
|
746
|
+
const { noChanges } = vuuUtils.isConfigChanged(
|
|
747
|
+
dataSourceConfigRef.current,
|
|
748
|
+
config
|
|
749
|
+
);
|
|
750
|
+
if (noChanges === false) {
|
|
751
|
+
dataSourceConfigRef.current = config;
|
|
752
|
+
save?.(config, "datasource-config");
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
},
|
|
756
|
+
[save]
|
|
757
|
+
);
|
|
758
|
+
const dataSource = react.useMemo(() => {
|
|
759
|
+
let ds = loadSession?.(dataSourceSessionKey);
|
|
760
|
+
if (ds) {
|
|
761
|
+
if (dataSourceConfigFromState) {
|
|
762
|
+
ds.applyConfig(dataSourceConfigFromState, true);
|
|
763
|
+
}
|
|
764
|
+
if (ds.range.from > 0) {
|
|
765
|
+
ds.range = ds.range.reset;
|
|
766
|
+
}
|
|
767
|
+
return ds;
|
|
768
|
+
}
|
|
769
|
+
const columns = dataSourceConfigFromState?.columns ?? tableSchema.columns.map((col) => col.name);
|
|
770
|
+
ds = new VuuDataSource({
|
|
771
|
+
// bufferSize: 0,
|
|
772
|
+
viewport: id,
|
|
773
|
+
table: tableSchema.table,
|
|
774
|
+
...dataSourceConfigRef.current,
|
|
775
|
+
columns,
|
|
776
|
+
title
|
|
777
|
+
});
|
|
778
|
+
ds.on("config", handleDataSourceConfigChange);
|
|
779
|
+
saveSession?.(ds, "data-source");
|
|
780
|
+
return ds;
|
|
781
|
+
}, [
|
|
782
|
+
VuuDataSource,
|
|
783
|
+
dataSourceConfigFromState,
|
|
784
|
+
dataSourceSessionKey,
|
|
785
|
+
handleDataSourceConfigChange,
|
|
786
|
+
id,
|
|
787
|
+
loadSession,
|
|
788
|
+
saveSession,
|
|
789
|
+
tableSchema.columns,
|
|
790
|
+
tableSchema.table,
|
|
791
|
+
title
|
|
792
|
+
]);
|
|
793
|
+
return dataSource;
|
|
794
|
+
};
|
|
795
|
+
|
|
796
|
+
var sessionEditingFormCss = ".vuuSessionEditingForm {\n display: flex;\n flex-direction: column;\n gap: 3px;\n min-width: 400px;\n}\n\n.vuuSessionEditingForm-content {\n display: flex;\n flex-direction: column;\n flex: 1 1 auto;\n gap: 3px;\n overflow: auto;\n padding: var(--salt-spacing-200);\n}\n\n\n.vuuSessionEditingForm-fieldValue.vuuReadOnly {\n font-weight: var(--salt-text-label-fontWeight-strong);\n}\n\n.vuuSessionEditingForm-buttonbar {\n align-items: center;\n border-top: solid 1px var(--salt-container-primary-borderColor);\n box-sizing: content-box;\n display: flex;\n justify-content: flex-end;\n flex: 0 0 autox;\n gap: 6px;\n height: var(--salt-size-base);\n margin: var(--salt-spacing-200);\n padding-top: var(--salt-spacing-200);\n\n}\n\n.vuuSessionEditingForm-errorBanner {\n --vuu-icon-left: 3px;\n --vuu-icon-size: 18px;\n --vuu-icon-top: 3px;\n border: solid 1px var(--salt-status-error-borderColor);\n line-height: 24px;\n padding: 0 6px 0 26px;\n position: relative;\n}";
|
|
797
|
+
|
|
798
|
+
const classBase = "vuuSessionEditingForm";
|
|
799
|
+
const getField = (fields, name) => {
|
|
800
|
+
const field = fields.find((f) => f.name === name);
|
|
801
|
+
if (field) {
|
|
802
|
+
return field;
|
|
803
|
+
} else {
|
|
804
|
+
throw Error(`SessionEditingForm, no field '${name}' found`);
|
|
805
|
+
}
|
|
806
|
+
};
|
|
807
|
+
const getFieldNameAndValue = ({
|
|
808
|
+
target
|
|
809
|
+
}) => {
|
|
810
|
+
const formField = vuuUtils.queryClosest(target, ".saltFormField");
|
|
811
|
+
if (formField) {
|
|
812
|
+
const {
|
|
813
|
+
dataset: { field }
|
|
814
|
+
} = formField;
|
|
815
|
+
if (field === void 0) {
|
|
816
|
+
throw Error("SessionEditingForm, form field has no field data attribute");
|
|
817
|
+
}
|
|
818
|
+
return [field, target.value];
|
|
819
|
+
} else {
|
|
820
|
+
throw Error("Form control is not enclosed in FormField");
|
|
821
|
+
}
|
|
822
|
+
};
|
|
823
|
+
const Status = {
|
|
824
|
+
uninitialised: 0,
|
|
825
|
+
unchanged: 1,
|
|
826
|
+
changed: 2,
|
|
827
|
+
invalid: 3
|
|
828
|
+
};
|
|
829
|
+
const getDataSource = (dataSource, schema) => {
|
|
830
|
+
if (dataSource) {
|
|
831
|
+
return dataSource;
|
|
832
|
+
} else if (schema) {
|
|
833
|
+
return new vuuDataRemote.VuuDataSource({
|
|
834
|
+
bufferSize: 0,
|
|
835
|
+
table: schema.table,
|
|
836
|
+
columns: schema.columns.map((col) => col.name)
|
|
837
|
+
});
|
|
838
|
+
} else {
|
|
839
|
+
throw Error(
|
|
840
|
+
"SessionEditingForm: either a DataSource or a TableSchema must be provided"
|
|
841
|
+
);
|
|
842
|
+
}
|
|
843
|
+
};
|
|
844
|
+
const SessionEditingForm = ({
|
|
845
|
+
className,
|
|
846
|
+
config: { fields, key: keyField },
|
|
847
|
+
dataSource: dataSourceProp,
|
|
848
|
+
id: idProp,
|
|
849
|
+
onClose,
|
|
850
|
+
schema,
|
|
851
|
+
...htmlAttributes
|
|
852
|
+
}) => {
|
|
853
|
+
const targetWindow = window.useWindow();
|
|
854
|
+
styles.useComponentCssInjection({
|
|
855
|
+
testId: "vuu-session-editing-form",
|
|
856
|
+
css: sessionEditingFormCss,
|
|
857
|
+
window: targetWindow
|
|
858
|
+
});
|
|
859
|
+
const [fieldStatusValues, setFieldStatusValues] = react.useState({});
|
|
860
|
+
const [values, setValues] = react.useState();
|
|
861
|
+
const [errorMessage, setErrorMessage] = react.useState("");
|
|
862
|
+
const formContentRef = react.useRef(null);
|
|
863
|
+
const initialDataRef = react.useRef(void 0);
|
|
864
|
+
const dataStatusRef = react.useRef(Status.uninitialised);
|
|
865
|
+
const dataSource = react.useMemo(() => {
|
|
866
|
+
const ds = getDataSource(dataSourceProp, schema);
|
|
867
|
+
const { columns } = ds;
|
|
868
|
+
const columnMap = vuuUtils.buildColumnMap(ds.columns);
|
|
869
|
+
const applyServerData = (data) => {
|
|
870
|
+
if (columnMap) {
|
|
871
|
+
const values2 = {};
|
|
872
|
+
for (const column of columns) {
|
|
873
|
+
values2[column] = data[columnMap[column]];
|
|
874
|
+
}
|
|
875
|
+
if (dataStatusRef.current === Status.uninitialised) {
|
|
876
|
+
dataStatusRef.current = Status.unchanged;
|
|
877
|
+
initialDataRef.current = values2;
|
|
878
|
+
}
|
|
879
|
+
setValues(values2);
|
|
880
|
+
}
|
|
881
|
+
};
|
|
882
|
+
ds.subscribe({ range: vuuUtils.Range(0, 5) }, (message) => {
|
|
883
|
+
if (message.type === "viewport-update" && message.rows) {
|
|
884
|
+
if (dataStatusRef.current === Status.uninitialised) {
|
|
885
|
+
applyServerData(message.rows[0]);
|
|
886
|
+
} else {
|
|
887
|
+
console.log("what do we do with server updates");
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
});
|
|
891
|
+
return ds;
|
|
892
|
+
}, [dataSourceProp, schema]);
|
|
893
|
+
const id = core.useIdMemo(idProp);
|
|
894
|
+
const handleChange = react.useCallback(
|
|
895
|
+
(evt) => {
|
|
896
|
+
const [field, value] = getFieldNameAndValue(evt);
|
|
897
|
+
const { type } = getField(fields, field);
|
|
898
|
+
const typedValue = vuuUtils.getTypedValue(value, type);
|
|
899
|
+
setValues((values2 = {}) => {
|
|
900
|
+
const newValues = {
|
|
901
|
+
...values2,
|
|
902
|
+
[field]: typedValue
|
|
903
|
+
};
|
|
904
|
+
const notUpdated = vuuUtils.shallowEquals(newValues, initialDataRef.current);
|
|
905
|
+
dataStatusRef.current = notUpdated ? Status.unchanged : typedValue !== void 0 ? Status.changed : Status.invalid;
|
|
906
|
+
return newValues;
|
|
907
|
+
});
|
|
908
|
+
},
|
|
909
|
+
[fields]
|
|
910
|
+
);
|
|
911
|
+
const handleBlur = react.useCallback(
|
|
912
|
+
(evt) => {
|
|
913
|
+
const [field, value] = getFieldNameAndValue(evt);
|
|
914
|
+
const rowKey = values?.[keyField];
|
|
915
|
+
const { type } = getField(fields, field);
|
|
916
|
+
const typedValue = vuuUtils.getTypedValue(value, type, true);
|
|
917
|
+
if (typeof rowKey === "string") {
|
|
918
|
+
dataSource.menuRpcCall(vuuUtils.vuuEditCellRequest(rowKey, field, typedValue)).then((response) => {
|
|
919
|
+
if (vuuUtils.isErrorResponse(response)) {
|
|
920
|
+
console.log(`edit rejected ${response.error}`);
|
|
921
|
+
setFieldStatusValues((map) => ({
|
|
922
|
+
...map,
|
|
923
|
+
[field]: response.error
|
|
924
|
+
}));
|
|
925
|
+
} else {
|
|
926
|
+
setFieldStatusValues((map) => ({
|
|
927
|
+
...map,
|
|
928
|
+
[field]: void 0
|
|
929
|
+
}));
|
|
930
|
+
}
|
|
931
|
+
});
|
|
932
|
+
}
|
|
933
|
+
},
|
|
934
|
+
[dataSource, fields, keyField, values]
|
|
935
|
+
);
|
|
936
|
+
const applyAction = react.useCallback(
|
|
937
|
+
(action) => {
|
|
938
|
+
if (action.type === "CLOSE_DIALOG_ACTION") {
|
|
939
|
+
onClose?.();
|
|
940
|
+
}
|
|
941
|
+
},
|
|
942
|
+
[onClose]
|
|
943
|
+
);
|
|
944
|
+
const handleSubmit = react.useCallback(async () => {
|
|
945
|
+
const rpcResponse = await dataSource.menuRpcCall({
|
|
946
|
+
type: "VP_EDIT_SUBMIT_FORM_RPC"
|
|
947
|
+
});
|
|
948
|
+
if (vuuUtils.isErrorResponse(rpcResponse)) {
|
|
949
|
+
setErrorMessage(rpcResponse.error);
|
|
950
|
+
} else if (vuuUtils.isActionMessage(rpcResponse)) {
|
|
951
|
+
applyAction(rpcResponse.action);
|
|
952
|
+
}
|
|
953
|
+
}, [applyAction, dataSource]);
|
|
954
|
+
const handleKeyDown = react.useCallback(
|
|
955
|
+
(evt) => {
|
|
956
|
+
if (evt.key === "Enter" && dataStatusRef.current === Status.changed) {
|
|
957
|
+
handleSubmit();
|
|
958
|
+
}
|
|
959
|
+
},
|
|
960
|
+
[handleSubmit]
|
|
961
|
+
);
|
|
962
|
+
const handleCancel = react.useCallback(() => {
|
|
963
|
+
onClose?.();
|
|
964
|
+
}, [onClose]);
|
|
965
|
+
const getFormControl = (field) => {
|
|
966
|
+
const value = String(values?.[field.name] ?? "");
|
|
967
|
+
if (field.readonly || field.name === keyField) {
|
|
968
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `${classBase}-fieldValue vuuReadOnly`, children: value });
|
|
969
|
+
} else {
|
|
970
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
971
|
+
core.Input,
|
|
972
|
+
{
|
|
973
|
+
className: `${classBase}-fieldValue`,
|
|
974
|
+
onBlur: handleBlur,
|
|
975
|
+
onChange: handleChange,
|
|
976
|
+
value,
|
|
977
|
+
id: `${id}-input-${field.name}`
|
|
978
|
+
}
|
|
979
|
+
);
|
|
980
|
+
}
|
|
981
|
+
};
|
|
982
|
+
react.useEffect(() => {
|
|
983
|
+
if (formContentRef.current) {
|
|
984
|
+
const firstInput = formContentRef.current.querySelector(
|
|
985
|
+
"input"
|
|
986
|
+
);
|
|
987
|
+
if (firstInput) {
|
|
988
|
+
setTimeout(() => {
|
|
989
|
+
firstInput.focus();
|
|
990
|
+
firstInput.select();
|
|
991
|
+
}, 100);
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
}, []);
|
|
995
|
+
react.useEffect(() => {
|
|
996
|
+
return () => {
|
|
997
|
+
if (dataSource) {
|
|
998
|
+
dataSource.unsubscribe();
|
|
999
|
+
}
|
|
1000
|
+
};
|
|
1001
|
+
}, [dataSource]);
|
|
1002
|
+
const isDirty = dataStatusRef.current === Status.changed;
|
|
1003
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { ...htmlAttributes, className: cx(classBase, className), children: [
|
|
1004
|
+
errorMessage ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1005
|
+
"div",
|
|
1006
|
+
{
|
|
1007
|
+
className: `${classBase}-errorBanner`,
|
|
1008
|
+
"data-icon": "error",
|
|
1009
|
+
title: errorMessage,
|
|
1010
|
+
children: "Error, edit(s) not saved"
|
|
1011
|
+
}
|
|
1012
|
+
) : void 0,
|
|
1013
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1014
|
+
"div",
|
|
1015
|
+
{
|
|
1016
|
+
className: `${classBase}-content`,
|
|
1017
|
+
ref: formContentRef,
|
|
1018
|
+
onKeyDown: handleKeyDown,
|
|
1019
|
+
children: fields.map((field) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1020
|
+
core.FormField,
|
|
1021
|
+
{
|
|
1022
|
+
className: `${classBase}-field`,
|
|
1023
|
+
"data-field": field.name,
|
|
1024
|
+
necessity: field.required ? "required" : "optional",
|
|
1025
|
+
readOnly: field.readonly,
|
|
1026
|
+
validationStatus: fieldStatusValues[field.name] ? "error" : void 0,
|
|
1027
|
+
children: [
|
|
1028
|
+
/* @__PURE__ */ jsxRuntime.jsx(core.FormFieldLabel, { children: field?.label ?? field.description }),
|
|
1029
|
+
getFormControl(field),
|
|
1030
|
+
/* @__PURE__ */ jsxRuntime.jsx(core.FormFieldHelperText, { children: fieldStatusValues[field.name] ?? "" })
|
|
1031
|
+
]
|
|
1032
|
+
},
|
|
1033
|
+
field.name
|
|
1034
|
+
))
|
|
1035
|
+
}
|
|
1036
|
+
),
|
|
1037
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: `${classBase}-buttonbar salt-theme salt-density-high`, children: [
|
|
1038
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1039
|
+
core.Button,
|
|
1040
|
+
{
|
|
1041
|
+
type: "submit",
|
|
1042
|
+
variant: "cta",
|
|
1043
|
+
disabled: !isDirty,
|
|
1044
|
+
onClick: handleSubmit,
|
|
1045
|
+
children: "Submit"
|
|
1046
|
+
}
|
|
1047
|
+
),
|
|
1048
|
+
/* @__PURE__ */ jsxRuntime.jsx(core.Button, { variant: "secondary", onClick: handleCancel, children: "Cancel" })
|
|
1049
|
+
] })
|
|
1050
|
+
] });
|
|
1051
|
+
};
|
|
1052
|
+
|
|
1053
|
+
const isRoot = (menu) => menu.name === "ROOT";
|
|
1054
|
+
const isCellMenu = (options) => options.context === "cell";
|
|
1055
|
+
const isRowMenu = (options) => options.context === "row";
|
|
1056
|
+
const isSelectionMenu = (options) => options.context === "selected-rows";
|
|
1057
|
+
const getColumnsFromOptions = (options) => {
|
|
1058
|
+
if (options && typeof options === "object" && "columns" in options) {
|
|
1059
|
+
return options.columns;
|
|
1060
|
+
}
|
|
1061
|
+
};
|
|
1062
|
+
const isVuuMenuItem = (menu) => "rpcName" in menu;
|
|
1063
|
+
const isGroupMenuItem = (menu) => "menus" in menu;
|
|
1064
|
+
const hasFilter = ({ filter }) => typeof filter === "string" && filter.length > 0;
|
|
1065
|
+
const { KEY } = vuuUtils.metadataKeys;
|
|
1066
|
+
const getMenuItemOptions = (menu, options) => {
|
|
1067
|
+
switch (menu.context) {
|
|
1068
|
+
case "cell":
|
|
1069
|
+
return {
|
|
1070
|
+
...menu,
|
|
1071
|
+
field: options.column.name,
|
|
1072
|
+
rowKey: options.row[KEY],
|
|
1073
|
+
value: options.row[options.columnMap[options.column.name]]
|
|
1074
|
+
};
|
|
1075
|
+
case "row":
|
|
1076
|
+
return {
|
|
1077
|
+
...menu,
|
|
1078
|
+
columns: options.columns,
|
|
1079
|
+
row: vuuUtils.dataSourceRowToDataRowDto(options.row, options.columnMap),
|
|
1080
|
+
rowKey: options.row[KEY]
|
|
1081
|
+
};
|
|
1082
|
+
case "selected-rows":
|
|
1083
|
+
return {
|
|
1084
|
+
...menu,
|
|
1085
|
+
columns: options.columns
|
|
1086
|
+
};
|
|
1087
|
+
default:
|
|
1088
|
+
return menu;
|
|
1089
|
+
}
|
|
1090
|
+
};
|
|
1091
|
+
const vuuContextCompatibleWithTableLocation = (uiLocation, vuuContext, selectedRowCount = 0) => {
|
|
1092
|
+
switch (uiLocation) {
|
|
1093
|
+
case "grid":
|
|
1094
|
+
if (vuuContext === "selected-rows") {
|
|
1095
|
+
return selectedRowCount > 0;
|
|
1096
|
+
} else {
|
|
1097
|
+
return true;
|
|
1098
|
+
}
|
|
1099
|
+
case "header":
|
|
1100
|
+
return vuuContext === "grid";
|
|
1101
|
+
default:
|
|
1102
|
+
return false;
|
|
1103
|
+
}
|
|
1104
|
+
};
|
|
1105
|
+
const gridRowMeetsFilterCriteria = (context, row, selectedRows, filter, columnMap) => {
|
|
1106
|
+
if (context === "cell" || context === "row") {
|
|
1107
|
+
const filterPredicate = vuuFilterParser.getFilterPredicate(columnMap, filter);
|
|
1108
|
+
return filterPredicate(row);
|
|
1109
|
+
} else if (context === "selected-rows") {
|
|
1110
|
+
if (selectedRows.length === 0) {
|
|
1111
|
+
return false;
|
|
1112
|
+
} else {
|
|
1113
|
+
const filterPredicate = vuuFilterParser.getFilterPredicate(columnMap, filter);
|
|
1114
|
+
return selectedRows.every(filterPredicate);
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
return true;
|
|
1118
|
+
};
|
|
1119
|
+
const menuShouldBeRenderedInThisContext = (menuItem, tableLocation, options) => {
|
|
1120
|
+
if (isGroupMenuItem(menuItem)) {
|
|
1121
|
+
return menuItem.menus.some(
|
|
1122
|
+
(childMenu) => menuShouldBeRenderedInThisContext(childMenu, tableLocation, options)
|
|
1123
|
+
);
|
|
1124
|
+
}
|
|
1125
|
+
if (!vuuContextCompatibleWithTableLocation(
|
|
1126
|
+
tableLocation,
|
|
1127
|
+
menuItem.context,
|
|
1128
|
+
options.selectedRows?.length
|
|
1129
|
+
)) {
|
|
1130
|
+
return false;
|
|
1131
|
+
}
|
|
1132
|
+
if (tableLocation === "grid" && hasFilter(menuItem)) {
|
|
1133
|
+
return gridRowMeetsFilterCriteria(
|
|
1134
|
+
menuItem.context,
|
|
1135
|
+
options.row,
|
|
1136
|
+
options.selectedRows,
|
|
1137
|
+
menuItem.filter,
|
|
1138
|
+
options.columnMap
|
|
1139
|
+
);
|
|
1140
|
+
}
|
|
1141
|
+
if (isCellMenu(menuItem) && menuItem.field !== "*") {
|
|
1142
|
+
return menuItem.field === options.column.name;
|
|
1143
|
+
}
|
|
1144
|
+
return true;
|
|
1145
|
+
};
|
|
1146
|
+
const buildMenuDescriptorFromVuuMenu = (menu, tableLocation, options) => {
|
|
1147
|
+
if (menuShouldBeRenderedInThisContext(menu, tableLocation, options)) {
|
|
1148
|
+
if (isVuuMenuItem(menu)) {
|
|
1149
|
+
return {
|
|
1150
|
+
label: menu.name,
|
|
1151
|
+
id: "MENU_RPC_CALL",
|
|
1152
|
+
options: getMenuItemOptions(menu, options)
|
|
1153
|
+
};
|
|
1154
|
+
} else {
|
|
1155
|
+
const children = menu.menus.map(
|
|
1156
|
+
(childMenu) => buildMenuDescriptorFromVuuMenu(childMenu, tableLocation, options)
|
|
1157
|
+
).filter(
|
|
1158
|
+
(childMenu) => childMenu !== void 0
|
|
1159
|
+
);
|
|
1160
|
+
if (children.length > 0) {
|
|
1161
|
+
return {
|
|
1162
|
+
label: menu.name,
|
|
1163
|
+
children
|
|
1164
|
+
};
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
};
|
|
1169
|
+
const keyFirst = (c1, c2) => c1.isKeyField ? -1 : c2.isKeyField ? 1 : 0;
|
|
1170
|
+
const defaultFormConfig = {
|
|
1171
|
+
fields: [],
|
|
1172
|
+
key: "",
|
|
1173
|
+
title: ""
|
|
1174
|
+
};
|
|
1175
|
+
const configFromSchema = (schema) => {
|
|
1176
|
+
if (schema) {
|
|
1177
|
+
const { columns, key } = schema;
|
|
1178
|
+
return {
|
|
1179
|
+
key,
|
|
1180
|
+
title: `Parameters for command`,
|
|
1181
|
+
fields: columns.map((col) => ({
|
|
1182
|
+
description: col.name,
|
|
1183
|
+
label: col.name,
|
|
1184
|
+
name: col.name,
|
|
1185
|
+
type: col.serverDataType,
|
|
1186
|
+
isKeyField: col.name === key
|
|
1187
|
+
})).sort(keyFirst)
|
|
1188
|
+
};
|
|
1189
|
+
}
|
|
1190
|
+
};
|
|
1191
|
+
const getFormConfig = (action) => {
|
|
1192
|
+
const { tableSchema: schema } = action;
|
|
1193
|
+
const config = configFromSchema(schema) ?? defaultFormConfig;
|
|
1194
|
+
return {
|
|
1195
|
+
config,
|
|
1196
|
+
schema
|
|
1197
|
+
};
|
|
1198
|
+
};
|
|
1199
|
+
const useVuuMenuActions = ({
|
|
1200
|
+
clientSideMenuActionHandler,
|
|
1201
|
+
dataSource,
|
|
1202
|
+
onRpcResponse
|
|
1203
|
+
}) => {
|
|
1204
|
+
const { VuuDataSource } = vuuUtils.useData();
|
|
1205
|
+
const menuBuilder = react.useCallback(
|
|
1206
|
+
(location, options) => {
|
|
1207
|
+
const descriptors = [];
|
|
1208
|
+
if (dataSource) {
|
|
1209
|
+
const { links, menu } = dataSource;
|
|
1210
|
+
const { visualLink } = dataSource;
|
|
1211
|
+
if (location === "grid" && links && !visualLink) {
|
|
1212
|
+
links.forEach((linkDescriptor) => {
|
|
1213
|
+
const { link, label: linkLabel } = linkDescriptor;
|
|
1214
|
+
const label = linkLabel ? linkLabel : link.toTable;
|
|
1215
|
+
descriptors.push({
|
|
1216
|
+
label: `Link to ${label}`,
|
|
1217
|
+
id: "link-table",
|
|
1218
|
+
options: linkDescriptor
|
|
1219
|
+
});
|
|
1220
|
+
});
|
|
1221
|
+
}
|
|
1222
|
+
if (menu && vuuTable.isTableLocation(location)) {
|
|
1223
|
+
const menuDescriptor = buildMenuDescriptorFromVuuMenu(
|
|
1224
|
+
menu,
|
|
1225
|
+
location,
|
|
1226
|
+
options
|
|
1227
|
+
);
|
|
1228
|
+
if (isRoot(menu) && vuuContextMenu.isGroupMenuItemDescriptor(menuDescriptor)) {
|
|
1229
|
+
descriptors.push(...menuDescriptor.children);
|
|
1230
|
+
} else if (menuDescriptor) {
|
|
1231
|
+
descriptors.push(menuDescriptor);
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
} else {
|
|
1235
|
+
throw Error("useVuuMenuActions no dataSource provided");
|
|
1236
|
+
}
|
|
1237
|
+
return descriptors;
|
|
1238
|
+
},
|
|
1239
|
+
[dataSource]
|
|
1240
|
+
);
|
|
1241
|
+
const { showDialog, closeDialog } = vuuPopups.useDialogContext();
|
|
1242
|
+
const showNotification = vuuPopups.useNotifications();
|
|
1243
|
+
const showBulkEditDialog = react.useCallback(
|
|
1244
|
+
(ds, table, columns) => {
|
|
1245
|
+
const sessionDs = new VuuDataSource({
|
|
1246
|
+
columns: columns?.map(vuuUtils.toColumnName),
|
|
1247
|
+
table,
|
|
1248
|
+
viewport: table.table
|
|
1249
|
+
});
|
|
1250
|
+
const handleClose = () => {
|
|
1251
|
+
sessionDs.unsubscribe();
|
|
1252
|
+
closeDialog();
|
|
1253
|
+
};
|
|
1254
|
+
showDialog(
|
|
1255
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1256
|
+
vuuTable.BulkEditDialog,
|
|
1257
|
+
{
|
|
1258
|
+
columns,
|
|
1259
|
+
sessionDs,
|
|
1260
|
+
parentDs: ds,
|
|
1261
|
+
closeDialog: handleClose
|
|
1262
|
+
}
|
|
1263
|
+
),
|
|
1264
|
+
"Bulk Amend"
|
|
1265
|
+
);
|
|
1266
|
+
return true;
|
|
1267
|
+
},
|
|
1268
|
+
[VuuDataSource, closeDialog, showDialog]
|
|
1269
|
+
);
|
|
1270
|
+
const showSessionEditingForm = react.useCallback(
|
|
1271
|
+
(ds, action) => {
|
|
1272
|
+
const { tableSchema } = action;
|
|
1273
|
+
if (tableSchema) {
|
|
1274
|
+
const formConfig = getFormConfig(action);
|
|
1275
|
+
showDialog(
|
|
1276
|
+
/* @__PURE__ */ jsxRuntime.jsx(SessionEditingForm, { ...formConfig, onClose: closeDialog }),
|
|
1277
|
+
"Set Parameters"
|
|
1278
|
+
);
|
|
1279
|
+
}
|
|
1280
|
+
const sessionDs = ds.createSessionDataSource?.(action.table);
|
|
1281
|
+
const handleSubmit = () => {
|
|
1282
|
+
sessionDs?.rpcCall?.(vuuUtils.viewportRpcRequest("VP_BULK_EDIT_SUBMIT_RPC"));
|
|
1283
|
+
closeDialog();
|
|
1284
|
+
};
|
|
1285
|
+
const handleChange = (isValid) => {
|
|
1286
|
+
console.log("placeholder: ", isValid);
|
|
1287
|
+
};
|
|
1288
|
+
if (sessionDs) {
|
|
1289
|
+
showDialog(
|
|
1290
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1291
|
+
vuuTable.BulkEditPanel,
|
|
1292
|
+
{
|
|
1293
|
+
dataSource: sessionDs,
|
|
1294
|
+
onSubmit: handleSubmit,
|
|
1295
|
+
parentDs: ds,
|
|
1296
|
+
onValidationStatusChange: handleChange
|
|
1297
|
+
}
|
|
1298
|
+
),
|
|
1299
|
+
"Multi Row Edit",
|
|
1300
|
+
[
|
|
1301
|
+
/* @__PURE__ */ jsxRuntime.jsx(core.Button, { onClick: closeDialog, children: "Cancel" }, "cancel"),
|
|
1302
|
+
/* @__PURE__ */ jsxRuntime.jsx(core.Button, { onClick: handleSubmit, children: "Save" }, "submit")
|
|
1303
|
+
]
|
|
1304
|
+
);
|
|
1305
|
+
}
|
|
1306
|
+
},
|
|
1307
|
+
[closeDialog, showDialog]
|
|
1308
|
+
);
|
|
1309
|
+
const getMenuRpcRequest = (options) => {
|
|
1310
|
+
const { rpcName } = options;
|
|
1311
|
+
if (isCellMenu(options)) {
|
|
1312
|
+
return {
|
|
1313
|
+
field: options.field,
|
|
1314
|
+
rowKey: options.rowKey,
|
|
1315
|
+
rpcName,
|
|
1316
|
+
value: options.value,
|
|
1317
|
+
type: "VIEW_PORT_MENU_CELL_RPC"
|
|
1318
|
+
};
|
|
1319
|
+
} else if (isRowMenu(options)) {
|
|
1320
|
+
return {
|
|
1321
|
+
rowKey: options.rowKey,
|
|
1322
|
+
row: options.row,
|
|
1323
|
+
rpcName,
|
|
1324
|
+
type: "VIEW_PORT_MENU_ROW_RPC"
|
|
1325
|
+
};
|
|
1326
|
+
} else if (isSelectionMenu(options)) {
|
|
1327
|
+
return {
|
|
1328
|
+
rpcName,
|
|
1329
|
+
type: "VIEW_PORT_MENUS_SELECT_RPC"
|
|
1330
|
+
};
|
|
1331
|
+
} else {
|
|
1332
|
+
return {
|
|
1333
|
+
rpcName,
|
|
1334
|
+
type: "VIEW_PORT_MENU_TABLE_RPC"
|
|
1335
|
+
};
|
|
1336
|
+
}
|
|
1337
|
+
};
|
|
1338
|
+
const menuActionHandler = react.useCallback(
|
|
1339
|
+
(menuItemId, options) => {
|
|
1340
|
+
if (clientSideMenuActionHandler?.(menuItemId, options)) {
|
|
1341
|
+
return true;
|
|
1342
|
+
} else if (menuItemId === "MENU_RPC_CALL") {
|
|
1343
|
+
const rpcRequest = getMenuRpcRequest(options);
|
|
1344
|
+
dataSource?.menuRpcCall(rpcRequest).then((rpcResponse) => {
|
|
1345
|
+
if (rpcResponse) {
|
|
1346
|
+
if (onRpcResponse?.(rpcResponse) === true) {
|
|
1347
|
+
return true;
|
|
1348
|
+
}
|
|
1349
|
+
if (vuuUtils.isActionMessage(rpcResponse)) {
|
|
1350
|
+
if (vuuUtils.hasShowNotificationAction(rpcResponse)) {
|
|
1351
|
+
const {
|
|
1352
|
+
action: { message, title = "Success" }
|
|
1353
|
+
} = rpcResponse;
|
|
1354
|
+
showNotification({
|
|
1355
|
+
type: "success",
|
|
1356
|
+
body: message,
|
|
1357
|
+
header: title
|
|
1358
|
+
});
|
|
1359
|
+
} else if (vuuUtils.isOpenBulkEditResponse(rpcResponse)) {
|
|
1360
|
+
showBulkEditDialog(
|
|
1361
|
+
dataSource,
|
|
1362
|
+
rpcResponse.action.table,
|
|
1363
|
+
getColumnsFromOptions(options)
|
|
1364
|
+
);
|
|
1365
|
+
} else if (vuuUtils.isSessionTableActionMessage(rpcResponse)) {
|
|
1366
|
+
showSessionEditingForm(dataSource, rpcResponse.action);
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
});
|
|
1371
|
+
return true;
|
|
1372
|
+
} else if (menuItemId === "link-table") {
|
|
1373
|
+
if (dataSource) {
|
|
1374
|
+
dataSource.visualLink = options;
|
|
1375
|
+
}
|
|
1376
|
+
return true;
|
|
1377
|
+
} else {
|
|
1378
|
+
console.log(
|
|
1379
|
+
`useViewServer handleMenuAction, can't handle action type ${menuItemId}`
|
|
1380
|
+
);
|
|
1381
|
+
}
|
|
1382
|
+
return false;
|
|
1383
|
+
},
|
|
1384
|
+
[
|
|
1385
|
+
clientSideMenuActionHandler,
|
|
1386
|
+
dataSource,
|
|
1387
|
+
onRpcResponse,
|
|
1388
|
+
showBulkEditDialog,
|
|
1389
|
+
showNotification,
|
|
1390
|
+
showSessionEditingForm
|
|
1391
|
+
]
|
|
1392
|
+
);
|
|
1393
|
+
return {
|
|
1394
|
+
menuBuilder,
|
|
1395
|
+
menuActionHandler
|
|
1396
|
+
};
|
|
1397
|
+
};
|
|
1398
|
+
|
|
1399
|
+
const useVuuTables = () => {
|
|
1400
|
+
const [tableSchemas, setTableSchemas] = react.useState();
|
|
1401
|
+
const { getServerAPI } = vuuUtils.useData();
|
|
1402
|
+
const buildTables = react.useCallback((schemas) => {
|
|
1403
|
+
const vuuTables = /* @__PURE__ */ new Map();
|
|
1404
|
+
schemas.forEach((schema) => {
|
|
1405
|
+
const { module, table } = schema.table;
|
|
1406
|
+
vuuTables.set(`${module}:${table}`, schema);
|
|
1407
|
+
});
|
|
1408
|
+
return vuuTables;
|
|
1409
|
+
}, []);
|
|
1410
|
+
react.useEffect(() => {
|
|
1411
|
+
async function fetchTableMetadata() {
|
|
1412
|
+
try {
|
|
1413
|
+
const server = await getServerAPI();
|
|
1414
|
+
const { tables } = await server.getTableList();
|
|
1415
|
+
const tableSchemas2 = await Promise.all(
|
|
1416
|
+
tables.map((vuuTable) => server.getTableSchema(vuuTable))
|
|
1417
|
+
);
|
|
1418
|
+
setTableSchemas(tableSchemas2);
|
|
1419
|
+
} catch (err) {
|
|
1420
|
+
console.warn(
|
|
1421
|
+
`useVuuTables: error fetching table metadata ${String(err)}`
|
|
1422
|
+
);
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
fetchTableMetadata();
|
|
1426
|
+
}, [buildTables, getServerAPI]);
|
|
1427
|
+
return tableSchemas;
|
|
1428
|
+
};
|
|
1429
|
+
|
|
1430
|
+
const useVisualLinks = (dataSource) => {
|
|
1431
|
+
const { dispatch } = vuuLayout.useViewContext();
|
|
1432
|
+
const clearVisualLinkTarget = react.useCallback(() => {
|
|
1433
|
+
if (dataSource.visualLink) {
|
|
1434
|
+
dispatch?.({
|
|
1435
|
+
type: "broadcast-message",
|
|
1436
|
+
message: {
|
|
1437
|
+
targetId: dataSource.visualLink.parentClientVpId,
|
|
1438
|
+
type: "highlight-off"
|
|
1439
|
+
}
|
|
1440
|
+
});
|
|
1441
|
+
}
|
|
1442
|
+
}, [dataSource, dispatch]);
|
|
1443
|
+
const removeVisualLink = react.useCallback(() => {
|
|
1444
|
+
if (dataSource.visualLink) {
|
|
1445
|
+
dispatch?.({
|
|
1446
|
+
type: "broadcast-message",
|
|
1447
|
+
message: {
|
|
1448
|
+
targetId: dataSource.visualLink.parentClientVpId,
|
|
1449
|
+
type: "highlight-off"
|
|
1450
|
+
}
|
|
1451
|
+
});
|
|
1452
|
+
dataSource.visualLink = void 0;
|
|
1453
|
+
}
|
|
1454
|
+
}, [dataSource, dispatch]);
|
|
1455
|
+
const handleLinkRemoved = react.useCallback(() => {
|
|
1456
|
+
dispatch?.({
|
|
1457
|
+
type: "remove-toolbar-contribution",
|
|
1458
|
+
location: "post-title"
|
|
1459
|
+
});
|
|
1460
|
+
}, [dispatch]);
|
|
1461
|
+
const highlightVisualLinkTarget = react.useCallback(() => {
|
|
1462
|
+
if (dataSource.visualLink) {
|
|
1463
|
+
dispatch?.({
|
|
1464
|
+
type: "broadcast-message",
|
|
1465
|
+
message: {
|
|
1466
|
+
targetId: dataSource.visualLink.parentClientVpId,
|
|
1467
|
+
type: "highlight-on"
|
|
1468
|
+
}
|
|
1469
|
+
});
|
|
1470
|
+
}
|
|
1471
|
+
}, [dataSource, dispatch]);
|
|
1472
|
+
const handleLinkCreated = react.useCallback(() => {
|
|
1473
|
+
dispatch?.({
|
|
1474
|
+
type: "add-toolbar-contribution",
|
|
1475
|
+
location: "post-title",
|
|
1476
|
+
content: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1477
|
+
vuuUiControls.IconButton,
|
|
1478
|
+
{
|
|
1479
|
+
"aria-label": "remove-link",
|
|
1480
|
+
icon: "link",
|
|
1481
|
+
onClick: removeVisualLink,
|
|
1482
|
+
onMouseEnter: highlightVisualLinkTarget,
|
|
1483
|
+
onMouseLeave: clearVisualLinkTarget,
|
|
1484
|
+
variant: "secondary"
|
|
1485
|
+
}
|
|
1486
|
+
)
|
|
1487
|
+
});
|
|
1488
|
+
}, [
|
|
1489
|
+
dispatch,
|
|
1490
|
+
removeVisualLink,
|
|
1491
|
+
highlightVisualLinkTarget,
|
|
1492
|
+
clearVisualLinkTarget
|
|
1493
|
+
]);
|
|
1494
|
+
react.useEffect(() => {
|
|
1495
|
+
dataSource.on("visual-link-created", handleLinkCreated);
|
|
1496
|
+
dataSource.on("visual-link-removed", handleLinkRemoved);
|
|
1497
|
+
return () => {
|
|
1498
|
+
dataSource.removeListener("visual-link-created", handleLinkCreated);
|
|
1499
|
+
dataSource.removeListener("visual-link-removed", handleLinkRemoved);
|
|
1500
|
+
};
|
|
1501
|
+
}, [dataSource, handleLinkCreated, handleLinkRemoved]);
|
|
1502
|
+
};
|
|
1503
|
+
|
|
1504
|
+
const getTypeaheadParams = (table, column, text = "", selectedValues = []) => {
|
|
1505
|
+
if (text !== "" && !selectedValues.includes(text.toLowerCase())) {
|
|
1506
|
+
return [table, column, text];
|
|
1507
|
+
}
|
|
1508
|
+
return [table, column];
|
|
1509
|
+
};
|
|
1510
|
+
const useTypeaheadSuggestions = () => {
|
|
1511
|
+
const { getServerAPI } = vuuUtils.useData();
|
|
1512
|
+
return react.useCallback(
|
|
1513
|
+
async (params) => {
|
|
1514
|
+
const rpcMessage = params.length === 2 ? {
|
|
1515
|
+
type: "RPC_CALL",
|
|
1516
|
+
service: "TypeAheadRpcHandler",
|
|
1517
|
+
method: "getUniqueFieldValues",
|
|
1518
|
+
params
|
|
1519
|
+
} : {
|
|
1520
|
+
type: "RPC_CALL",
|
|
1521
|
+
service: "TypeAheadRpcHandler",
|
|
1522
|
+
method: "getUniqueFieldValuesStartingWith",
|
|
1523
|
+
params
|
|
1524
|
+
};
|
|
1525
|
+
try {
|
|
1526
|
+
const serverAPI = await getServerAPI();
|
|
1527
|
+
const response = await serverAPI.rpcCall(rpcMessage);
|
|
1528
|
+
return response;
|
|
1529
|
+
} catch (err) {
|
|
1530
|
+
return false;
|
|
1531
|
+
}
|
|
1532
|
+
},
|
|
1533
|
+
[getServerAPI]
|
|
1534
|
+
);
|
|
1535
|
+
};
|
|
1536
|
+
|
|
1537
|
+
exports.CLEAN_FORM = CLEAN_FORM;
|
|
1538
|
+
exports.EditForm = EditForm;
|
|
1539
|
+
exports.OK = OK;
|
|
1540
|
+
exports.RestDataSourceProvider = RestDataSourceProvider;
|
|
1541
|
+
exports.SessionEditingForm = SessionEditingForm;
|
|
1542
|
+
exports.UnsavedChangesReport = UnsavedChangesReport;
|
|
1543
|
+
exports.VuuDataSourceProvider = VuuDataSourceProvider;
|
|
1544
|
+
exports.buildFormEditState = buildFormEditState;
|
|
1545
|
+
exports.buildValidationChecker = buildValidationChecker;
|
|
1546
|
+
exports.getDataItemEditControl = getDataItemEditControl;
|
|
1547
|
+
exports.getEditValidationRules = getEditValidationRules;
|
|
1548
|
+
exports.getTypeaheadParams = getTypeaheadParams;
|
|
1549
|
+
exports.isRestDataSourceExtension = isRestDataSourceExtension;
|
|
1550
|
+
exports.isRowMenu = isRowMenu;
|
|
1551
|
+
exports.isSelectionMenu = isSelectionMenu;
|
|
1552
|
+
exports.useEditForm = useEditForm;
|
|
1553
|
+
exports.useLookupValues = useLookupValues;
|
|
1554
|
+
exports.useSessionDataSource = useSessionDataSource;
|
|
1555
|
+
exports.useTypeaheadSuggestions = useTypeaheadSuggestions;
|
|
1556
|
+
exports.useVisualLinks = useVisualLinks;
|
|
1557
|
+
exports.useVuuMenuActions = useVuuMenuActions;
|
|
1558
|
+
exports.useVuuTables = useVuuTables;
|
|
43
1559
|
//# sourceMappingURL=index.js.map
|