@etsoo/materialui 1.6.13 → 1.6.14
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/lib/cjs/JsonTextField.d.ts +20 -0
- package/lib/cjs/JsonTextField.js +62 -0
- package/lib/cjs/custom/CustomAttributeArea.d.ts +11 -0
- package/lib/cjs/custom/CustomAttributeArea.js +194 -0
- package/lib/cjs/index.d.ts +2 -0
- package/lib/cjs/index.js +2 -0
- package/lib/mjs/JsonTextField.d.ts +20 -0
- package/lib/mjs/JsonTextField.js +56 -0
- package/lib/mjs/custom/CustomAttributeArea.d.ts +11 -0
- package/lib/mjs/custom/CustomAttributeArea.js +188 -0
- package/lib/mjs/index.d.ts +2 -0
- package/lib/mjs/index.js +2 -0
- package/package.json +3 -3
- package/src/JsonTextField.tsx +104 -0
- package/src/custom/CustomAttributeArea.tsx +454 -0
- package/src/index.ts +2 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { TextFieldProps } from "@mui/material/TextField";
|
|
2
|
+
/**
|
|
3
|
+
* JSON text field props
|
|
4
|
+
*/
|
|
5
|
+
export type JsonTextFieldProps = TextFieldProps & {
|
|
6
|
+
/**
|
|
7
|
+
* Whether the value is an array
|
|
8
|
+
*/
|
|
9
|
+
isArray?: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Edit button click handler
|
|
12
|
+
*/
|
|
13
|
+
onEdit?: (input: HTMLInputElement) => void;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* JSON text field component
|
|
17
|
+
* @param props Props
|
|
18
|
+
* @returns Component
|
|
19
|
+
*/
|
|
20
|
+
export declare function JsonTextField(props: JsonTextFieldProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.JsonTextField = JsonTextField;
|
|
7
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
8
|
+
const react_1 = require("@etsoo/react");
|
|
9
|
+
const IconButton_1 = __importDefault(require("@mui/material/IconButton"));
|
|
10
|
+
const InputAdornment_1 = __importDefault(require("@mui/material/InputAdornment"));
|
|
11
|
+
const TextField_1 = __importDefault(require("@mui/material/TextField"));
|
|
12
|
+
const Edit_1 = __importDefault(require("@mui/icons-material/Edit"));
|
|
13
|
+
const react_2 = __importDefault(require("react"));
|
|
14
|
+
const MUGlobal_1 = require("./MUGlobal");
|
|
15
|
+
const ReactApp_1 = require("./app/ReactApp");
|
|
16
|
+
/**
|
|
17
|
+
* JSON text field component
|
|
18
|
+
* @param props Props
|
|
19
|
+
* @returns Component
|
|
20
|
+
*/
|
|
21
|
+
function JsonTextField(props) {
|
|
22
|
+
// Destruct
|
|
23
|
+
const { fullWidth = true, inputRef, isArray = false, multiline = true, onChange, onEdit, rows = 3, slotProps, ...rest } = props;
|
|
24
|
+
// Slot props
|
|
25
|
+
const { input, inputLabel, ...restSlotProps } = slotProps ?? {};
|
|
26
|
+
const localRef = react_2.default.useRef(null);
|
|
27
|
+
// Global app
|
|
28
|
+
const app = (0, ReactApp_1.useRequiredAppContext)();
|
|
29
|
+
return ((0, jsx_runtime_1.jsx)(TextField_1.default, { fullWidth: fullWidth, inputRef: (0, react_1.useCombinedRefs)(inputRef, localRef), multiline: multiline, onChange: (event) => {
|
|
30
|
+
const value = event.target.value.trim();
|
|
31
|
+
let errorMessage = "";
|
|
32
|
+
if (value.length > 0) {
|
|
33
|
+
try {
|
|
34
|
+
const parsed = JSON.parse(value);
|
|
35
|
+
if (isArray && !Array.isArray(parsed)) {
|
|
36
|
+
errorMessage =
|
|
37
|
+
app.get("jsonDataArrayError") || "Value must be a JSON array";
|
|
38
|
+
}
|
|
39
|
+
if (typeof parsed !== "object") {
|
|
40
|
+
throw new Error("Parsed value is not an object");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
catch (e) {
|
|
44
|
+
errorMessage =
|
|
45
|
+
(app.get("jsonDataError") || "Invalid JSON text") + " - " + e;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
event.target.setCustomValidity(errorMessage);
|
|
49
|
+
event.target.reportValidity();
|
|
50
|
+
onChange?.(event);
|
|
51
|
+
}, rows: rows, slotProps: {
|
|
52
|
+
input: {
|
|
53
|
+
endAdornment: onEdit ? ((0, jsx_runtime_1.jsx)(InputAdornment_1.default, { position: "end", children: (0, jsx_runtime_1.jsx)(IconButton_1.default, { onClick: () => onEdit?.(localRef.current), children: (0, jsx_runtime_1.jsx)(Edit_1.default, {}) }) })) : undefined,
|
|
54
|
+
...input
|
|
55
|
+
},
|
|
56
|
+
inputLabel: {
|
|
57
|
+
shrink: MUGlobal_1.MUGlobal.inputFieldShrink,
|
|
58
|
+
...inputLabel
|
|
59
|
+
},
|
|
60
|
+
...restSlotProps
|
|
61
|
+
}, ...rest }));
|
|
62
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { TextFieldProps } from "@mui/material/TextField";
|
|
2
|
+
/**
|
|
3
|
+
* Custom attribute area properties
|
|
4
|
+
*/
|
|
5
|
+
export type CustomAttributeAreaProps = TextFieldProps & {};
|
|
6
|
+
/**
|
|
7
|
+
* Custom attribute area
|
|
8
|
+
* @param props Properties
|
|
9
|
+
* @returns Component
|
|
10
|
+
*/
|
|
11
|
+
export declare function CustomAttributeArea(props: CustomAttributeAreaProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.CustomAttributeArea = CustomAttributeArea;
|
|
7
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
8
|
+
const IconButton_1 = __importDefault(require("@mui/material/IconButton"));
|
|
9
|
+
const Add_1 = __importDefault(require("@mui/icons-material/Add"));
|
|
10
|
+
const Edit_1 = __importDefault(require("@mui/icons-material/Edit"));
|
|
11
|
+
const Delete_1 = __importDefault(require("@mui/icons-material/Delete"));
|
|
12
|
+
const DragIndicator_1 = __importDefault(require("@mui/icons-material/DragIndicator"));
|
|
13
|
+
const react_1 = __importDefault(require("react"));
|
|
14
|
+
const Card_1 = __importDefault(require("@mui/material/Card"));
|
|
15
|
+
const CardContent_1 = __importDefault(require("@mui/material/CardContent"));
|
|
16
|
+
const Grid_1 = __importDefault(require("@mui/material/Grid"));
|
|
17
|
+
const appscript_1 = require("@etsoo/appscript");
|
|
18
|
+
const Button_1 = __importDefault(require("@mui/material/Button"));
|
|
19
|
+
const material_1 = require("@mui/material");
|
|
20
|
+
const shared_1 = require("@etsoo/shared");
|
|
21
|
+
const CustomFieldUtils_1 = require("./CustomFieldUtils");
|
|
22
|
+
const ComboBox_1 = require("../ComboBox");
|
|
23
|
+
const InputField_1 = require("../InputField");
|
|
24
|
+
const JsonTextField_1 = require("../JsonTextField");
|
|
25
|
+
const FlexBox_1 = require("../FlexBox");
|
|
26
|
+
const DnDSortableList_1 = require("../DnDSortableList");
|
|
27
|
+
const ReactApp_1 = require("../app/ReactApp");
|
|
28
|
+
const size = { xs: 6, sm: 4, lg: 3, xl: 2 };
|
|
29
|
+
const smallSize = { xs: 3, sm: 2, xl: 1 };
|
|
30
|
+
const random4Digit = () => {
|
|
31
|
+
return Math.floor(1000 + Math.random() * 9000);
|
|
32
|
+
};
|
|
33
|
+
const isCamelCase = (name) => {
|
|
34
|
+
return /^[a-z][a-zA-Z0-9]*$/.test(name);
|
|
35
|
+
};
|
|
36
|
+
function InputItemUIs({ data }) {
|
|
37
|
+
// Global app
|
|
38
|
+
const app = (0, ReactApp_1.useRequiredAppContext)();
|
|
39
|
+
// Labels
|
|
40
|
+
const labels = app.getLabels("gridItemProps", "helperText", "label", "mainSlotProps", "nameB", "options", "optionsFormat", "refs", "size", "type");
|
|
41
|
+
const types = Object.keys(CustomFieldUtils_1.CustomFieldUtils.customFieldCreators);
|
|
42
|
+
const nameRef = react_1.default.useRef(null);
|
|
43
|
+
const optionsRef = react_1.default.useRef(null);
|
|
44
|
+
return ((0, jsx_runtime_1.jsxs)(Grid_1.default, { container: true, spacing: 2, marginTop: 1, children: [(0, jsx_runtime_1.jsx)(Grid_1.default, { size: { xs: 12, sm: 6 }, children: (0, jsx_runtime_1.jsx)(ComboBox_1.ComboBox, { name: "type", label: labels.type, inputRequired: true, size: "small", loadData: () => Promise.resolve(types.map((t) => ({ id: t, label: t }))), onValueChange: (item) => {
|
|
45
|
+
const type = item?.id;
|
|
46
|
+
optionsRef.current.disabled =
|
|
47
|
+
type !== "combobox" && type !== "select";
|
|
48
|
+
const nameInput = nameRef.current;
|
|
49
|
+
if (nameInput.value === "" &&
|
|
50
|
+
(type === "amountlabel" || type === "divider" || type === "label")) {
|
|
51
|
+
nameInput.value = type + random4Digit();
|
|
52
|
+
}
|
|
53
|
+
}, idValue: data?.type, fullWidth: true }) }), (0, jsx_runtime_1.jsx)(Grid_1.default, { size: { xs: 12, sm: 6 }, children: (0, jsx_runtime_1.jsx)(ComboBox_1.ComboBox, { name: "space", label: labels.size, inputRequired: true, size: "small", loadData: () => Promise.resolve(appscript_1.CustomFieldSpaceValues.map((t) => ({ id: t, label: t }))), idValue: data?.space, fullWidth: true }) }), (0, jsx_runtime_1.jsx)(Grid_1.default, { size: { xs: 12, sm: 6 }, children: (0, jsx_runtime_1.jsx)(InputField_1.InputField, { fullWidth: true, required: true, name: "name", size: "small", inputRef: nameRef, slotProps: { htmlInput: { maxLength: 128, readOnly: !!data } }, label: labels.nameB, defaultValue: data?.name ?? "" }) }), (0, jsx_runtime_1.jsx)(Grid_1.default, { size: { xs: 12, sm: 6 }, children: (0, jsx_runtime_1.jsx)(InputField_1.InputField, { fullWidth: true, name: "label", size: "small", slotProps: { htmlInput: { maxLength: 128 } }, label: labels.label, defaultValue: data?.label ?? "" }) }), (0, jsx_runtime_1.jsx)(Grid_1.default, { size: { xs: 12, sm: 12 }, children: (0, jsx_runtime_1.jsx)(InputField_1.InputField, { fullWidth: true, name: "helperText", size: "small", label: labels.helperText, defaultValue: data?.helperText ?? "" }) }), (0, jsx_runtime_1.jsx)(Grid_1.default, { size: { xs: 12, sm: 12 }, children: (0, jsx_runtime_1.jsx)(InputField_1.InputField, { fullWidth: true, name: "options", size: "small", multiline: true, rows: 2, label: labels.options, inputRef: optionsRef, helperText: labels.optionsFormat, slotProps: { htmlInput: { disabled: true } }, defaultValue: data?.options
|
|
54
|
+
? data.options
|
|
55
|
+
.map((o) => `${o.id}=${shared_1.DataTypes.getListItemLabel(o)}`)
|
|
56
|
+
.join("\n")
|
|
57
|
+
: "" }) }), (0, jsx_runtime_1.jsx)(Grid_1.default, { size: { xs: 12, sm: 12 }, children: (0, jsx_runtime_1.jsx)(JsonTextField_1.JsonTextField, { isArray: true, name: "refs", size: "small", multiline: false, label: labels.refs + " (JSON)", defaultValue: data?.refs ? JSON.stringify(data.refs) : "" }) }), (0, jsx_runtime_1.jsx)(Grid_1.default, { size: { xs: 12, sm: 12 }, children: (0, jsx_runtime_1.jsx)(JsonTextField_1.JsonTextField, { name: "gridItemProps", size: "small", multiline: false, label: labels.gridItemProps + " (JSON)", defaultValue: data?.gridItemProps ? JSON.stringify(data.gridItemProps) : "" }) }), (0, jsx_runtime_1.jsx)(Grid_1.default, { size: { xs: 12, sm: 12 }, children: (0, jsx_runtime_1.jsx)(JsonTextField_1.JsonTextField, { name: "mainSlotProps", size: "small", multiline: false, label: labels.mainSlotProps + " (JSON)", defaultValue: data?.mainSlotProps ? JSON.stringify(data.mainSlotProps) : "", helperText: '{"required":true}' }) })] }));
|
|
58
|
+
}
|
|
59
|
+
function InputUIs({ source, onChange }) {
|
|
60
|
+
// Global app
|
|
61
|
+
const app = (0, ReactApp_1.useRequiredAppContext)();
|
|
62
|
+
// Labels
|
|
63
|
+
const labels = app.getLabels("add", "delete", "edit", "sortTip", "dragIndicator");
|
|
64
|
+
const [items, setItems] = react_1.default.useState([]);
|
|
65
|
+
const doChange = (items) => {
|
|
66
|
+
setItems(items);
|
|
67
|
+
onChange(items);
|
|
68
|
+
};
|
|
69
|
+
const editItem = (item) => {
|
|
70
|
+
app.showInputDialog({
|
|
71
|
+
title: item ? labels.edit : labels.add,
|
|
72
|
+
message: "",
|
|
73
|
+
callback: async (form) => {
|
|
74
|
+
// Cancelled
|
|
75
|
+
if (form == null) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
// Validate form
|
|
79
|
+
if (!form.reportValidity()) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
// Form data
|
|
83
|
+
const { typeInput: type, spaceInput: space, name, label, helperText, options, refs, gridItemProps, mainSlotProps } = shared_1.DomUtils.dataAs(new FormData(form), {
|
|
84
|
+
typeInput: "string",
|
|
85
|
+
spaceInput: "string",
|
|
86
|
+
name: "string",
|
|
87
|
+
label: "string",
|
|
88
|
+
helperText: "string",
|
|
89
|
+
options: "string",
|
|
90
|
+
refs: "string",
|
|
91
|
+
gridItemProps: "string",
|
|
92
|
+
mainSlotProps: "string"
|
|
93
|
+
});
|
|
94
|
+
if (!type || !space || !name) {
|
|
95
|
+
return app.get("noData");
|
|
96
|
+
}
|
|
97
|
+
if (!isCamelCase(name)) {
|
|
98
|
+
shared_1.DomUtils.setFocus("name", form);
|
|
99
|
+
return app.get("invalidNaming") + " (camelCase)";
|
|
100
|
+
}
|
|
101
|
+
if (type !== "divider" && !label) {
|
|
102
|
+
shared_1.DomUtils.setFocus("label", form);
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
if (item == null && items.some((item) => item.name === name)) {
|
|
106
|
+
return app.get("itemExists")?.format(name);
|
|
107
|
+
}
|
|
108
|
+
const optionsJson = options
|
|
109
|
+
? options.split("\n").map((line) => {
|
|
110
|
+
const [id, ...labelParts] = line.split("=");
|
|
111
|
+
return { id, label: labelParts.join("=") };
|
|
112
|
+
})
|
|
113
|
+
: undefined;
|
|
114
|
+
const refsJson = refs ? JSON.parse(refs) : undefined;
|
|
115
|
+
const gridItemPropsJson = gridItemProps
|
|
116
|
+
? JSON.parse(gridItemProps)
|
|
117
|
+
: undefined;
|
|
118
|
+
const mainSlotPropsJson = mainSlotProps
|
|
119
|
+
? JSON.parse(mainSlotProps)
|
|
120
|
+
: undefined;
|
|
121
|
+
if (item == null) {
|
|
122
|
+
const newItem = {
|
|
123
|
+
type,
|
|
124
|
+
name,
|
|
125
|
+
space: space,
|
|
126
|
+
label,
|
|
127
|
+
helperText,
|
|
128
|
+
options: optionsJson,
|
|
129
|
+
refs: refsJson,
|
|
130
|
+
gridItemProps: gridItemPropsJson,
|
|
131
|
+
mainSlotProps: mainSlotPropsJson
|
|
132
|
+
};
|
|
133
|
+
doChange([...items, newItem]);
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
item.type = type;
|
|
137
|
+
item.space = space;
|
|
138
|
+
item.name = name;
|
|
139
|
+
item.label = label;
|
|
140
|
+
item.helperText = helperText;
|
|
141
|
+
item.options = optionsJson;
|
|
142
|
+
item.refs = refsJson;
|
|
143
|
+
item.gridItemProps = gridItemPropsJson;
|
|
144
|
+
item.mainSlotProps = mainSlotPropsJson;
|
|
145
|
+
doChange([...items]);
|
|
146
|
+
}
|
|
147
|
+
return;
|
|
148
|
+
},
|
|
149
|
+
inputs: (0, jsx_runtime_1.jsx)(InputItemUIs, { data: item })
|
|
150
|
+
});
|
|
151
|
+
};
|
|
152
|
+
react_1.default.useEffect(() => {
|
|
153
|
+
try {
|
|
154
|
+
if (source) {
|
|
155
|
+
const parsed = JSON.parse(source);
|
|
156
|
+
if (Array.isArray(parsed)) {
|
|
157
|
+
setItems(parsed);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
console.error("Failed to parse source:", error);
|
|
163
|
+
}
|
|
164
|
+
}, [source]);
|
|
165
|
+
return ((0, jsx_runtime_1.jsxs)(react_1.default.Fragment, { children: [(0, jsx_runtime_1.jsxs)(FlexBox_1.HBox, { marginBottom: 0.5, gap: 1, alignItems: "center", children: [(0, jsx_runtime_1.jsx)(material_1.Typography, { children: labels.sortTip }), (0, jsx_runtime_1.jsx)(Button_1.default, { size: "small", color: "primary", variant: "outlined", startIcon: (0, jsx_runtime_1.jsx)(Add_1.default, {}), onClick: () => editItem(), children: labels.add })] }), (0, jsx_runtime_1.jsx)(Card_1.default, { children: (0, jsx_runtime_1.jsx)(CardContent_1.default, { children: (0, jsx_runtime_1.jsx)(Grid_1.default, { container: true, spacing: 0, children: (0, jsx_runtime_1.jsx)(DnDSortableList_1.DnDSortableList, { items: items, idField: (item) => item.name, labelField: (item) => item.label || item.name, onChange: (items) => doChange(items), itemRenderer: (data, style, { sortable: { index }, ref, handleRef }) => ((0, jsx_runtime_1.jsxs)(Grid_1.default, { container: true, size: { xs: 12, sm: 12 }, ref: ref, style: style, gap: 1, children: [(0, jsx_runtime_1.jsx)(Grid_1.default, { size: smallSize, children: (0, jsx_runtime_1.jsx)(IconButton_1.default, { style: { cursor: "move" }, size: "small", title: labels.dragIndicator, ref: handleRef, children: (0, jsx_runtime_1.jsx)(DragIndicator_1.default, {}) }) }), (0, jsx_runtime_1.jsxs)(Grid_1.default, { size: size, children: [index + 1, " - ", data.type, " / ", data.space] }), (0, jsx_runtime_1.jsxs)(Grid_1.default, { size: size, children: [data.name, " - ", data.label] }), (0, jsx_runtime_1.jsxs)(Grid_1.default, { size: smallSize, children: [(0, jsx_runtime_1.jsx)(IconButton_1.default, { size: "small", title: labels.delete, onClick: () => doChange(items.filter((item) => item.name !== data.name)), children: (0, jsx_runtime_1.jsx)(Delete_1.default, {}) }), (0, jsx_runtime_1.jsx)(IconButton_1.default, { size: "small", title: labels.edit, onClick: () => editItem(data), children: (0, jsx_runtime_1.jsx)(Edit_1.default, {}) })] })] })) }) }) }) })] }));
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Custom attribute area
|
|
169
|
+
* @param props Properties
|
|
170
|
+
* @returns Component
|
|
171
|
+
*/
|
|
172
|
+
function CustomAttributeArea(props) {
|
|
173
|
+
// Global app
|
|
174
|
+
const app = (0, ReactApp_1.useRequiredAppContext)();
|
|
175
|
+
// Destruct
|
|
176
|
+
const { label = app.get("attributeDefinition"), ...rest } = props;
|
|
177
|
+
const ref = react_1.default.useRef([]);
|
|
178
|
+
const showUI = (input) => {
|
|
179
|
+
app.showInputDialog({
|
|
180
|
+
title: label,
|
|
181
|
+
message: "",
|
|
182
|
+
fullScreen: true,
|
|
183
|
+
callback: (form) => {
|
|
184
|
+
if (form == null) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
input.value = ref.current.length > 0 ? JSON.stringify(ref.current) : "";
|
|
188
|
+
},
|
|
189
|
+
inputs: ((0, jsx_runtime_1.jsx)(InputUIs, { source: input.value, onChange: (items) => (ref.current = items) }))
|
|
190
|
+
});
|
|
191
|
+
};
|
|
192
|
+
// Layout
|
|
193
|
+
return (0, jsx_runtime_1.jsx)(JsonTextField_1.JsonTextField, { label: label, onEdit: showUI, isArray: true, ...rest });
|
|
194
|
+
}
|
package/lib/cjs/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export * from "./app/ISmartERPUser";
|
|
|
6
6
|
export * from "./app/Labels";
|
|
7
7
|
export * from "./app/ReactApp";
|
|
8
8
|
export * from "./app/ServiceApp";
|
|
9
|
+
export * from "./custom/CustomAttributeArea";
|
|
9
10
|
export * from "./custom/CustomFieldUtils";
|
|
10
11
|
export * from "./custom/CustomFieldViewer";
|
|
11
12
|
export * from "./custom/CustomFieldWindow";
|
|
@@ -73,6 +74,7 @@ export * from "./InputTipField";
|
|
|
73
74
|
export * from "./IntInputField";
|
|
74
75
|
export * from "./ItemList";
|
|
75
76
|
export * from "./JsonDataInput";
|
|
77
|
+
export * from "./JsonTextField";
|
|
76
78
|
export * from "./LineChart";
|
|
77
79
|
export * from "./LinkEx";
|
|
78
80
|
export * from "./ListChooser";
|
package/lib/cjs/index.js
CHANGED
|
@@ -22,6 +22,7 @@ __exportStar(require("./app/ISmartERPUser"), exports);
|
|
|
22
22
|
__exportStar(require("./app/Labels"), exports);
|
|
23
23
|
__exportStar(require("./app/ReactApp"), exports);
|
|
24
24
|
__exportStar(require("./app/ServiceApp"), exports);
|
|
25
|
+
__exportStar(require("./custom/CustomAttributeArea"), exports);
|
|
25
26
|
__exportStar(require("./custom/CustomFieldUtils"), exports);
|
|
26
27
|
__exportStar(require("./custom/CustomFieldViewer"), exports);
|
|
27
28
|
__exportStar(require("./custom/CustomFieldWindow"), exports);
|
|
@@ -89,6 +90,7 @@ __exportStar(require("./InputTipField"), exports);
|
|
|
89
90
|
__exportStar(require("./IntInputField"), exports);
|
|
90
91
|
__exportStar(require("./ItemList"), exports);
|
|
91
92
|
__exportStar(require("./JsonDataInput"), exports);
|
|
93
|
+
__exportStar(require("./JsonTextField"), exports);
|
|
92
94
|
__exportStar(require("./LineChart"), exports);
|
|
93
95
|
__exportStar(require("./LinkEx"), exports);
|
|
94
96
|
__exportStar(require("./ListChooser"), exports);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { TextFieldProps } from "@mui/material/TextField";
|
|
2
|
+
/**
|
|
3
|
+
* JSON text field props
|
|
4
|
+
*/
|
|
5
|
+
export type JsonTextFieldProps = TextFieldProps & {
|
|
6
|
+
/**
|
|
7
|
+
* Whether the value is an array
|
|
8
|
+
*/
|
|
9
|
+
isArray?: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Edit button click handler
|
|
12
|
+
*/
|
|
13
|
+
onEdit?: (input: HTMLInputElement) => void;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* JSON text field component
|
|
17
|
+
* @param props Props
|
|
18
|
+
* @returns Component
|
|
19
|
+
*/
|
|
20
|
+
export declare function JsonTextField(props: JsonTextFieldProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useCombinedRefs } from "@etsoo/react";
|
|
3
|
+
import IconButton from "@mui/material/IconButton";
|
|
4
|
+
import InputAdornment from "@mui/material/InputAdornment";
|
|
5
|
+
import TextField from "@mui/material/TextField";
|
|
6
|
+
import EditIcon from "@mui/icons-material/Edit";
|
|
7
|
+
import React from "react";
|
|
8
|
+
import { MUGlobal } from "./MUGlobal";
|
|
9
|
+
import { useRequiredAppContext } from "./app/ReactApp";
|
|
10
|
+
/**
|
|
11
|
+
* JSON text field component
|
|
12
|
+
* @param props Props
|
|
13
|
+
* @returns Component
|
|
14
|
+
*/
|
|
15
|
+
export function JsonTextField(props) {
|
|
16
|
+
// Destruct
|
|
17
|
+
const { fullWidth = true, inputRef, isArray = false, multiline = true, onChange, onEdit, rows = 3, slotProps, ...rest } = props;
|
|
18
|
+
// Slot props
|
|
19
|
+
const { input, inputLabel, ...restSlotProps } = slotProps ?? {};
|
|
20
|
+
const localRef = React.useRef(null);
|
|
21
|
+
// Global app
|
|
22
|
+
const app = useRequiredAppContext();
|
|
23
|
+
return (_jsx(TextField, { fullWidth: fullWidth, inputRef: useCombinedRefs(inputRef, localRef), multiline: multiline, onChange: (event) => {
|
|
24
|
+
const value = event.target.value.trim();
|
|
25
|
+
let errorMessage = "";
|
|
26
|
+
if (value.length > 0) {
|
|
27
|
+
try {
|
|
28
|
+
const parsed = JSON.parse(value);
|
|
29
|
+
if (isArray && !Array.isArray(parsed)) {
|
|
30
|
+
errorMessage =
|
|
31
|
+
app.get("jsonDataArrayError") || "Value must be a JSON array";
|
|
32
|
+
}
|
|
33
|
+
if (typeof parsed !== "object") {
|
|
34
|
+
throw new Error("Parsed value is not an object");
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch (e) {
|
|
38
|
+
errorMessage =
|
|
39
|
+
(app.get("jsonDataError") || "Invalid JSON text") + " - " + e;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
event.target.setCustomValidity(errorMessage);
|
|
43
|
+
event.target.reportValidity();
|
|
44
|
+
onChange?.(event);
|
|
45
|
+
}, rows: rows, slotProps: {
|
|
46
|
+
input: {
|
|
47
|
+
endAdornment: onEdit ? (_jsx(InputAdornment, { position: "end", children: _jsx(IconButton, { onClick: () => onEdit?.(localRef.current), children: _jsx(EditIcon, {}) }) })) : undefined,
|
|
48
|
+
...input
|
|
49
|
+
},
|
|
50
|
+
inputLabel: {
|
|
51
|
+
shrink: MUGlobal.inputFieldShrink,
|
|
52
|
+
...inputLabel
|
|
53
|
+
},
|
|
54
|
+
...restSlotProps
|
|
55
|
+
}, ...rest }));
|
|
56
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { TextFieldProps } from "@mui/material/TextField";
|
|
2
|
+
/**
|
|
3
|
+
* Custom attribute area properties
|
|
4
|
+
*/
|
|
5
|
+
export type CustomAttributeAreaProps = TextFieldProps & {};
|
|
6
|
+
/**
|
|
7
|
+
* Custom attribute area
|
|
8
|
+
* @param props Properties
|
|
9
|
+
* @returns Component
|
|
10
|
+
*/
|
|
11
|
+
export declare function CustomAttributeArea(props: CustomAttributeAreaProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import IconButton from "@mui/material/IconButton";
|
|
3
|
+
import AddIcon from "@mui/icons-material/Add";
|
|
4
|
+
import EditIcon from "@mui/icons-material/Edit";
|
|
5
|
+
import DeleteIcon from "@mui/icons-material/Delete";
|
|
6
|
+
import DragIndicatorIcon from "@mui/icons-material/DragIndicator";
|
|
7
|
+
import React from "react";
|
|
8
|
+
import Card from "@mui/material/Card";
|
|
9
|
+
import CardContent from "@mui/material/CardContent";
|
|
10
|
+
import Grid from "@mui/material/Grid";
|
|
11
|
+
import { CustomFieldSpaceValues } from "@etsoo/appscript";
|
|
12
|
+
import Button from "@mui/material/Button";
|
|
13
|
+
import { Typography } from "@mui/material";
|
|
14
|
+
import { DataTypes, DomUtils } from "@etsoo/shared";
|
|
15
|
+
import { CustomFieldUtils } from "./CustomFieldUtils";
|
|
16
|
+
import { ComboBox } from "../ComboBox";
|
|
17
|
+
import { InputField } from "../InputField";
|
|
18
|
+
import { JsonTextField } from "../JsonTextField";
|
|
19
|
+
import { HBox } from "../FlexBox";
|
|
20
|
+
import { DnDSortableList } from "../DnDSortableList";
|
|
21
|
+
import { useRequiredAppContext } from "../app/ReactApp";
|
|
22
|
+
const size = { xs: 6, sm: 4, lg: 3, xl: 2 };
|
|
23
|
+
const smallSize = { xs: 3, sm: 2, xl: 1 };
|
|
24
|
+
const random4Digit = () => {
|
|
25
|
+
return Math.floor(1000 + Math.random() * 9000);
|
|
26
|
+
};
|
|
27
|
+
const isCamelCase = (name) => {
|
|
28
|
+
return /^[a-z][a-zA-Z0-9]*$/.test(name);
|
|
29
|
+
};
|
|
30
|
+
function InputItemUIs({ data }) {
|
|
31
|
+
// Global app
|
|
32
|
+
const app = useRequiredAppContext();
|
|
33
|
+
// Labels
|
|
34
|
+
const labels = app.getLabels("gridItemProps", "helperText", "label", "mainSlotProps", "nameB", "options", "optionsFormat", "refs", "size", "type");
|
|
35
|
+
const types = Object.keys(CustomFieldUtils.customFieldCreators);
|
|
36
|
+
const nameRef = React.useRef(null);
|
|
37
|
+
const optionsRef = React.useRef(null);
|
|
38
|
+
return (_jsxs(Grid, { container: true, spacing: 2, marginTop: 1, children: [_jsx(Grid, { size: { xs: 12, sm: 6 }, children: _jsx(ComboBox, { name: "type", label: labels.type, inputRequired: true, size: "small", loadData: () => Promise.resolve(types.map((t) => ({ id: t, label: t }))), onValueChange: (item) => {
|
|
39
|
+
const type = item?.id;
|
|
40
|
+
optionsRef.current.disabled =
|
|
41
|
+
type !== "combobox" && type !== "select";
|
|
42
|
+
const nameInput = nameRef.current;
|
|
43
|
+
if (nameInput.value === "" &&
|
|
44
|
+
(type === "amountlabel" || type === "divider" || type === "label")) {
|
|
45
|
+
nameInput.value = type + random4Digit();
|
|
46
|
+
}
|
|
47
|
+
}, idValue: data?.type, fullWidth: true }) }), _jsx(Grid, { size: { xs: 12, sm: 6 }, children: _jsx(ComboBox, { name: "space", label: labels.size, inputRequired: true, size: "small", loadData: () => Promise.resolve(CustomFieldSpaceValues.map((t) => ({ id: t, label: t }))), idValue: data?.space, fullWidth: true }) }), _jsx(Grid, { size: { xs: 12, sm: 6 }, children: _jsx(InputField, { fullWidth: true, required: true, name: "name", size: "small", inputRef: nameRef, slotProps: { htmlInput: { maxLength: 128, readOnly: !!data } }, label: labels.nameB, defaultValue: data?.name ?? "" }) }), _jsx(Grid, { size: { xs: 12, sm: 6 }, children: _jsx(InputField, { fullWidth: true, name: "label", size: "small", slotProps: { htmlInput: { maxLength: 128 } }, label: labels.label, defaultValue: data?.label ?? "" }) }), _jsx(Grid, { size: { xs: 12, sm: 12 }, children: _jsx(InputField, { fullWidth: true, name: "helperText", size: "small", label: labels.helperText, defaultValue: data?.helperText ?? "" }) }), _jsx(Grid, { size: { xs: 12, sm: 12 }, children: _jsx(InputField, { fullWidth: true, name: "options", size: "small", multiline: true, rows: 2, label: labels.options, inputRef: optionsRef, helperText: labels.optionsFormat, slotProps: { htmlInput: { disabled: true } }, defaultValue: data?.options
|
|
48
|
+
? data.options
|
|
49
|
+
.map((o) => `${o.id}=${DataTypes.getListItemLabel(o)}`)
|
|
50
|
+
.join("\n")
|
|
51
|
+
: "" }) }), _jsx(Grid, { size: { xs: 12, sm: 12 }, children: _jsx(JsonTextField, { isArray: true, name: "refs", size: "small", multiline: false, label: labels.refs + " (JSON)", defaultValue: data?.refs ? JSON.stringify(data.refs) : "" }) }), _jsx(Grid, { size: { xs: 12, sm: 12 }, children: _jsx(JsonTextField, { name: "gridItemProps", size: "small", multiline: false, label: labels.gridItemProps + " (JSON)", defaultValue: data?.gridItemProps ? JSON.stringify(data.gridItemProps) : "" }) }), _jsx(Grid, { size: { xs: 12, sm: 12 }, children: _jsx(JsonTextField, { name: "mainSlotProps", size: "small", multiline: false, label: labels.mainSlotProps + " (JSON)", defaultValue: data?.mainSlotProps ? JSON.stringify(data.mainSlotProps) : "", helperText: '{"required":true}' }) })] }));
|
|
52
|
+
}
|
|
53
|
+
function InputUIs({ source, onChange }) {
|
|
54
|
+
// Global app
|
|
55
|
+
const app = useRequiredAppContext();
|
|
56
|
+
// Labels
|
|
57
|
+
const labels = app.getLabels("add", "delete", "edit", "sortTip", "dragIndicator");
|
|
58
|
+
const [items, setItems] = React.useState([]);
|
|
59
|
+
const doChange = (items) => {
|
|
60
|
+
setItems(items);
|
|
61
|
+
onChange(items);
|
|
62
|
+
};
|
|
63
|
+
const editItem = (item) => {
|
|
64
|
+
app.showInputDialog({
|
|
65
|
+
title: item ? labels.edit : labels.add,
|
|
66
|
+
message: "",
|
|
67
|
+
callback: async (form) => {
|
|
68
|
+
// Cancelled
|
|
69
|
+
if (form == null) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
// Validate form
|
|
73
|
+
if (!form.reportValidity()) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
// Form data
|
|
77
|
+
const { typeInput: type, spaceInput: space, name, label, helperText, options, refs, gridItemProps, mainSlotProps } = DomUtils.dataAs(new FormData(form), {
|
|
78
|
+
typeInput: "string",
|
|
79
|
+
spaceInput: "string",
|
|
80
|
+
name: "string",
|
|
81
|
+
label: "string",
|
|
82
|
+
helperText: "string",
|
|
83
|
+
options: "string",
|
|
84
|
+
refs: "string",
|
|
85
|
+
gridItemProps: "string",
|
|
86
|
+
mainSlotProps: "string"
|
|
87
|
+
});
|
|
88
|
+
if (!type || !space || !name) {
|
|
89
|
+
return app.get("noData");
|
|
90
|
+
}
|
|
91
|
+
if (!isCamelCase(name)) {
|
|
92
|
+
DomUtils.setFocus("name", form);
|
|
93
|
+
return app.get("invalidNaming") + " (camelCase)";
|
|
94
|
+
}
|
|
95
|
+
if (type !== "divider" && !label) {
|
|
96
|
+
DomUtils.setFocus("label", form);
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
if (item == null && items.some((item) => item.name === name)) {
|
|
100
|
+
return app.get("itemExists")?.format(name);
|
|
101
|
+
}
|
|
102
|
+
const optionsJson = options
|
|
103
|
+
? options.split("\n").map((line) => {
|
|
104
|
+
const [id, ...labelParts] = line.split("=");
|
|
105
|
+
return { id, label: labelParts.join("=") };
|
|
106
|
+
})
|
|
107
|
+
: undefined;
|
|
108
|
+
const refsJson = refs ? JSON.parse(refs) : undefined;
|
|
109
|
+
const gridItemPropsJson = gridItemProps
|
|
110
|
+
? JSON.parse(gridItemProps)
|
|
111
|
+
: undefined;
|
|
112
|
+
const mainSlotPropsJson = mainSlotProps
|
|
113
|
+
? JSON.parse(mainSlotProps)
|
|
114
|
+
: undefined;
|
|
115
|
+
if (item == null) {
|
|
116
|
+
const newItem = {
|
|
117
|
+
type,
|
|
118
|
+
name,
|
|
119
|
+
space: space,
|
|
120
|
+
label,
|
|
121
|
+
helperText,
|
|
122
|
+
options: optionsJson,
|
|
123
|
+
refs: refsJson,
|
|
124
|
+
gridItemProps: gridItemPropsJson,
|
|
125
|
+
mainSlotProps: mainSlotPropsJson
|
|
126
|
+
};
|
|
127
|
+
doChange([...items, newItem]);
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
item.type = type;
|
|
131
|
+
item.space = space;
|
|
132
|
+
item.name = name;
|
|
133
|
+
item.label = label;
|
|
134
|
+
item.helperText = helperText;
|
|
135
|
+
item.options = optionsJson;
|
|
136
|
+
item.refs = refsJson;
|
|
137
|
+
item.gridItemProps = gridItemPropsJson;
|
|
138
|
+
item.mainSlotProps = mainSlotPropsJson;
|
|
139
|
+
doChange([...items]);
|
|
140
|
+
}
|
|
141
|
+
return;
|
|
142
|
+
},
|
|
143
|
+
inputs: _jsx(InputItemUIs, { data: item })
|
|
144
|
+
});
|
|
145
|
+
};
|
|
146
|
+
React.useEffect(() => {
|
|
147
|
+
try {
|
|
148
|
+
if (source) {
|
|
149
|
+
const parsed = JSON.parse(source);
|
|
150
|
+
if (Array.isArray(parsed)) {
|
|
151
|
+
setItems(parsed);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
console.error("Failed to parse source:", error);
|
|
157
|
+
}
|
|
158
|
+
}, [source]);
|
|
159
|
+
return (_jsxs(React.Fragment, { children: [_jsxs(HBox, { marginBottom: 0.5, gap: 1, alignItems: "center", children: [_jsx(Typography, { children: labels.sortTip }), _jsx(Button, { size: "small", color: "primary", variant: "outlined", startIcon: _jsx(AddIcon, {}), onClick: () => editItem(), children: labels.add })] }), _jsx(Card, { children: _jsx(CardContent, { children: _jsx(Grid, { container: true, spacing: 0, children: _jsx(DnDSortableList, { items: items, idField: (item) => item.name, labelField: (item) => item.label || item.name, onChange: (items) => doChange(items), itemRenderer: (data, style, { sortable: { index }, ref, handleRef }) => (_jsxs(Grid, { container: true, size: { xs: 12, sm: 12 }, ref: ref, style: style, gap: 1, children: [_jsx(Grid, { size: smallSize, children: _jsx(IconButton, { style: { cursor: "move" }, size: "small", title: labels.dragIndicator, ref: handleRef, children: _jsx(DragIndicatorIcon, {}) }) }), _jsxs(Grid, { size: size, children: [index + 1, " - ", data.type, " / ", data.space] }), _jsxs(Grid, { size: size, children: [data.name, " - ", data.label] }), _jsxs(Grid, { size: smallSize, children: [_jsx(IconButton, { size: "small", title: labels.delete, onClick: () => doChange(items.filter((item) => item.name !== data.name)), children: _jsx(DeleteIcon, {}) }), _jsx(IconButton, { size: "small", title: labels.edit, onClick: () => editItem(data), children: _jsx(EditIcon, {}) })] })] })) }) }) }) })] }));
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Custom attribute area
|
|
163
|
+
* @param props Properties
|
|
164
|
+
* @returns Component
|
|
165
|
+
*/
|
|
166
|
+
export function CustomAttributeArea(props) {
|
|
167
|
+
// Global app
|
|
168
|
+
const app = useRequiredAppContext();
|
|
169
|
+
// Destruct
|
|
170
|
+
const { label = app.get("attributeDefinition"), ...rest } = props;
|
|
171
|
+
const ref = React.useRef([]);
|
|
172
|
+
const showUI = (input) => {
|
|
173
|
+
app.showInputDialog({
|
|
174
|
+
title: label,
|
|
175
|
+
message: "",
|
|
176
|
+
fullScreen: true,
|
|
177
|
+
callback: (form) => {
|
|
178
|
+
if (form == null) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
input.value = ref.current.length > 0 ? JSON.stringify(ref.current) : "";
|
|
182
|
+
},
|
|
183
|
+
inputs: (_jsx(InputUIs, { source: input.value, onChange: (items) => (ref.current = items) }))
|
|
184
|
+
});
|
|
185
|
+
};
|
|
186
|
+
// Layout
|
|
187
|
+
return _jsx(JsonTextField, { label: label, onEdit: showUI, isArray: true, ...rest });
|
|
188
|
+
}
|
package/lib/mjs/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export * from "./app/ISmartERPUser";
|
|
|
6
6
|
export * from "./app/Labels";
|
|
7
7
|
export * from "./app/ReactApp";
|
|
8
8
|
export * from "./app/ServiceApp";
|
|
9
|
+
export * from "./custom/CustomAttributeArea";
|
|
9
10
|
export * from "./custom/CustomFieldUtils";
|
|
10
11
|
export * from "./custom/CustomFieldViewer";
|
|
11
12
|
export * from "./custom/CustomFieldWindow";
|
|
@@ -73,6 +74,7 @@ export * from "./InputTipField";
|
|
|
73
74
|
export * from "./IntInputField";
|
|
74
75
|
export * from "./ItemList";
|
|
75
76
|
export * from "./JsonDataInput";
|
|
77
|
+
export * from "./JsonTextField";
|
|
76
78
|
export * from "./LineChart";
|
|
77
79
|
export * from "./LinkEx";
|
|
78
80
|
export * from "./ListChooser";
|
package/lib/mjs/index.js
CHANGED
|
@@ -6,6 +6,7 @@ export * from "./app/ISmartERPUser";
|
|
|
6
6
|
export * from "./app/Labels";
|
|
7
7
|
export * from "./app/ReactApp";
|
|
8
8
|
export * from "./app/ServiceApp";
|
|
9
|
+
export * from "./custom/CustomAttributeArea";
|
|
9
10
|
export * from "./custom/CustomFieldUtils";
|
|
10
11
|
export * from "./custom/CustomFieldViewer";
|
|
11
12
|
export * from "./custom/CustomFieldWindow";
|
|
@@ -73,6 +74,7 @@ export * from "./InputTipField";
|
|
|
73
74
|
export * from "./IntInputField";
|
|
74
75
|
export * from "./ItemList";
|
|
75
76
|
export * from "./JsonDataInput";
|
|
77
|
+
export * from "./JsonTextField";
|
|
76
78
|
export * from "./LineChart";
|
|
77
79
|
export * from "./LinkEx";
|
|
78
80
|
export * from "./ListChooser";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@etsoo/materialui",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.14",
|
|
4
4
|
"description": "TypeScript Material-UI Implementation",
|
|
5
5
|
"main": "lib/cjs/index.js",
|
|
6
6
|
"module": "lib/mjs/index.js",
|
|
@@ -39,9 +39,9 @@
|
|
|
39
39
|
"@dnd-kit/react": "^0.3.2",
|
|
40
40
|
"@emotion/react": "^11.14.0",
|
|
41
41
|
"@emotion/styled": "^11.14.1",
|
|
42
|
-
"@etsoo/appscript": "^1.6.
|
|
42
|
+
"@etsoo/appscript": "^1.6.56",
|
|
43
43
|
"@etsoo/notificationbase": "^1.1.66",
|
|
44
|
-
"@etsoo/react": "^1.8.
|
|
44
|
+
"@etsoo/react": "^1.8.76",
|
|
45
45
|
"@etsoo/shared": "^1.2.80",
|
|
46
46
|
"@mui/icons-material": "^7.3.8",
|
|
47
47
|
"@mui/material": "^7.3.8",
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { useCombinedRefs } from "@etsoo/react";
|
|
2
|
+
import IconButton from "@mui/material/IconButton";
|
|
3
|
+
import InputAdornment from "@mui/material/InputAdornment";
|
|
4
|
+
import TextField, { TextFieldProps } from "@mui/material/TextField";
|
|
5
|
+
import EditIcon from "@mui/icons-material/Edit";
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { MUGlobal } from "./MUGlobal";
|
|
8
|
+
import { useRequiredAppContext } from "./app/ReactApp";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* JSON text field props
|
|
12
|
+
*/
|
|
13
|
+
export type JsonTextFieldProps = TextFieldProps & {
|
|
14
|
+
/**
|
|
15
|
+
* Whether the value is an array
|
|
16
|
+
*/
|
|
17
|
+
isArray?: boolean;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Edit button click handler
|
|
21
|
+
*/
|
|
22
|
+
onEdit?: (input: HTMLInputElement) => void;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* JSON text field component
|
|
27
|
+
* @param props Props
|
|
28
|
+
* @returns Component
|
|
29
|
+
*/
|
|
30
|
+
export function JsonTextField(props: JsonTextFieldProps) {
|
|
31
|
+
// Destruct
|
|
32
|
+
const {
|
|
33
|
+
fullWidth = true,
|
|
34
|
+
inputRef,
|
|
35
|
+
isArray = false,
|
|
36
|
+
multiline = true,
|
|
37
|
+
onChange,
|
|
38
|
+
onEdit,
|
|
39
|
+
rows = 3,
|
|
40
|
+
slotProps,
|
|
41
|
+
...rest
|
|
42
|
+
} = props;
|
|
43
|
+
|
|
44
|
+
// Slot props
|
|
45
|
+
const { input, inputLabel, ...restSlotProps } = slotProps ?? {};
|
|
46
|
+
|
|
47
|
+
const localRef = React.useRef<HTMLInputElement>(null);
|
|
48
|
+
|
|
49
|
+
// Global app
|
|
50
|
+
const app = useRequiredAppContext();
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<TextField
|
|
54
|
+
fullWidth={fullWidth}
|
|
55
|
+
inputRef={useCombinedRefs(inputRef, localRef)}
|
|
56
|
+
multiline={multiline}
|
|
57
|
+
onChange={(event) => {
|
|
58
|
+
const value = event.target.value.trim();
|
|
59
|
+
let errorMessage = "";
|
|
60
|
+
|
|
61
|
+
if (value.length > 0) {
|
|
62
|
+
try {
|
|
63
|
+
const parsed = JSON.parse(value);
|
|
64
|
+
if (isArray && !Array.isArray(parsed)) {
|
|
65
|
+
errorMessage =
|
|
66
|
+
app.get("jsonDataArrayError") || "Value must be a JSON array";
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (typeof parsed !== "object") {
|
|
70
|
+
throw new Error("Parsed value is not an object");
|
|
71
|
+
}
|
|
72
|
+
} catch (e) {
|
|
73
|
+
errorMessage =
|
|
74
|
+
(app.get("jsonDataError") || "Invalid JSON text") + " - " + e;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
event.target.setCustomValidity(errorMessage);
|
|
79
|
+
event.target.reportValidity();
|
|
80
|
+
|
|
81
|
+
onChange?.(event);
|
|
82
|
+
}}
|
|
83
|
+
rows={rows}
|
|
84
|
+
slotProps={{
|
|
85
|
+
input: {
|
|
86
|
+
endAdornment: onEdit ? (
|
|
87
|
+
<InputAdornment position="end">
|
|
88
|
+
<IconButton onClick={() => onEdit?.(localRef.current!)}>
|
|
89
|
+
<EditIcon />
|
|
90
|
+
</IconButton>
|
|
91
|
+
</InputAdornment>
|
|
92
|
+
) : undefined,
|
|
93
|
+
...input
|
|
94
|
+
},
|
|
95
|
+
inputLabel: {
|
|
96
|
+
shrink: MUGlobal.inputFieldShrink,
|
|
97
|
+
...inputLabel
|
|
98
|
+
},
|
|
99
|
+
...restSlotProps
|
|
100
|
+
}}
|
|
101
|
+
{...rest}
|
|
102
|
+
/>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
import { TextFieldProps } from "@mui/material/TextField";
|
|
2
|
+
import IconButton from "@mui/material/IconButton";
|
|
3
|
+
import AddIcon from "@mui/icons-material/Add";
|
|
4
|
+
import EditIcon from "@mui/icons-material/Edit";
|
|
5
|
+
import DeleteIcon from "@mui/icons-material/Delete";
|
|
6
|
+
import DragIndicatorIcon from "@mui/icons-material/DragIndicator";
|
|
7
|
+
import React from "react";
|
|
8
|
+
import Card from "@mui/material/Card";
|
|
9
|
+
import CardContent from "@mui/material/CardContent";
|
|
10
|
+
import Grid from "@mui/material/Grid";
|
|
11
|
+
import { CustomFieldData, CustomFieldSpaceValues } from "@etsoo/appscript";
|
|
12
|
+
import Button from "@mui/material/Button";
|
|
13
|
+
import { Typography } from "@mui/material";
|
|
14
|
+
import { DataTypes, DomUtils } from "@etsoo/shared";
|
|
15
|
+
import { CustomFieldUtils } from "./CustomFieldUtils";
|
|
16
|
+
import { ComboBox } from "../ComboBox";
|
|
17
|
+
import { InputField } from "../InputField";
|
|
18
|
+
import { JsonTextField } from "../JsonTextField";
|
|
19
|
+
import { HBox } from "../FlexBox";
|
|
20
|
+
import { DnDSortableList } from "../DnDSortableList";
|
|
21
|
+
import { useRequiredAppContext } from "../app/ReactApp";
|
|
22
|
+
|
|
23
|
+
const size = { xs: 6, sm: 4, lg: 3, xl: 2 };
|
|
24
|
+
const smallSize = { xs: 3, sm: 2, xl: 1 };
|
|
25
|
+
|
|
26
|
+
const random4Digit = (): number => {
|
|
27
|
+
return Math.floor(1000 + Math.random() * 9000);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const isCamelCase = (name: string): boolean => {
|
|
31
|
+
return /^[a-z][a-zA-Z0-9]*$/.test(name);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
function InputItemUIs({ data }: { data?: CustomFieldData }) {
|
|
35
|
+
// Global app
|
|
36
|
+
const app = useRequiredAppContext();
|
|
37
|
+
|
|
38
|
+
// Labels
|
|
39
|
+
const labels = app.getLabels(
|
|
40
|
+
"gridItemProps",
|
|
41
|
+
"helperText",
|
|
42
|
+
"label",
|
|
43
|
+
"mainSlotProps",
|
|
44
|
+
"nameB",
|
|
45
|
+
"options",
|
|
46
|
+
"optionsFormat",
|
|
47
|
+
"refs",
|
|
48
|
+
"size",
|
|
49
|
+
"type"
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
const types = Object.keys(CustomFieldUtils.customFieldCreators);
|
|
53
|
+
|
|
54
|
+
const nameRef = React.useRef<HTMLInputElement>(null);
|
|
55
|
+
const optionsRef = React.useRef<HTMLInputElement>(null);
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<Grid container spacing={2} marginTop={1}>
|
|
59
|
+
<Grid size={{ xs: 12, sm: 6 }}>
|
|
60
|
+
<ComboBox
|
|
61
|
+
name="type"
|
|
62
|
+
label={labels.type}
|
|
63
|
+
inputRequired
|
|
64
|
+
size="small"
|
|
65
|
+
loadData={() =>
|
|
66
|
+
Promise.resolve(types.map((t) => ({ id: t, label: t })))
|
|
67
|
+
}
|
|
68
|
+
onValueChange={(item) => {
|
|
69
|
+
const type = item?.id;
|
|
70
|
+
optionsRef.current!.disabled =
|
|
71
|
+
type !== "combobox" && type !== "select";
|
|
72
|
+
|
|
73
|
+
const nameInput = nameRef.current!;
|
|
74
|
+
if (
|
|
75
|
+
nameInput.value === "" &&
|
|
76
|
+
(type === "amountlabel" || type === "divider" || type === "label")
|
|
77
|
+
) {
|
|
78
|
+
nameInput.value = type + random4Digit();
|
|
79
|
+
}
|
|
80
|
+
}}
|
|
81
|
+
idValue={data?.type}
|
|
82
|
+
fullWidth
|
|
83
|
+
/>
|
|
84
|
+
</Grid>
|
|
85
|
+
<Grid size={{ xs: 12, sm: 6 }}>
|
|
86
|
+
<ComboBox
|
|
87
|
+
name="space"
|
|
88
|
+
label={labels.size}
|
|
89
|
+
inputRequired
|
|
90
|
+
size="small"
|
|
91
|
+
loadData={() =>
|
|
92
|
+
Promise.resolve(
|
|
93
|
+
CustomFieldSpaceValues.map((t) => ({ id: t, label: t }))
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
idValue={data?.space}
|
|
97
|
+
fullWidth
|
|
98
|
+
/>
|
|
99
|
+
</Grid>
|
|
100
|
+
<Grid size={{ xs: 12, sm: 6 }}>
|
|
101
|
+
<InputField
|
|
102
|
+
fullWidth
|
|
103
|
+
required
|
|
104
|
+
name="name"
|
|
105
|
+
size="small"
|
|
106
|
+
inputRef={nameRef}
|
|
107
|
+
slotProps={{ htmlInput: { maxLength: 128, readOnly: !!data } }}
|
|
108
|
+
label={labels.nameB}
|
|
109
|
+
defaultValue={data?.name ?? ""}
|
|
110
|
+
/>
|
|
111
|
+
</Grid>
|
|
112
|
+
<Grid size={{ xs: 12, sm: 6 }}>
|
|
113
|
+
<InputField
|
|
114
|
+
fullWidth
|
|
115
|
+
name="label"
|
|
116
|
+
size="small"
|
|
117
|
+
slotProps={{ htmlInput: { maxLength: 128 } }}
|
|
118
|
+
label={labels.label}
|
|
119
|
+
defaultValue={data?.label ?? ""}
|
|
120
|
+
/>
|
|
121
|
+
</Grid>
|
|
122
|
+
<Grid size={{ xs: 12, sm: 12 }}>
|
|
123
|
+
<InputField
|
|
124
|
+
fullWidth
|
|
125
|
+
name="helperText"
|
|
126
|
+
size="small"
|
|
127
|
+
label={labels.helperText}
|
|
128
|
+
defaultValue={data?.helperText ?? ""}
|
|
129
|
+
/>
|
|
130
|
+
</Grid>
|
|
131
|
+
<Grid size={{ xs: 12, sm: 12 }}>
|
|
132
|
+
<InputField
|
|
133
|
+
fullWidth
|
|
134
|
+
name="options"
|
|
135
|
+
size="small"
|
|
136
|
+
multiline
|
|
137
|
+
rows={2}
|
|
138
|
+
label={labels.options}
|
|
139
|
+
inputRef={optionsRef}
|
|
140
|
+
helperText={labels.optionsFormat}
|
|
141
|
+
slotProps={{ htmlInput: { disabled: true } }}
|
|
142
|
+
defaultValue={
|
|
143
|
+
data?.options
|
|
144
|
+
? data.options
|
|
145
|
+
.map((o) => `${o.id}=${DataTypes.getListItemLabel(o)}`)
|
|
146
|
+
.join("\n")
|
|
147
|
+
: ""
|
|
148
|
+
}
|
|
149
|
+
/>
|
|
150
|
+
</Grid>
|
|
151
|
+
<Grid size={{ xs: 12, sm: 12 }}>
|
|
152
|
+
<JsonTextField
|
|
153
|
+
isArray
|
|
154
|
+
name="refs"
|
|
155
|
+
size="small"
|
|
156
|
+
multiline={false}
|
|
157
|
+
label={labels.refs + " (JSON)"}
|
|
158
|
+
defaultValue={data?.refs ? JSON.stringify(data.refs) : ""}
|
|
159
|
+
/>
|
|
160
|
+
</Grid>
|
|
161
|
+
<Grid size={{ xs: 12, sm: 12 }}>
|
|
162
|
+
<JsonTextField
|
|
163
|
+
name="gridItemProps"
|
|
164
|
+
size="small"
|
|
165
|
+
multiline={false}
|
|
166
|
+
label={labels.gridItemProps + " (JSON)"}
|
|
167
|
+
defaultValue={
|
|
168
|
+
data?.gridItemProps ? JSON.stringify(data.gridItemProps) : ""
|
|
169
|
+
}
|
|
170
|
+
/>
|
|
171
|
+
</Grid>
|
|
172
|
+
<Grid size={{ xs: 12, sm: 12 }}>
|
|
173
|
+
<JsonTextField
|
|
174
|
+
name="mainSlotProps"
|
|
175
|
+
size="small"
|
|
176
|
+
multiline={false}
|
|
177
|
+
label={labels.mainSlotProps + " (JSON)"}
|
|
178
|
+
defaultValue={
|
|
179
|
+
data?.mainSlotProps ? JSON.stringify(data.mainSlotProps) : ""
|
|
180
|
+
}
|
|
181
|
+
helperText='{"required":true}'
|
|
182
|
+
/>
|
|
183
|
+
</Grid>
|
|
184
|
+
</Grid>
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function InputUIs({
|
|
189
|
+
source,
|
|
190
|
+
onChange
|
|
191
|
+
}: {
|
|
192
|
+
source?: string;
|
|
193
|
+
onChange: (items: CustomFieldData[]) => void;
|
|
194
|
+
}) {
|
|
195
|
+
// Global app
|
|
196
|
+
const app = useRequiredAppContext();
|
|
197
|
+
|
|
198
|
+
// Labels
|
|
199
|
+
const labels = app.getLabels(
|
|
200
|
+
"add",
|
|
201
|
+
"delete",
|
|
202
|
+
"edit",
|
|
203
|
+
"sortTip",
|
|
204
|
+
"dragIndicator"
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
const [items, setItems] = React.useState<CustomFieldData[]>([]);
|
|
208
|
+
|
|
209
|
+
const doChange = (items: CustomFieldData[]) => {
|
|
210
|
+
setItems(items);
|
|
211
|
+
onChange(items);
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const editItem = (item?: CustomFieldData) => {
|
|
215
|
+
app.showInputDialog({
|
|
216
|
+
title: item ? labels.edit : labels.add,
|
|
217
|
+
message: "",
|
|
218
|
+
callback: async (form) => {
|
|
219
|
+
// Cancelled
|
|
220
|
+
if (form == null) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Validate form
|
|
225
|
+
if (!form.reportValidity()) {
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Form data
|
|
230
|
+
const {
|
|
231
|
+
typeInput: type,
|
|
232
|
+
spaceInput: space,
|
|
233
|
+
name,
|
|
234
|
+
label,
|
|
235
|
+
helperText,
|
|
236
|
+
options,
|
|
237
|
+
refs,
|
|
238
|
+
gridItemProps,
|
|
239
|
+
mainSlotProps
|
|
240
|
+
} = DomUtils.dataAs(new FormData(form), {
|
|
241
|
+
typeInput: "string",
|
|
242
|
+
spaceInput: "string",
|
|
243
|
+
name: "string",
|
|
244
|
+
label: "string",
|
|
245
|
+
helperText: "string",
|
|
246
|
+
options: "string",
|
|
247
|
+
refs: "string",
|
|
248
|
+
gridItemProps: "string",
|
|
249
|
+
mainSlotProps: "string"
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
if (!type || !space || !name) {
|
|
253
|
+
return app.get("noData");
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (!isCamelCase(name)) {
|
|
257
|
+
DomUtils.setFocus("name", form);
|
|
258
|
+
return app.get("invalidNaming") + " (camelCase)";
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (type !== "divider" && !label) {
|
|
262
|
+
DomUtils.setFocus("label", form);
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (item == null && items.some((item) => item.name === name)) {
|
|
267
|
+
return app.get("itemExists")?.format(name);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const optionsJson = options
|
|
271
|
+
? options.split("\n").map((line) => {
|
|
272
|
+
const [id, ...labelParts] = line.split("=");
|
|
273
|
+
return { id, label: labelParts.join("=") };
|
|
274
|
+
})
|
|
275
|
+
: undefined;
|
|
276
|
+
|
|
277
|
+
const refsJson = refs ? JSON.parse(refs) : undefined;
|
|
278
|
+
const gridItemPropsJson = gridItemProps
|
|
279
|
+
? JSON.parse(gridItemProps)
|
|
280
|
+
: undefined;
|
|
281
|
+
const mainSlotPropsJson = mainSlotProps
|
|
282
|
+
? JSON.parse(mainSlotProps)
|
|
283
|
+
: undefined;
|
|
284
|
+
|
|
285
|
+
if (item == null) {
|
|
286
|
+
const newItem: CustomFieldData = {
|
|
287
|
+
type,
|
|
288
|
+
name,
|
|
289
|
+
space: space as CustomFieldData["space"],
|
|
290
|
+
label,
|
|
291
|
+
helperText,
|
|
292
|
+
options: optionsJson,
|
|
293
|
+
refs: refsJson,
|
|
294
|
+
gridItemProps: gridItemPropsJson,
|
|
295
|
+
mainSlotProps: mainSlotPropsJson
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
doChange([...items, newItem]);
|
|
299
|
+
} else {
|
|
300
|
+
item.type = type;
|
|
301
|
+
item.space = space as CustomFieldData["space"];
|
|
302
|
+
item.name = name;
|
|
303
|
+
item.label = label;
|
|
304
|
+
item.helperText = helperText;
|
|
305
|
+
item.options = optionsJson;
|
|
306
|
+
item.refs = refsJson;
|
|
307
|
+
item.gridItemProps = gridItemPropsJson;
|
|
308
|
+
item.mainSlotProps = mainSlotPropsJson;
|
|
309
|
+
|
|
310
|
+
doChange([...items]);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return;
|
|
314
|
+
},
|
|
315
|
+
inputs: <InputItemUIs data={item} />
|
|
316
|
+
});
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
React.useEffect(() => {
|
|
320
|
+
try {
|
|
321
|
+
if (source) {
|
|
322
|
+
const parsed = JSON.parse(source);
|
|
323
|
+
if (Array.isArray(parsed)) {
|
|
324
|
+
setItems(parsed);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
} catch (error) {
|
|
328
|
+
console.error("Failed to parse source:", error);
|
|
329
|
+
}
|
|
330
|
+
}, [source]);
|
|
331
|
+
|
|
332
|
+
return (
|
|
333
|
+
<React.Fragment>
|
|
334
|
+
<HBox marginBottom={0.5} gap={1} alignItems="center">
|
|
335
|
+
<Typography>{labels.sortTip}</Typography>
|
|
336
|
+
<Button
|
|
337
|
+
size="small"
|
|
338
|
+
color="primary"
|
|
339
|
+
variant="outlined"
|
|
340
|
+
startIcon={<AddIcon />}
|
|
341
|
+
onClick={() => editItem()}
|
|
342
|
+
>
|
|
343
|
+
{labels.add}
|
|
344
|
+
</Button>
|
|
345
|
+
</HBox>
|
|
346
|
+
<Card>
|
|
347
|
+
<CardContent>
|
|
348
|
+
<Grid container spacing={0}>
|
|
349
|
+
<DnDSortableList<CustomFieldData>
|
|
350
|
+
items={items}
|
|
351
|
+
idField={(item) => item.name!}
|
|
352
|
+
labelField={(item) => item.label || item.name!}
|
|
353
|
+
onChange={(items) => doChange(items)}
|
|
354
|
+
itemRenderer={(
|
|
355
|
+
data,
|
|
356
|
+
style,
|
|
357
|
+
{ sortable: { index }, ref, handleRef }
|
|
358
|
+
) => (
|
|
359
|
+
<Grid
|
|
360
|
+
container
|
|
361
|
+
size={{ xs: 12, sm: 12 }}
|
|
362
|
+
ref={ref}
|
|
363
|
+
style={style}
|
|
364
|
+
gap={1}
|
|
365
|
+
>
|
|
366
|
+
<Grid size={smallSize}>
|
|
367
|
+
<IconButton
|
|
368
|
+
style={{ cursor: "move" }}
|
|
369
|
+
size="small"
|
|
370
|
+
title={labels.dragIndicator}
|
|
371
|
+
ref={handleRef}
|
|
372
|
+
>
|
|
373
|
+
<DragIndicatorIcon />
|
|
374
|
+
</IconButton>
|
|
375
|
+
</Grid>
|
|
376
|
+
<Grid size={size}>
|
|
377
|
+
{index + 1} - {data.type} / {data.space}
|
|
378
|
+
</Grid>
|
|
379
|
+
<Grid size={size}>
|
|
380
|
+
{data.name} - {data.label}
|
|
381
|
+
</Grid>
|
|
382
|
+
<Grid size={smallSize}>
|
|
383
|
+
<IconButton
|
|
384
|
+
size="small"
|
|
385
|
+
title={labels.delete}
|
|
386
|
+
onClick={() =>
|
|
387
|
+
doChange(
|
|
388
|
+
items.filter((item) => item.name !== data.name)
|
|
389
|
+
)
|
|
390
|
+
}
|
|
391
|
+
>
|
|
392
|
+
<DeleteIcon />
|
|
393
|
+
</IconButton>
|
|
394
|
+
<IconButton
|
|
395
|
+
size="small"
|
|
396
|
+
title={labels.edit}
|
|
397
|
+
onClick={() => editItem(data)}
|
|
398
|
+
>
|
|
399
|
+
<EditIcon />
|
|
400
|
+
</IconButton>
|
|
401
|
+
</Grid>
|
|
402
|
+
</Grid>
|
|
403
|
+
)}
|
|
404
|
+
></DnDSortableList>
|
|
405
|
+
</Grid>
|
|
406
|
+
</CardContent>
|
|
407
|
+
</Card>
|
|
408
|
+
</React.Fragment>
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Custom attribute area properties
|
|
414
|
+
*/
|
|
415
|
+
export type CustomAttributeAreaProps = TextFieldProps & {};
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Custom attribute area
|
|
419
|
+
* @param props Properties
|
|
420
|
+
* @returns Component
|
|
421
|
+
*/
|
|
422
|
+
export function CustomAttributeArea(props: CustomAttributeAreaProps) {
|
|
423
|
+
// Global app
|
|
424
|
+
const app = useRequiredAppContext();
|
|
425
|
+
|
|
426
|
+
// Destruct
|
|
427
|
+
const { label = app.get("attributeDefinition"), ...rest } = props;
|
|
428
|
+
|
|
429
|
+
const ref = React.useRef<CustomFieldData[]>([]);
|
|
430
|
+
|
|
431
|
+
const showUI = (input: HTMLInputElement) => {
|
|
432
|
+
app.showInputDialog({
|
|
433
|
+
title: label,
|
|
434
|
+
message: "",
|
|
435
|
+
fullScreen: true,
|
|
436
|
+
callback: (form) => {
|
|
437
|
+
if (form == null) {
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
input.value = ref.current.length > 0 ? JSON.stringify(ref.current) : "";
|
|
442
|
+
},
|
|
443
|
+
inputs: (
|
|
444
|
+
<InputUIs
|
|
445
|
+
source={input.value}
|
|
446
|
+
onChange={(items) => (ref.current = items)}
|
|
447
|
+
/>
|
|
448
|
+
)
|
|
449
|
+
});
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
// Layout
|
|
453
|
+
return <JsonTextField label={label} onEdit={showUI} isArray {...rest} />;
|
|
454
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -7,6 +7,7 @@ export * from "./app/Labels";
|
|
|
7
7
|
export * from "./app/ReactApp";
|
|
8
8
|
export * from "./app/ServiceApp";
|
|
9
9
|
|
|
10
|
+
export * from "./custom/CustomAttributeArea";
|
|
10
11
|
export * from "./custom/CustomFieldUtils";
|
|
11
12
|
export * from "./custom/CustomFieldViewer";
|
|
12
13
|
export * from "./custom/CustomFieldWindow";
|
|
@@ -79,6 +80,7 @@ export * from "./InputTipField";
|
|
|
79
80
|
export * from "./IntInputField";
|
|
80
81
|
export * from "./ItemList";
|
|
81
82
|
export * from "./JsonDataInput";
|
|
83
|
+
export * from "./JsonTextField";
|
|
82
84
|
export * from "./LineChart";
|
|
83
85
|
export * from "./LinkEx";
|
|
84
86
|
export * from "./ListChooser";
|