json-edit-react 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +674 -0
- package/README.md +334 -0
- package/build/index.cjs.js +876 -0
- package/build/index.d.ts +98 -0
- package/build/index.esm.js +873 -0
- package/package.json +46 -0
|
@@ -0,0 +1,876 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
4
|
+
var react = require('react');
|
|
5
|
+
var assign = require('object-property-assigner');
|
|
6
|
+
var extract = require('object-property-extractor');
|
|
7
|
+
var clone = require('just-clone');
|
|
8
|
+
var hi = require('react-icons/hi');
|
|
9
|
+
var bi = require('react-icons/bi');
|
|
10
|
+
var md = require('react-icons/md');
|
|
11
|
+
var fi = require('react-icons/fi');
|
|
12
|
+
var ti = require('react-icons/ti');
|
|
13
|
+
var fa = require('react-icons/fa');
|
|
14
|
+
|
|
15
|
+
var e=[],t=[];function n(n,r){if(n&&"undefined"!=typeof document){var a,s=!0===r.prepend?"prepend":"append",d=!0===r.singleTag,i="string"==typeof r.container?document.querySelector(r.container):document.getElementsByTagName("head")[0];if(d){var u=e.indexOf(i);-1===u&&(u=e.push(i)-1,t[u]={}),a=t[u]&&t[u][s]?t[u][s]:t[u][s]=c();}else a=c();65279===n.charCodeAt(0)&&(n=n.substring(1)),a.styleSheet?a.styleSheet.cssText+=n:a.appendChild(document.createTextNode(n));}function c(){var e=document.createElement("style");if(e.setAttribute("type","text/css"),r.attributes)for(var t=Object.keys(r.attributes),n=0;n<t.length;n++)e.setAttribute(t[n],r.attributes[t[n]]);var a="prepend"===s?"afterbegin":"beforeend";return i.insertAdjacentElement(a,e),e}}
|
|
16
|
+
|
|
17
|
+
var css = ":root {\n --jer-form-border: 1px solid #ededf0;\n --jer-form-border-focus: 1px solid #e2e2e2;\n --jer-expand-transition-time: 0.2s;\n --jer-highlight-color: #b3d8ff;\n}\n.jer-editor-container {\n font-size: 1.1em;\n /* font-size: 20px; */\n padding: 1em;\n margin-top: 1em;\n margin-bottom: 1em;\n border-radius: 0.5em;\n}\n\n.jer-editor-container textarea {\n /* width: 90%; */\n color: var(--jer-input-color);\n border: var(--jer-form-border);\n border-radius: 0.3em;\n outline: none;\n}\n\n.jer-editor-container textarea:focus {\n border: var(--jer-form-border-focus);\n}\n\n.jer-editor-container input {\n border: var(--jer-form-border);\n border-radius: 0.3em;\n outline: none;\n}\n\n.jer-editor-container input:focus {\n border: var(--jer-form-border-focus);\n}\n\n/* Input highlighted text */\n.jer-editor-container ::selection {\n background-color: var(--jer-highlight-color);\n}\n\n.jer-editor-container select {\n /* color: red; */\n}\n\n.jer-component {\n}\n\n.fb-collection-component {\n}\n\n.jer-collection-header-row,\n.jer-value-main-row {\n display: flex;\n min-height: 1.7em;\n gap: 0.3em;\n align-items: center;\n}\n\n.jer-collection-header-row {\n display: flex;\n flex-wrap: wrap;\n}\n\n.jer-brackets {\n}\n\n.jer-collection-item-count {\n}\n\n.jer-collection-inner {\n position: relative;\n}\n\n.jer-collection-text-edit {\n display: flex;\n flex-direction: column;\n gap: 0.3em;\n}\n\n.jer-collection-text-area {\n resize: both;\n padding-top: 0.2em;\n padding-left: 0.5em;\n padding-right: 0.5em;\n padding-bottom: 0;\n overflow: hidden;\n max-height: 40em;\n}\n\n.jer-collection-input-button-row {\n width: 90%;\n display: flex;\n justify-content: flex-end;\n font-size: 150%;\n}\n\n.jer-collection-element {\n}\n\n.jer-collection-error-row {\n position: absolute;\n bottom: 0;\n}\n\n.jer-collection-error-row-edit {\n}\n\n.jer-error-slug {\n margin-left: 1em;\n}\n\n.jer-value-component {\n position: relative;\n}\n\n.jer-value-main-row {\n display: flex;\n}\n\n.jer-value-error-row {\n position: absolute;\n}\n\n.jer-value-string {\n line-height: 1.3em;\n}\n\n.jer-input-text {\n resize: none;\n margin: 0;\n height: 1.4em;\n padding-left: 0.5em;\n padding-right: 0.5em;\n padding-bottom: 0;\n min-width: 6em;\n overflow: hidden;\n max-height: 30em;\n}\n\n/* To prevent over-writing by UI libraries */\n.jer-type-select {\n border: revert;\n border-radius: revert;\n padding: revert;\n font-family: revert;\n font-size: revert;\n color: revert;\n}\n\n.jer-value-number {\n}\n\n.jer-value-boolean {\n}\n\n.jer-value-null {\n}\n\n.jer-value-object {\n}\n\n.jer-object-key {\n word-break: break-word;\n line-height: 1.1em;\n}\n\n.jer-value-array {\n}\n\n.jer-value-invalid {\n opacity: 0.5;\n font-style: italic;\n}\n\n/* .jer-input-text, */\n.jer-input-number {\n height: 1.6em;\n}\n\n.jer-input-number {\n min-width: 3em;\n}\n\n.jer-input-component {\n}\n\n.jer-edit-buttons,\n.jer-confirm-buttons {\n display: flex;\n align-items: center;\n cursor: pointer;\n}\n\n.jer-input-buttons {\n gap: 0.4em;\n}\n\n.jer-edit-buttons {\n gap: 0.4em;\n margin-left: 0.5em;\n opacity: 0;\n}\n\n.jer-confirm-buttons {\n gap: 0.2em;\n margin-left: 0.4em;\n}\n\n.jer-edit-buttons:hover {\n opacity: 1;\n position: relative;\n}\n\n.jer-collection-header-row:hover > .jer-edit-buttons {\n opacity: 1;\n}\n\n.jer-value-main-row:hover > .jer-edit-buttons {\n opacity: 1;\n}\n\n.jer-input-buttons {\n}\n\n.jer-icon-wrapper {\n height: 1.4em;\n opacity: 1;\n transition: 0.2s;\n}\n\n.jer-icon-wrapper:active {\n position: relative;\n top: 0.07em;\n}\n\n.jer-icon-wrapper:hover {\n opacity: 0.85;\n transition: 0.2s;\n transform: scale(1.2);\n}\n\n.jer-copy-pulse {\n position: relative;\n}\n\n.jer-copy-pulse:hover {\n}\n\n.jer-copy-pulse:after {\n content: '';\n display: block;\n position: absolute;\n border-radius: 50%;\n left: 0;\n top: 0;\n width: 100%;\n height: 100%;\n opacity: 0;\n transition: all 0.5s;\n box-shadow: 0 0 15px 5px var(--jer-icon-copy-color);\n}\n\n.jer-copy-pulse:active:after {\n box-shadow: 0 0 0 0 var(--jer-icon-copy-color);\n position: absolute;\n border-radius: 4em;\n left: 0;\n top: 0;\n opacity: 1;\n transition: 0s;\n}\n\n.jer-copy-pulse:active {\n top: 0.07em;\n}\n\n.jer-rotate-90 {\n transform: rotate(-90deg);\n transition: transform var(--jer-expand-transition-time);\n}\n\n.jer-icon {\n /* font-size: '2em'; */\n}\n\n.jer-icon:hover {\n opacity: 0.85;\n transition: 0.3s;\n transform: scale(1.2);\n}\n";
|
|
18
|
+
n(css,{});
|
|
19
|
+
|
|
20
|
+
const defaultTheme$1 = {
|
|
21
|
+
displayName: 'Default',
|
|
22
|
+
fragments: { edit: 'rgb(42, 161, 152)' },
|
|
23
|
+
styles: {
|
|
24
|
+
container: {
|
|
25
|
+
backgroundColor: '#f6f6f6',
|
|
26
|
+
fontFamily: 'monospace',
|
|
27
|
+
},
|
|
28
|
+
property: '#292929',
|
|
29
|
+
bracket: { color: 'rgb(0, 43, 54)', fontWeight: 'bold' },
|
|
30
|
+
itemCount: { color: 'rgba(0, 0, 0, 0.3)', fontStyle: 'italic' },
|
|
31
|
+
string: 'rgb(203, 75, 22)',
|
|
32
|
+
number: 'rgb(38, 139, 210)',
|
|
33
|
+
boolean: 'green',
|
|
34
|
+
null: { color: 'rgb(220, 50, 47)', fontVariant: 'small-caps', fontWeight: 'bold' },
|
|
35
|
+
input: ['#292929', { fontSize: '90%' }],
|
|
36
|
+
inputHighlight: '#b3d8ff',
|
|
37
|
+
error: { fontSize: '0.8em', color: 'red', fontWeight: 'bold' },
|
|
38
|
+
iconCollection: 'rgb(0, 43, 54)',
|
|
39
|
+
iconEdit: 'edit',
|
|
40
|
+
iconDelete: 'rgb(203, 75, 22)',
|
|
41
|
+
iconAdd: 'edit',
|
|
42
|
+
iconCopy: 'rgb(38, 139, 210)',
|
|
43
|
+
iconOk: 'green',
|
|
44
|
+
iconCancel: 'rgb(203, 75, 22)',
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
const themes = {
|
|
48
|
+
default: defaultTheme$1,
|
|
49
|
+
githubDark: {
|
|
50
|
+
displayName: 'Github Dark',
|
|
51
|
+
styles: {
|
|
52
|
+
container: {
|
|
53
|
+
backgroundColor: '#0d1117',
|
|
54
|
+
color: 'white',
|
|
55
|
+
},
|
|
56
|
+
property: '#E6EDF3',
|
|
57
|
+
bracket: '#56d364',
|
|
58
|
+
itemCount: '#8B949E',
|
|
59
|
+
string: '#A5D6FF',
|
|
60
|
+
number: '#D2A8FF',
|
|
61
|
+
boolean: { color: '#FF7B72', fontSize: '90%', fontWeight: 'bold' },
|
|
62
|
+
null: 'green',
|
|
63
|
+
iconCollection: '#D2A8FF',
|
|
64
|
+
iconEdit: '#D2A8FF',
|
|
65
|
+
iconDelete: 'rgb(203, 75, 22)',
|
|
66
|
+
iconAdd: 'rgb(203, 75, 22)',
|
|
67
|
+
iconCopy: '#A5D6FF',
|
|
68
|
+
iconOk: '#56d364',
|
|
69
|
+
iconCancel: 'rgb(203, 75, 22)',
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
githubLight: {
|
|
73
|
+
displayName: 'Github Light',
|
|
74
|
+
styles: {
|
|
75
|
+
container: 'white',
|
|
76
|
+
property: '#1F2328',
|
|
77
|
+
bracket: '#00802e',
|
|
78
|
+
itemCount: '#8B949E',
|
|
79
|
+
string: '#0A3069',
|
|
80
|
+
number: '#953800',
|
|
81
|
+
boolean: { color: '#CF222E', fontSize: '90%', fontWeight: 'bold' },
|
|
82
|
+
null: '#FF7B72',
|
|
83
|
+
iconCollection: '#8250DF',
|
|
84
|
+
iconEdit: '#8250DF',
|
|
85
|
+
iconDelete: 'rgb(203, 75, 22)',
|
|
86
|
+
iconAdd: '#8250DF',
|
|
87
|
+
iconCopy: '#57606A',
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
monoDark: {
|
|
91
|
+
displayName: 'Black & White',
|
|
92
|
+
fragments: {
|
|
93
|
+
lightText: { color: 'white' },
|
|
94
|
+
midGrey: '#5c5c5c',
|
|
95
|
+
},
|
|
96
|
+
styles: {
|
|
97
|
+
container: ['lightText', { backgroundColor: 'black' }],
|
|
98
|
+
property: 'lightText',
|
|
99
|
+
bracket: 'midGrey',
|
|
100
|
+
itemCount: '#4a4a4a',
|
|
101
|
+
string: '#a8a8a8',
|
|
102
|
+
number: '#666666',
|
|
103
|
+
boolean: { color: '#848484', fontStyle: 'italic' },
|
|
104
|
+
null: '#333333',
|
|
105
|
+
iconCollection: 'midGrey',
|
|
106
|
+
iconEdit: 'midGrey',
|
|
107
|
+
iconDelete: 'midGrey',
|
|
108
|
+
iconAdd: 'midGrey',
|
|
109
|
+
iconCopy: 'midGrey',
|
|
110
|
+
iconOk: 'midGrey',
|
|
111
|
+
iconCancel: 'midGrey',
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
monoLight: {
|
|
115
|
+
fragments: { midGrey: '#a3a3a3' },
|
|
116
|
+
displayName: 'White & Black',
|
|
117
|
+
styles: {
|
|
118
|
+
container: 'white',
|
|
119
|
+
property: 'black',
|
|
120
|
+
bracket: 'midGrey',
|
|
121
|
+
itemCount: '#b5b5b5',
|
|
122
|
+
string: '#575757',
|
|
123
|
+
number: '#999999',
|
|
124
|
+
boolean: { color: '#7b7b7b', fontStyle: 'italic' },
|
|
125
|
+
null: '#cccccc',
|
|
126
|
+
iconCollection: 'midGrey',
|
|
127
|
+
iconEdit: 'midGrey',
|
|
128
|
+
iconDelete: 'midGrey',
|
|
129
|
+
iconAdd: 'midGrey',
|
|
130
|
+
iconCopy: 'midGrey',
|
|
131
|
+
iconOk: 'midGrey',
|
|
132
|
+
iconCancel: 'midGrey',
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
candyWrapper: {
|
|
136
|
+
displayName: 'Candy Wrapper',
|
|
137
|
+
fragments: {
|
|
138
|
+
minty: { backgroundColor: '#F1FAEE' },
|
|
139
|
+
pale: { color: '#A8DADC' },
|
|
140
|
+
mid: { color: '#457B9D' },
|
|
141
|
+
dark: { color: '#1D3557' },
|
|
142
|
+
pop: { color: '#E63946' },
|
|
143
|
+
darkBlue: { color: '#2B2D42' },
|
|
144
|
+
},
|
|
145
|
+
styles: {
|
|
146
|
+
container: 'minty',
|
|
147
|
+
property: 'pop',
|
|
148
|
+
bracket: 'dark',
|
|
149
|
+
itemCount: 'pale',
|
|
150
|
+
string: 'mid',
|
|
151
|
+
number: ['darkBlue', { fontSize: '85%' }],
|
|
152
|
+
boolean: ['mid', { fontStyle: 'italic', fontWeight: 'bold', fontSize: '80%' }],
|
|
153
|
+
null: ['#cccccc', { fontWeight: 'bold' }],
|
|
154
|
+
iconCollection: '#1D3557',
|
|
155
|
+
iconEdit: '#457B9D',
|
|
156
|
+
iconDelete: '#E63946',
|
|
157
|
+
iconAdd: '#2B2D42',
|
|
158
|
+
iconCopy: '#1D3557',
|
|
159
|
+
iconCancel: '#E63946',
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
psychedelic: {
|
|
163
|
+
displayName: 'Psychedelic',
|
|
164
|
+
fragments: {
|
|
165
|
+
minty: {
|
|
166
|
+
backgroundColor: '#F1FAEE',
|
|
167
|
+
},
|
|
168
|
+
pale: {
|
|
169
|
+
color: '#A8DADC',
|
|
170
|
+
},
|
|
171
|
+
mid: {
|
|
172
|
+
color: '#457B9D',
|
|
173
|
+
},
|
|
174
|
+
dark: {
|
|
175
|
+
color: '#1D3557',
|
|
176
|
+
},
|
|
177
|
+
pop: {
|
|
178
|
+
color: '#E63946',
|
|
179
|
+
},
|
|
180
|
+
fluroYellow: 'rgb(242, 228, 21)',
|
|
181
|
+
fluroGreen: 'rgb(68, 255, 62)',
|
|
182
|
+
hotPink: '#f7379a',
|
|
183
|
+
},
|
|
184
|
+
styles: {
|
|
185
|
+
container: {
|
|
186
|
+
backgroundColor: 'unset',
|
|
187
|
+
background: 'linear-gradient(90deg, hsla(333, 100%, 53%, 1) 0%, hsla(33, 94%, 57%, 1) 100%)',
|
|
188
|
+
color: 'black',
|
|
189
|
+
},
|
|
190
|
+
property: 'black',
|
|
191
|
+
bracket: 'fluroYellow',
|
|
192
|
+
itemCount: [
|
|
193
|
+
'pale',
|
|
194
|
+
{
|
|
195
|
+
opacity: 0.7,
|
|
196
|
+
},
|
|
197
|
+
],
|
|
198
|
+
string: 'white',
|
|
199
|
+
number: [
|
|
200
|
+
'#33d9ff',
|
|
201
|
+
{
|
|
202
|
+
fontSize: '90%',
|
|
203
|
+
fontWeight: 'bold',
|
|
204
|
+
},
|
|
205
|
+
],
|
|
206
|
+
boolean: [
|
|
207
|
+
'fluroGreen',
|
|
208
|
+
{
|
|
209
|
+
fontWeight: 'bold',
|
|
210
|
+
fontSize: '80%',
|
|
211
|
+
},
|
|
212
|
+
],
|
|
213
|
+
null: [
|
|
214
|
+
'black',
|
|
215
|
+
{
|
|
216
|
+
fontWeight: 'bold',
|
|
217
|
+
opacity: 0.3,
|
|
218
|
+
backgroundColor: 'rgb(255, 255, 255, 0.5)',
|
|
219
|
+
padding: '0 0.4em',
|
|
220
|
+
borderRadius: '0.4em',
|
|
221
|
+
},
|
|
222
|
+
],
|
|
223
|
+
iconCollection: 'fluroYellow',
|
|
224
|
+
iconEdit: ['black'],
|
|
225
|
+
iconDelete: [
|
|
226
|
+
'white',
|
|
227
|
+
{
|
|
228
|
+
opacity: 0.5,
|
|
229
|
+
},
|
|
230
|
+
],
|
|
231
|
+
iconAdd: [
|
|
232
|
+
'white',
|
|
233
|
+
{
|
|
234
|
+
opacity: 0.5,
|
|
235
|
+
},
|
|
236
|
+
],
|
|
237
|
+
iconCopy: 'rgb(32, 84, 242)',
|
|
238
|
+
iconOk: 'fluroGreen',
|
|
239
|
+
iconCancel: 'hotPink',
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
};
|
|
243
|
+
const emptyStyleObject = {
|
|
244
|
+
container: {},
|
|
245
|
+
property: {},
|
|
246
|
+
bracket: {},
|
|
247
|
+
itemCount: {},
|
|
248
|
+
string: {},
|
|
249
|
+
number: {},
|
|
250
|
+
boolean: {},
|
|
251
|
+
null: {},
|
|
252
|
+
input: {},
|
|
253
|
+
inputHighlight: {},
|
|
254
|
+
error: {},
|
|
255
|
+
iconCollection: {},
|
|
256
|
+
iconEdit: {},
|
|
257
|
+
iconDelete: {},
|
|
258
|
+
iconAdd: {},
|
|
259
|
+
iconCopy: {},
|
|
260
|
+
iconOk: {},
|
|
261
|
+
iconCancel: {},
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
const defaultTheme = themes.default;
|
|
265
|
+
const initialContext = {
|
|
266
|
+
styles: emptyStyleObject,
|
|
267
|
+
setTheme: (_) => { },
|
|
268
|
+
icons: {},
|
|
269
|
+
setIcons: () => { },
|
|
270
|
+
};
|
|
271
|
+
const ThemeProviderContext = react.createContext(initialContext);
|
|
272
|
+
const ThemeProvider = ({ children }) => {
|
|
273
|
+
const [styles, setStyles] = react.useState(emptyStyleObject);
|
|
274
|
+
const [icons, setIcons] = react.useState({});
|
|
275
|
+
const setTheme = (theme) => setStyles(compileStyles(theme));
|
|
276
|
+
return (jsxRuntime.jsx(ThemeProviderContext.Provider, { value: { styles, setTheme, icons, setIcons }, children: children }));
|
|
277
|
+
};
|
|
278
|
+
const useTheme = () => react.useContext(ThemeProviderContext);
|
|
279
|
+
const compileStyles = (themeInput) => {
|
|
280
|
+
if (typeof themeInput === 'string')
|
|
281
|
+
return buildStyleObject(themes[themeInput]);
|
|
282
|
+
if (Array.isArray(themeInput)) {
|
|
283
|
+
const [name, overrides] = themeInput;
|
|
284
|
+
return buildStyleObject(themes[name], isStyleObject(overrides) ? { styles: overrides } : overrides);
|
|
285
|
+
}
|
|
286
|
+
return buildStyleObject(defaultTheme, isStyleObject(themeInput) ? { styles: themeInput } : themeInput);
|
|
287
|
+
};
|
|
288
|
+
const buildStyleObject = (baseTheme, overrides = { styles: emptyStyleObject }) => {
|
|
289
|
+
const [defaultStyles, baseStyles, overrideStyles] = [defaultTheme, baseTheme, overrides].map((theme) => {
|
|
290
|
+
const { fragments, styles } = theme;
|
|
291
|
+
const compiledStyles = {};
|
|
292
|
+
Object.entries(styles).forEach(([key, value]) => {
|
|
293
|
+
const elements = Array.isArray(value) ? value : [value];
|
|
294
|
+
const cssStyles = elements.reduce((acc, curr) => {
|
|
295
|
+
if (typeof curr === 'string') {
|
|
296
|
+
const style = fragments?.[curr] ?? curr;
|
|
297
|
+
switch (typeof style) {
|
|
298
|
+
case 'string':
|
|
299
|
+
return { ...acc, [defaultStyleProperties[key]]: style };
|
|
300
|
+
default:
|
|
301
|
+
return { ...acc, ...style };
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
else
|
|
305
|
+
return { ...acc, ...curr };
|
|
306
|
+
}, {});
|
|
307
|
+
compiledStyles[key] = cssStyles;
|
|
308
|
+
});
|
|
309
|
+
return compiledStyles;
|
|
310
|
+
});
|
|
311
|
+
const finalStyles = {};
|
|
312
|
+
Object.keys(defaultTheme.styles).forEach((key) => {
|
|
313
|
+
finalStyles[key] = {
|
|
314
|
+
...defaultStyles[key],
|
|
315
|
+
...baseStyles[key],
|
|
316
|
+
...overrideStyles[key],
|
|
317
|
+
};
|
|
318
|
+
});
|
|
319
|
+
if (finalStyles?.inputHighlight?.backgroundColor)
|
|
320
|
+
document.documentElement.style.setProperty('--jer-highlight-color', finalStyles?.inputHighlight?.backgroundColor);
|
|
321
|
+
if (finalStyles?.iconCopy?.color)
|
|
322
|
+
document.documentElement.style.setProperty('--jer-icon-copy-color', finalStyles?.iconCopy?.color);
|
|
323
|
+
return finalStyles;
|
|
324
|
+
};
|
|
325
|
+
const isStyleObject = (overrideObject) => {
|
|
326
|
+
return !('styles' in overrideObject);
|
|
327
|
+
};
|
|
328
|
+
const defaultStyleProperties = {
|
|
329
|
+
container: 'backgroundColor',
|
|
330
|
+
property: 'color',
|
|
331
|
+
bracket: 'color',
|
|
332
|
+
itemCount: 'color',
|
|
333
|
+
string: 'color',
|
|
334
|
+
number: 'color',
|
|
335
|
+
boolean: 'color',
|
|
336
|
+
null: 'color',
|
|
337
|
+
input: 'color',
|
|
338
|
+
inputHighlight: 'backgroundColor',
|
|
339
|
+
error: 'color',
|
|
340
|
+
iconCollection: 'color',
|
|
341
|
+
iconEdit: 'color',
|
|
342
|
+
iconDelete: 'color',
|
|
343
|
+
iconAdd: 'color',
|
|
344
|
+
iconCopy: 'color',
|
|
345
|
+
iconOk: 'color',
|
|
346
|
+
iconCancel: 'color',
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
const AutogrowTextArea = ({ className, name, value, setValue, handleKeyPress, }) => {
|
|
350
|
+
const { styles } = useTheme();
|
|
351
|
+
const dummyValue = value.slice(-1) === '\n' ? value + '.' : value;
|
|
352
|
+
return (jsxRuntime.jsxs("div", { style: { display: 'grid' }, children: [jsxRuntime.jsx("textarea", { style: {
|
|
353
|
+
height: 'auto',
|
|
354
|
+
gridArea: '1 / 1 / 2 / 2',
|
|
355
|
+
overflowY: 'auto',
|
|
356
|
+
...styles.input,
|
|
357
|
+
}, rows: 1, className: className, name: name, value: value, onChange: (e) => setValue(e.target.value), autoFocus: true, onFocus: (e) => {
|
|
358
|
+
if (value.length < 40)
|
|
359
|
+
e.target.select();
|
|
360
|
+
}, onKeyDown: handleKeyPress }), jsxRuntime.jsx("span", { className: className, style: {
|
|
361
|
+
visibility: 'hidden',
|
|
362
|
+
height: 'auto',
|
|
363
|
+
gridArea: '1 / 1 / 2 / 2',
|
|
364
|
+
color: 'red',
|
|
365
|
+
opacity: 0.9,
|
|
366
|
+
whiteSpace: 'pre-wrap',
|
|
367
|
+
overflow: 'clip',
|
|
368
|
+
border: '1px solid transparent',
|
|
369
|
+
...styles.input,
|
|
370
|
+
}, children: dummyValue })] }));
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
const truncate = (string, length = 200) => string.length < length ? string : `${string.slice(0, length - 2).trim()}...`;
|
|
374
|
+
const StringValue = ({ value, setValue, isEditing, path, setIsEditing, handleEdit, handleCancel, stringTruncate, }) => {
|
|
375
|
+
const { styles } = useTheme();
|
|
376
|
+
const handleKeyPress = (e) => {
|
|
377
|
+
if (e.key === 'Enter' && !e.shiftKey)
|
|
378
|
+
handleEdit();
|
|
379
|
+
else if (e.key === 'Escape')
|
|
380
|
+
handleCancel();
|
|
381
|
+
};
|
|
382
|
+
const breakString = (text) => text.split('\n').map((line, index, arr) => (jsxRuntime.jsxs("span", { children: [line, index < arr.length - 1 ? jsxRuntime.jsx("br", {}) : null] }, index)));
|
|
383
|
+
return isEditing ? (jsxRuntime.jsx(AutogrowTextArea, { className: "jer-input-text", name: path.join('.'), value: value, setValue: setValue, isEditing: isEditing, handleKeyPress: handleKeyPress })) : (jsxRuntime.jsxs("div", { onDoubleClick: () => setIsEditing(true), onClick: (e) => {
|
|
384
|
+
if (e.getModifierState('Control') || e.getModifierState('Meta'))
|
|
385
|
+
setIsEditing(true);
|
|
386
|
+
}, className: "jer-value-string", style: styles.string, children: ["\"", breakString(truncate(value, stringTruncate)), "\""] }));
|
|
387
|
+
};
|
|
388
|
+
const NumberValue = ({ value, setValue, isEditing, path, setIsEditing, handleEdit, handleCancel, }) => {
|
|
389
|
+
const { styles } = useTheme();
|
|
390
|
+
const handleKeyPress = (e) => {
|
|
391
|
+
if (e.key === 'Enter')
|
|
392
|
+
handleEdit();
|
|
393
|
+
else if (e.key === 'Escape')
|
|
394
|
+
handleCancel();
|
|
395
|
+
};
|
|
396
|
+
const validateNumber = (input) => {
|
|
397
|
+
return input.replace(/[^0-9.-]/g, '');
|
|
398
|
+
};
|
|
399
|
+
return isEditing ? (jsxRuntime.jsx("input", { className: "jer-input-number", type: "text", name: path.join('.'), value: value, onChange: (e) => setValue(validateNumber(e.target.value)), autoFocus: true, onFocus: (e) => e.target.select(), onKeyDown: handleKeyPress, style: { width: `${String(value).length / 1.5 + 2}em` } })) : (jsxRuntime.jsx("span", { onDoubleClick: () => setIsEditing(true), className: "jer-value-number", style: styles.number, children: value }));
|
|
400
|
+
};
|
|
401
|
+
const BooleanValue = ({ value, setValue, isEditing, path, setIsEditing, }) => {
|
|
402
|
+
const { styles } = useTheme();
|
|
403
|
+
return isEditing ? (jsxRuntime.jsx("input", { className: "jer-input-boolean", type: "checkbox", name: path.join('.'), checked: value, onChange: () => setValue(!value) })) : (jsxRuntime.jsx("span", { onDoubleClick: () => setIsEditing(true), className: "jer-value-boolean", style: styles.boolean, children: String(value) }));
|
|
404
|
+
};
|
|
405
|
+
const NullValue = ({ value, isEditing, setIsEditing }) => {
|
|
406
|
+
const { styles } = useTheme();
|
|
407
|
+
return isEditing ? (jsxRuntime.jsx("div", { className: "jer-input-null", children: "null" })) : (jsxRuntime.jsx("div", { onDoubleClick: () => setIsEditing(true), className: "jer-value-null", style: styles.null, children: String(value) }));
|
|
408
|
+
};
|
|
409
|
+
const ObjectValue = () => {
|
|
410
|
+
return jsxRuntime.jsx("span", { className: "jer-value-object", children: '{ }' });
|
|
411
|
+
};
|
|
412
|
+
const ArrayValue = ({ value }) => {
|
|
413
|
+
return jsxRuntime.jsx("span", { className: "jer-value-array", children: `[${value === null ? '' : value}]` });
|
|
414
|
+
};
|
|
415
|
+
const InvalidValue = () => {
|
|
416
|
+
return jsxRuntime.jsx("span", { className: "jer-value-invalid", children: "Invalid value" });
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
const Icon = ({ name, rotate }) => {
|
|
420
|
+
const { styles, icons } = useTheme();
|
|
421
|
+
const commonProps = { size: '1.4em', className: 'jer-icon' };
|
|
422
|
+
switch (name) {
|
|
423
|
+
case 'add':
|
|
424
|
+
return icons?.add ?? jsxRuntime.jsx(bi.BiPlusCircle, { ...commonProps, style: styles.iconAdd });
|
|
425
|
+
case 'edit':
|
|
426
|
+
return icons?.edit ?? jsxRuntime.jsx(bi.BiEdit, { ...commonProps, style: styles.iconEdit });
|
|
427
|
+
case 'delete':
|
|
428
|
+
return (icons?.delete ?? jsxRuntime.jsx(md.MdDeleteForever, { ...commonProps, style: styles.iconDelete, size: "1.5em" }));
|
|
429
|
+
case 'copy':
|
|
430
|
+
return icons?.copy ?? jsxRuntime.jsx(hi.HiOutlineClipboardCopy, { ...commonProps, style: styles.iconCopy });
|
|
431
|
+
case 'ok':
|
|
432
|
+
return (icons?.ok ?? (jsxRuntime.jsx(fi.FiCheckCircle, { ...commonProps, style: { fontSize: '90%', ...styles.iconOk } })));
|
|
433
|
+
case 'cancel':
|
|
434
|
+
return (icons?.cancel ?? (jsxRuntime.jsx(ti.TiCancel, { ...commonProps, style: { fontSize: '130%', ...styles.iconCancel } })));
|
|
435
|
+
case 'chevron':
|
|
436
|
+
return (icons?.chevron ?? (jsxRuntime.jsx(fa.FaChevronDown, { className: `${rotate ? ' jer-rotate-90' : ''}`, style: styles.iconCollection })));
|
|
437
|
+
default:
|
|
438
|
+
return jsxRuntime.jsx(jsxRuntime.Fragment, {});
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
const EditButtons = ({ startEdit, handleDelete, handleAdd, enableClipboard, type, data, path, name, translate, }) => {
|
|
443
|
+
const { styles } = useTheme();
|
|
444
|
+
const NEW_KEY_PROMPT = translate('KEY_NEW');
|
|
445
|
+
const [isAdding, setIsAdding] = react.useState(false);
|
|
446
|
+
const [newKey, setNewKey] = react.useState(NEW_KEY_PROMPT);
|
|
447
|
+
react.useEffect(() => {
|
|
448
|
+
if (!isAdding)
|
|
449
|
+
setNewKey(NEW_KEY_PROMPT);
|
|
450
|
+
}, [isAdding]);
|
|
451
|
+
const handleKeyPress = (e) => {
|
|
452
|
+
if (e.key === 'Enter' && handleAdd) {
|
|
453
|
+
setIsAdding(false);
|
|
454
|
+
handleAdd(newKey);
|
|
455
|
+
}
|
|
456
|
+
else if (e.key === 'Escape')
|
|
457
|
+
setIsAdding(false);
|
|
458
|
+
};
|
|
459
|
+
const handleCopy = (e) => {
|
|
460
|
+
let type = 'value';
|
|
461
|
+
let value;
|
|
462
|
+
if (enableClipboard) {
|
|
463
|
+
switch (e.ctrlKey || e.metaKey) {
|
|
464
|
+
case true:
|
|
465
|
+
value = stringifyPath(path);
|
|
466
|
+
type = 'path';
|
|
467
|
+
break;
|
|
468
|
+
case false:
|
|
469
|
+
value = JSON.stringify(data, null, 2);
|
|
470
|
+
}
|
|
471
|
+
navigator.clipboard.writeText(value);
|
|
472
|
+
}
|
|
473
|
+
if (typeof enableClipboard === 'function')
|
|
474
|
+
enableClipboard({ value, path, key: name, type });
|
|
475
|
+
};
|
|
476
|
+
return (jsxRuntime.jsxs("div", { className: "jer-edit-buttons", style: isAdding ? { opacity: 1 } : undefined, children: [enableClipboard && (jsxRuntime.jsx("div", { onClick: handleCopy, className: "jer-copy-pulse", children: jsxRuntime.jsx(Icon, { name: "copy" }) })), startEdit && (jsxRuntime.jsx("div", { onClick: startEdit, children: jsxRuntime.jsx(Icon, { name: "edit" }) })), handleDelete && (jsxRuntime.jsx("div", { onClick: handleDelete, children: jsxRuntime.jsx(Icon, { name: "delete" }) })), handleAdd && (jsxRuntime.jsx("div", { onClick: () => {
|
|
477
|
+
if (type === 'object')
|
|
478
|
+
setIsAdding(true);
|
|
479
|
+
else
|
|
480
|
+
handleAdd('');
|
|
481
|
+
}, children: jsxRuntime.jsx(Icon, { name: "add" }) })), isAdding && handleAdd && type === 'object' && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("input", { className: "jer-input-new-key", type: "text", name: "new-object-key", value: newKey, onChange: (e) => setNewKey(e.target.value), autoFocus: true, onFocus: (e) => e.target.select(), onKeyDown: handleKeyPress, style: { ...styles.input } }), jsxRuntime.jsx(InputButtons, { onOk: () => {
|
|
482
|
+
if (!!newKey) {
|
|
483
|
+
setIsAdding(false);
|
|
484
|
+
handleAdd(newKey);
|
|
485
|
+
}
|
|
486
|
+
}, onCancel: () => {
|
|
487
|
+
setIsAdding(false);
|
|
488
|
+
} })] }))] }));
|
|
489
|
+
};
|
|
490
|
+
const InputButtons = ({ onOk, onCancel }) => {
|
|
491
|
+
return (jsxRuntime.jsxs("div", { className: "jer-confirm-buttons", children: [jsxRuntime.jsx("div", { onClick: onOk, children: jsxRuntime.jsx(Icon, { name: "ok" }) }), jsxRuntime.jsx("div", { onClick: onCancel, children: jsxRuntime.jsx(Icon, { name: "cancel" }) })] }));
|
|
492
|
+
};
|
|
493
|
+
const stringifyPath = (path) => path.reduce((str, part) => {
|
|
494
|
+
if (typeof part === 'number')
|
|
495
|
+
return `${str}[${part}]`;
|
|
496
|
+
else
|
|
497
|
+
return str === '' ? part : `${str}.${part}`;
|
|
498
|
+
}, '');
|
|
499
|
+
|
|
500
|
+
const ERROR_DISPLAY_TIME = 2500;
|
|
501
|
+
const ValueDataTypes = ['string', 'number', 'boolean', 'null'];
|
|
502
|
+
const CollectionDataTypes = ['object', 'array'];
|
|
503
|
+
const DataTypes = [...ValueDataTypes, ...CollectionDataTypes];
|
|
504
|
+
|
|
505
|
+
const ValueNodeWrapper = ({ data, name, path, onEdit, onDelete, enableClipboard, restrictEditFilter, restrictDeleteFilter, showArrayIndices, stringTruncate, indent, translate, }) => {
|
|
506
|
+
const { styles } = useTheme();
|
|
507
|
+
const [isEditing, setIsEditing] = react.useState(false);
|
|
508
|
+
const [value, setValue] = react.useState(data);
|
|
509
|
+
const [error, setError] = react.useState(null);
|
|
510
|
+
const [dataType, setDataType] = react.useState(getDataType(data));
|
|
511
|
+
react.useEffect(() => {
|
|
512
|
+
setValue(data);
|
|
513
|
+
setDataType(getDataType(data));
|
|
514
|
+
}, [data, error]);
|
|
515
|
+
const handleChangeDataType = (type) => {
|
|
516
|
+
setValue(convertValue(value, type));
|
|
517
|
+
setDataType(type);
|
|
518
|
+
};
|
|
519
|
+
const logError = (errorString) => {
|
|
520
|
+
setError(errorString);
|
|
521
|
+
setTimeout(() => setError(null), ERROR_DISPLAY_TIME);
|
|
522
|
+
console.log('Error', errorString);
|
|
523
|
+
};
|
|
524
|
+
const handleEdit = () => {
|
|
525
|
+
setIsEditing(false);
|
|
526
|
+
let newValue;
|
|
527
|
+
switch (dataType) {
|
|
528
|
+
case 'object':
|
|
529
|
+
newValue = {};
|
|
530
|
+
break;
|
|
531
|
+
case 'array':
|
|
532
|
+
newValue = value !== null ? [value] : [];
|
|
533
|
+
break;
|
|
534
|
+
case 'number':
|
|
535
|
+
const n = Number(value);
|
|
536
|
+
if (isNaN(n))
|
|
537
|
+
newValue = 0;
|
|
538
|
+
else
|
|
539
|
+
newValue = n;
|
|
540
|
+
break;
|
|
541
|
+
default:
|
|
542
|
+
newValue = value;
|
|
543
|
+
}
|
|
544
|
+
onEdit(newValue, path).then((error) => {
|
|
545
|
+
if (error)
|
|
546
|
+
logError(error);
|
|
547
|
+
});
|
|
548
|
+
};
|
|
549
|
+
const handleCancel = () => {
|
|
550
|
+
setIsEditing(false);
|
|
551
|
+
setValue(data);
|
|
552
|
+
};
|
|
553
|
+
const handleDelete = () => {
|
|
554
|
+
onDelete(value, path).then((error) => {
|
|
555
|
+
if (error)
|
|
556
|
+
logError(error);
|
|
557
|
+
});
|
|
558
|
+
};
|
|
559
|
+
const filterProps = { key: name, path, level: path.length, value: data, size: null };
|
|
560
|
+
const canEdit = react.useMemo(() => !restrictEditFilter(filterProps), [filterProps]);
|
|
561
|
+
const canDelete = react.useMemo(() => !restrictDeleteFilter(filterProps), [filterProps]);
|
|
562
|
+
const inputProps = {
|
|
563
|
+
value,
|
|
564
|
+
setValue,
|
|
565
|
+
isEditing,
|
|
566
|
+
setIsEditing,
|
|
567
|
+
handleEdit,
|
|
568
|
+
handleCancel,
|
|
569
|
+
path,
|
|
570
|
+
stringTruncate,
|
|
571
|
+
};
|
|
572
|
+
return (jsxRuntime.jsx("div", { className: "jer-component jer-value-component", style: { marginLeft: `${indent / 2}em` }, children: jsxRuntime.jsxs("div", { className: "jer-value-main-row", children: [showArrayIndices && (jsxRuntime.jsxs("label", { htmlFor: path.join('.'), className: "jer-object-key", style: {
|
|
573
|
+
...styles.property,
|
|
574
|
+
minWidth: `${Math.min(String(name).length + 1, 5)}ch`,
|
|
575
|
+
}, children: [name, ":", ' '] })), jsxRuntime.jsx("div", { className: "jer-input-component", children: getInputComponent(dataType, inputProps) }), isEditing ? (jsxRuntime.jsx(InputButtons, { onOk: handleEdit, onCancel: handleCancel })) : (dataType !== 'invalid' &&
|
|
576
|
+
!error && (jsxRuntime.jsx(EditButtons, { startEdit: canEdit ? () => setIsEditing(true) : undefined, handleDelete: canDelete ? handleDelete : undefined, data: data, enableClipboard: enableClipboard, name: name, path: path, translate: translate }))), isEditing && (jsxRuntime.jsx("select", { name: `${name}-type-select`, className: "jer-type-select", onChange: (e) => handleChangeDataType(e.target.value), value: dataType, children: DataTypes.map((type) => (jsxRuntime.jsx("option", { value: type, children: type }, type))) })), !isEditing && error && (jsxRuntime.jsx("span", { className: "jer-error-slug", style: styles.error, children: error }))] }) }));
|
|
577
|
+
};
|
|
578
|
+
const getDataType = (value) => {
|
|
579
|
+
if (typeof value === 'string')
|
|
580
|
+
return 'string';
|
|
581
|
+
if (typeof value === 'number')
|
|
582
|
+
return 'number';
|
|
583
|
+
if (typeof value === 'boolean')
|
|
584
|
+
return 'boolean';
|
|
585
|
+
if (value === null)
|
|
586
|
+
return 'null';
|
|
587
|
+
return 'invalid';
|
|
588
|
+
};
|
|
589
|
+
const getInputComponent = (dataType, inputProps) => {
|
|
590
|
+
const value = inputProps.value;
|
|
591
|
+
switch (dataType) {
|
|
592
|
+
case 'string':
|
|
593
|
+
return jsxRuntime.jsx(StringValue, { ...inputProps, value: value });
|
|
594
|
+
case 'number':
|
|
595
|
+
return jsxRuntime.jsx(NumberValue, { ...inputProps, value: value });
|
|
596
|
+
case 'boolean':
|
|
597
|
+
return jsxRuntime.jsx(BooleanValue, { ...inputProps, value: value });
|
|
598
|
+
case 'null':
|
|
599
|
+
return jsxRuntime.jsx(NullValue, { ...inputProps });
|
|
600
|
+
case 'object':
|
|
601
|
+
return jsxRuntime.jsx(ObjectValue, { ...inputProps });
|
|
602
|
+
case 'array':
|
|
603
|
+
return jsxRuntime.jsx(ArrayValue, { ...inputProps });
|
|
604
|
+
default:
|
|
605
|
+
return jsxRuntime.jsx(InvalidValue, { ...inputProps });
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
const convertValue = (value, type) => {
|
|
609
|
+
switch (type) {
|
|
610
|
+
case 'string':
|
|
611
|
+
return String(value);
|
|
612
|
+
case 'number':
|
|
613
|
+
const n = Number(value);
|
|
614
|
+
return isNaN(n) ? 0 : n;
|
|
615
|
+
case 'boolean':
|
|
616
|
+
return !!value;
|
|
617
|
+
case 'null':
|
|
618
|
+
return null;
|
|
619
|
+
case 'object':
|
|
620
|
+
return {};
|
|
621
|
+
case 'array':
|
|
622
|
+
return [value];
|
|
623
|
+
default:
|
|
624
|
+
return String(value);
|
|
625
|
+
}
|
|
626
|
+
};
|
|
627
|
+
|
|
628
|
+
const isCollection = (value) => value !== null && typeof value == 'object';
|
|
629
|
+
const CollectionNode = ({ data, path, name, ...props }) => {
|
|
630
|
+
const { styles } = useTheme();
|
|
631
|
+
const { onEdit, onAdd, onDelete, restrictEditFilter, restrictDeleteFilter, restrictAddFilter, collapseFilter, enableClipboard, indent, keySort, showArrayIndices, defaultValue, translate, } = props;
|
|
632
|
+
const [isEditing, setIsEditing] = react.useState(false);
|
|
633
|
+
const [stringifiedValue, setStringifiedValue] = react.useState(JSON.stringify(data, null, 2));
|
|
634
|
+
const [error, setError] = react.useState(null);
|
|
635
|
+
const collectionSize = Object.keys(data).length;
|
|
636
|
+
const filterProps = { key: name, path, level: path.length, value: data, size: collectionSize };
|
|
637
|
+
const [collapsed, setCollapsed] = react.useState(collapseFilter(filterProps));
|
|
638
|
+
react.useEffect(() => {
|
|
639
|
+
setStringifiedValue(JSON.stringify(data, null, 2));
|
|
640
|
+
}, [data]);
|
|
641
|
+
react.useEffect(() => {
|
|
642
|
+
setCollapsed(collapseFilter(filterProps));
|
|
643
|
+
}, [collapseFilter]);
|
|
644
|
+
const collectionType = Array.isArray(data) ? 'array' : 'object';
|
|
645
|
+
const brackets = collectionType === 'array' ? { open: '[', close: ']' } : { open: '{', close: '}' };
|
|
646
|
+
const transitionTime = getComputedStyle(document.documentElement).getPropertyValue('--jer-expand-transition-time');
|
|
647
|
+
const handleKeyPress = (e) => {
|
|
648
|
+
if (e.key === 'Enter' && (e.metaKey || e.shiftKey || e.ctrlKey))
|
|
649
|
+
handleEdit();
|
|
650
|
+
else if (e.key === 'Escape')
|
|
651
|
+
handleCancel();
|
|
652
|
+
};
|
|
653
|
+
const showError = (errorString) => {
|
|
654
|
+
setError(errorString);
|
|
655
|
+
setTimeout(() => setError(null), ERROR_DISPLAY_TIME);
|
|
656
|
+
console.log('Error', errorString);
|
|
657
|
+
};
|
|
658
|
+
const handleEdit = () => {
|
|
659
|
+
try {
|
|
660
|
+
const value = JSON.parse(stringifiedValue);
|
|
661
|
+
setIsEditing(false);
|
|
662
|
+
setError(null);
|
|
663
|
+
onEdit(value, path).then((error) => {
|
|
664
|
+
if (error)
|
|
665
|
+
showError(error);
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
catch {
|
|
669
|
+
setError(translate('ERROR_INVALID_JSON'));
|
|
670
|
+
setTimeout(() => setError(null), ERROR_DISPLAY_TIME);
|
|
671
|
+
console.log('Invalid JSON');
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
};
|
|
675
|
+
const handleAdd = (key) => {
|
|
676
|
+
setCollapsed(false);
|
|
677
|
+
if (collectionType === 'array') {
|
|
678
|
+
onAdd(defaultValue, [...path, data.length]).then((error) => {
|
|
679
|
+
if (error)
|
|
680
|
+
showError(error);
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
else if (key in data) {
|
|
684
|
+
showError(translate('ERROR_KEY_EXISTS'));
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
else
|
|
688
|
+
onAdd(defaultValue, [...path, key]).then((error) => {
|
|
689
|
+
if (error)
|
|
690
|
+
showError(error);
|
|
691
|
+
});
|
|
692
|
+
};
|
|
693
|
+
const handleDelete = path.length > 0
|
|
694
|
+
? () => {
|
|
695
|
+
onDelete(data, path).then((result) => {
|
|
696
|
+
if (result)
|
|
697
|
+
showError(result);
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
: undefined;
|
|
701
|
+
const handleCancel = () => {
|
|
702
|
+
setIsEditing(false);
|
|
703
|
+
setError(null);
|
|
704
|
+
setStringifiedValue(JSON.stringify(data, null, 2));
|
|
705
|
+
};
|
|
706
|
+
const canEdit = react.useMemo(() => !restrictEditFilter(filterProps), [filterProps]);
|
|
707
|
+
const canDelete = react.useMemo(() => !restrictDeleteFilter(filterProps), [filterProps]);
|
|
708
|
+
const canAdd = react.useMemo(() => !restrictAddFilter(filterProps), [filterProps]);
|
|
709
|
+
const showLabel = showArrayIndices || !(typeof path.slice(-1)[0] === 'number');
|
|
710
|
+
const keyValueArray = Object.entries(data).map(([key, value]) => [
|
|
711
|
+
collectionType === 'array' ? Number(key) : key,
|
|
712
|
+
value,
|
|
713
|
+
]);
|
|
714
|
+
if (keySort && collectionType === 'object')
|
|
715
|
+
keyValueArray.sort(typeof keySort === 'function' ? (a, b) => keySort(a[0], b[0]) : undefined);
|
|
716
|
+
const numOfLines = JSON.stringify(data, null, 2).split('\n').length;
|
|
717
|
+
return (jsxRuntime.jsxs("div", { className: "jer-component fb-collection-component", style: { marginLeft: `${path.length === 0 ? 0 : indent / 2}em` }, children: [jsxRuntime.jsxs("div", { className: "jer-collection-header-row", children: [jsxRuntime.jsx("div", { onClick: () => {
|
|
718
|
+
if (!isEditing)
|
|
719
|
+
setCollapsed(!collapsed);
|
|
720
|
+
}, children: jsxRuntime.jsx(Icon, { name: "chevron", rotate: collapsed }) }), jsxRuntime.jsxs("div", { className: "jer-collection-name", children: [jsxRuntime.jsx("span", { style: styles.property, children: showLabel ? `${name}:` : null }), jsxRuntime.jsx("span", { className: "jer-brackets", style: styles.bracket, children: brackets.open })] }), jsxRuntime.jsx("div", { className: "jer-collection-item-count", style: styles.itemCount, children: collectionSize === 1
|
|
721
|
+
? translate('ITEM_SINGLE', 1)
|
|
722
|
+
: translate('ITEMS_MULTIPLE', collectionSize) }), collapsed && (jsxRuntime.jsx("div", { className: "jer-brackets", style: styles.bracket, children: brackets.close })), !isEditing && (jsxRuntime.jsx(EditButtons, { startEdit: canEdit
|
|
723
|
+
? () => {
|
|
724
|
+
setIsEditing(true);
|
|
725
|
+
setCollapsed(false);
|
|
726
|
+
}
|
|
727
|
+
: undefined, handleAdd: canAdd ? handleAdd : undefined, handleDelete: canDelete ? handleDelete : undefined, enableClipboard: enableClipboard, type: collectionType, data: data, name: name, path: path, translate: translate }))] }), jsxRuntime.jsxs("div", { className: 'jer-collection-inner', style: {
|
|
728
|
+
maxHeight: collapsed ? 0 : `${numOfLines * 1.6}em`,
|
|
729
|
+
overflowY: collapsed ? 'hidden' : 'visible',
|
|
730
|
+
transition: `max-height ${transitionTime}`,
|
|
731
|
+
}, children: [isEditing ? (jsxRuntime.jsxs("div", { className: "jer-collection-text-edit", children: [jsxRuntime.jsx(AutogrowTextArea, { className: "jer-collection-text-area", name: path.join('.'), value: stringifiedValue, setValue: setStringifiedValue, isEditing: isEditing, handleKeyPress: handleKeyPress }), jsxRuntime.jsx("div", { className: "jer-collection-input-button-row", children: jsxRuntime.jsx(InputButtons, { onOk: handleEdit, onCancel: handleCancel, isCollection: true }) })] })) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: keyValueArray.map(([key, value]) => (jsxRuntime.jsx("div", { className: "jer-collection-element", children: isCollection(value) ? (jsxRuntime.jsx(CollectionNode, { data: value, path: [...path, key], name: key, ...props }, key)) : (jsxRuntime.jsx(ValueNodeWrapper, { data: value, path: [...path, key], name: key, ...props, showArrayIndices: collectionType === 'object' ? true : showArrayIndices }, key)) }, key))) })), jsxRuntime.jsx("div", { className: isEditing ? 'jer-collection-error-row' : 'jer-collection-error-row-edit', children: error && (jsxRuntime.jsx("span", { className: "jer-error-slug", style: styles.error, children: error })) })] }), !collapsed && (jsxRuntime.jsx("div", { className: "jer-brackets", style: styles.bracket, children: brackets.close }))] }));
|
|
732
|
+
};
|
|
733
|
+
|
|
734
|
+
const localisedStrings = {
|
|
735
|
+
ITEM_SINGLE: '{{count}} item',
|
|
736
|
+
ITEMS_MULTIPLE: '{{count}} items',
|
|
737
|
+
KEY_NEW: 'Enter new key',
|
|
738
|
+
ERROR_KEY_EXISTS: 'Key already exists',
|
|
739
|
+
ERROR_INVALID_JSON: 'Invalid JSON',
|
|
740
|
+
ERROR_UPDATE: 'Update unsuccessful',
|
|
741
|
+
ERROR_DELETE: 'Delete unsuccessful',
|
|
742
|
+
ERROR_ADD: 'Adding node unsuccessful',
|
|
743
|
+
};
|
|
744
|
+
const translate = (translations, key, count) => {
|
|
745
|
+
const string = key in translations ? translations[key] : localisedStrings[key];
|
|
746
|
+
return count === undefined ? string : string?.replace('{{count}}', String(count));
|
|
747
|
+
};
|
|
748
|
+
const getTranslateFunction = (translations) => (key, count) => translate(translations, key, count);
|
|
749
|
+
|
|
750
|
+
const Editor = ({ data: srcData, rootName = 'root', onUpdate, onEdit: srcEdit = onUpdate, onDelete: srcDelete = onUpdate, onAdd: srcAdd = onUpdate, enableClipboard = true, theme = 'default', icons, indent = 4, collapse = false, restrictEdit = false, restrictDelete = false, restrictAdd = false, keySort = false, showArrayIndices = true, defaultValue = null, minWidth = 250, maxWidth = 600, stringTruncate = 250, translations = {}, className, }) => {
|
|
751
|
+
const [data, setData] = react.useState(srcData);
|
|
752
|
+
const { styles, setTheme, setIcons } = useTheme();
|
|
753
|
+
const collapseFilter = react.useCallback(getFilterFunction(collapse), [collapse]);
|
|
754
|
+
const translate = react.useCallback(getTranslateFunction(translations), [translations]);
|
|
755
|
+
react.useEffect(() => {
|
|
756
|
+
setData(srcData);
|
|
757
|
+
}, [srcData]);
|
|
758
|
+
react.useEffect(() => {
|
|
759
|
+
if (theme)
|
|
760
|
+
setTheme(theme);
|
|
761
|
+
if (icons)
|
|
762
|
+
setIcons(icons);
|
|
763
|
+
}, [theme, icons]);
|
|
764
|
+
const onEdit = async (value, path) => {
|
|
765
|
+
const { currentData, newData, currentValue, newValue } = updateDataObject(data, path, value, 'update');
|
|
766
|
+
if (srcEdit) {
|
|
767
|
+
const result = await srcEdit({
|
|
768
|
+
currentData,
|
|
769
|
+
newData,
|
|
770
|
+
currentValue,
|
|
771
|
+
newValue,
|
|
772
|
+
name: path.slice(-1)[0],
|
|
773
|
+
path,
|
|
774
|
+
});
|
|
775
|
+
if (result === undefined)
|
|
776
|
+
setData(newData);
|
|
777
|
+
if (result === false)
|
|
778
|
+
return translate('ERROR_UPDATE');
|
|
779
|
+
return result;
|
|
780
|
+
}
|
|
781
|
+
else
|
|
782
|
+
setData(newData);
|
|
783
|
+
};
|
|
784
|
+
const onDelete = async (value, path) => {
|
|
785
|
+
const { currentData, newData, currentValue, newValue } = updateDataObject(data, path, value, 'delete');
|
|
786
|
+
if (srcDelete) {
|
|
787
|
+
const result = await srcDelete({
|
|
788
|
+
currentData,
|
|
789
|
+
newData,
|
|
790
|
+
currentValue,
|
|
791
|
+
newValue,
|
|
792
|
+
name: path.slice(-1)[0],
|
|
793
|
+
path,
|
|
794
|
+
});
|
|
795
|
+
if (result === undefined)
|
|
796
|
+
setData(newData);
|
|
797
|
+
if (result === false)
|
|
798
|
+
return translate('ERROR_DELETE');
|
|
799
|
+
return result;
|
|
800
|
+
}
|
|
801
|
+
else
|
|
802
|
+
setData(newData);
|
|
803
|
+
};
|
|
804
|
+
const onAdd = async (value, path) => {
|
|
805
|
+
const { currentData, newData, currentValue, newValue } = updateDataObject(data, path, value, 'add');
|
|
806
|
+
if (srcAdd) {
|
|
807
|
+
const result = await srcAdd({
|
|
808
|
+
currentData,
|
|
809
|
+
newData,
|
|
810
|
+
currentValue,
|
|
811
|
+
newValue,
|
|
812
|
+
name: path.slice(-1)[0],
|
|
813
|
+
path,
|
|
814
|
+
});
|
|
815
|
+
if (result === undefined)
|
|
816
|
+
setData(newData);
|
|
817
|
+
if (result === false)
|
|
818
|
+
return translate('ERROR_ADD');
|
|
819
|
+
return result;
|
|
820
|
+
}
|
|
821
|
+
else
|
|
822
|
+
setData(newData);
|
|
823
|
+
};
|
|
824
|
+
const restrictEditFilter = getFilterFunction(restrictEdit);
|
|
825
|
+
const restrictDeleteFilter = getFilterFunction(restrictDelete);
|
|
826
|
+
const restrictAddFilter = getFilterFunction(restrictAdd);
|
|
827
|
+
const otherProps = {
|
|
828
|
+
name: rootName,
|
|
829
|
+
onEdit,
|
|
830
|
+
onDelete,
|
|
831
|
+
onAdd,
|
|
832
|
+
collapseFilter,
|
|
833
|
+
restrictEditFilter,
|
|
834
|
+
restrictDeleteFilter,
|
|
835
|
+
restrictAddFilter,
|
|
836
|
+
enableClipboard,
|
|
837
|
+
keySort,
|
|
838
|
+
showArrayIndices,
|
|
839
|
+
indent,
|
|
840
|
+
defaultValue,
|
|
841
|
+
stringTruncate,
|
|
842
|
+
translate,
|
|
843
|
+
};
|
|
844
|
+
if (!styles)
|
|
845
|
+
return null;
|
|
846
|
+
return (jsxRuntime.jsx("div", { className: 'jer-editor-container ' + className, style: { ...styles.container, minWidth, maxWidth }, children: isCollection(data) && jsxRuntime.jsx(CollectionNode, { data: data, path: [], ...otherProps }) }));
|
|
847
|
+
};
|
|
848
|
+
const JsonEditor = (props) => (jsxRuntime.jsx(ThemeProvider, { children: jsxRuntime.jsx(Editor, { ...props }) }));
|
|
849
|
+
const updateDataObject = (data, path, newValue, action) => {
|
|
850
|
+
if (path.length === 0) {
|
|
851
|
+
return {
|
|
852
|
+
currentData: data,
|
|
853
|
+
newData: newValue,
|
|
854
|
+
currentValue: data,
|
|
855
|
+
newValue: newValue,
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
const currentValue = action !== 'add' ? extract(data, path) : undefined;
|
|
859
|
+
const newData = assign(clone(data), path, newValue, { remove: action === 'delete' });
|
|
860
|
+
return {
|
|
861
|
+
currentData: data,
|
|
862
|
+
newData,
|
|
863
|
+
currentValue,
|
|
864
|
+
newValue: action === 'update' ? newValue : undefined,
|
|
865
|
+
};
|
|
866
|
+
};
|
|
867
|
+
const getFilterFunction = (propValue) => {
|
|
868
|
+
if (typeof propValue === 'boolean')
|
|
869
|
+
return () => propValue;
|
|
870
|
+
if (typeof propValue === 'number')
|
|
871
|
+
return ({ level }) => level >= propValue;
|
|
872
|
+
return propValue;
|
|
873
|
+
};
|
|
874
|
+
|
|
875
|
+
exports.JsonEditor = JsonEditor;
|
|
876
|
+
exports.themes = themes;
|