axiodb 22.2.2 → 22.4.2

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.
Files changed (74) hide show
  1. package/electron/electron-builder.json +58 -0
  2. package/electron/main/main.cts +585 -0
  3. package/electron/main/preload.cts +51 -0
  4. package/electron/package-lock.json +8123 -0
  5. package/electron/package.json +52 -0
  6. package/electron/public/AXioDB.png +0 -0
  7. package/electron/resources/after-install.sh +45 -0
  8. package/electron/resources/after-remove.sh +29 -0
  9. package/electron/resources/icon.png +0 -0
  10. package/electron/resources/icons/128x128.png +0 -0
  11. package/electron/resources/icons/16x16.png +0 -0
  12. package/electron/resources/icons/24x24.png +0 -0
  13. package/electron/resources/icons/256x256.png +0 -0
  14. package/electron/resources/icons/32x32.png +0 -0
  15. package/electron/resources/icons/48x48.png +0 -0
  16. package/electron/resources/icons/512x512.png +0 -0
  17. package/electron/resources/icons/64x64.png +0 -0
  18. package/electron/src/App.jsx +128 -0
  19. package/electron/src/api/authApi.js +83 -0
  20. package/electron/src/api/client.js +102 -0
  21. package/electron/src/assets/AXioDB.png +0 -0
  22. package/electron/src/components/auth/CreateRoleModal.jsx +189 -0
  23. package/electron/src/components/auth/CreateUserModal.jsx +131 -0
  24. package/electron/src/components/auth/ForcePasswordChangeModal.jsx +132 -0
  25. package/electron/src/components/auth/ProtectedRoute.jsx +42 -0
  26. package/electron/src/components/auth/ResetPasswordModal.jsx +90 -0
  27. package/electron/src/components/auth/UserAvatarMenu.jsx +103 -0
  28. package/electron/src/components/collection/CreateCollectionModal.jsx +116 -0
  29. package/electron/src/components/collection/DeleteCollectionModal.jsx +85 -0
  30. package/electron/src/components/collection/SchemaViewModal.jsx +369 -0
  31. package/electron/src/components/dashboard/CollectionsChart.jsx +94 -0
  32. package/electron/src/components/dashboard/DatabaseTreeView.jsx +130 -0
  33. package/electron/src/components/dashboard/InMemoryCacheCard.jsx +60 -0
  34. package/electron/src/components/dashboard/StorageDonut.jsx +76 -0
  35. package/electron/src/components/dashboard/StorageUsageCard.jsx +59 -0
  36. package/electron/src/components/dashboard/TotalCollectionsCard.jsx +47 -0
  37. package/electron/src/components/dashboard/TotalDatabasesCard.jsx +43 -0
  38. package/electron/src/components/dashboard/TotalDocumentsCard.jsx +44 -0
  39. package/electron/src/components/database/CreateDatabaseModal.jsx +109 -0
  40. package/electron/src/components/database/DeleteDatabaseModal.jsx +97 -0
  41. package/electron/src/components/query/CodeEditor.jsx +343 -0
  42. package/electron/src/components/query/ObjectEditor.jsx +115 -0
  43. package/electron/src/components/query/ObjectView.jsx +44 -0
  44. package/electron/src/components/query/QueryEditor.jsx +32 -0
  45. package/electron/src/components/query/queryLanguage.js +755 -0
  46. package/electron/src/components/ui/Button.jsx +58 -0
  47. package/electron/src/components/ui/Card.jsx +29 -0
  48. package/electron/src/components/ui/ErrorBoundary.jsx +108 -0
  49. package/electron/src/components/ui/Feedback.jsx +66 -0
  50. package/electron/src/components/ui/Field.jsx +65 -0
  51. package/electron/src/components/ui/MetricCard.jsx +104 -0
  52. package/electron/src/components/ui/Modal.jsx +119 -0
  53. package/electron/src/components/ui/Page.jsx +40 -0
  54. package/electron/src/config/key.js +11 -0
  55. package/electron/src/index.css +181 -0
  56. package/electron/src/index.html +13 -0
  57. package/electron/src/layout/Sidebar.jsx +478 -0
  58. package/electron/src/layout/StatusBar.jsx +70 -0
  59. package/electron/src/layout/Titlebar.jsx +128 -0
  60. package/electron/src/main.jsx +42 -0
  61. package/electron/src/pages/ConnectionHub.jsx +464 -0
  62. package/electron/src/pages/Dashboard.jsx +128 -0
  63. package/electron/src/pages/Documents.jsx +823 -0
  64. package/electron/src/pages/Import.jsx +527 -0
  65. package/electron/src/pages/UserManagement.jsx +294 -0
  66. package/electron/src/pages/Welcome.jsx +157 -0
  67. package/electron/src/store/authStore.js +30 -0
  68. package/electron/src/store/connectionStore.js +103 -0
  69. package/electron/src/store/dbStore.js +165 -0
  70. package/electron/src/store/store.js +8 -0
  71. package/electron/src/utils/format.js +39 -0
  72. package/electron/vite.config.js +28 -0
  73. package/lib/Services/Indexation.operation.js +1 -1
  74. package/package.json +1 -1
@@ -0,0 +1,343 @@
1
+ import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
2
+ import { tokenize } from './queryLanguage'
3
+
4
+ /** Light VS Code / GitHub Light palette */
5
+ const TOKEN_COLORS = {
6
+ key: '#0284c7', // sky-600
7
+ operator: '#9333ea', // purple-600
8
+ string: '#15803d', // green-700
9
+ number: '#b45309', // amber-700
10
+ literal: '#0369a1', // cyan-700
11
+ method: '#7c3aed', // violet-600
12
+ identifier: '#0f766e', // teal-700
13
+ punctuation: '#475569', // slate-600
14
+ space: 'inherit'
15
+ }
16
+
17
+ const FONT = '13px/20px ui-monospace, SFMono-Regular, Menlo, Consolas, monospace'
18
+ const PADDING_Y = 10
19
+ const PADDING_X = 12
20
+ const GUTTER = 38
21
+ const LINE_HEIGHT = 20
22
+
23
+ const NO_SUGGESTIONS = { items: [], replaceFrom: 0, prefix: '' }
24
+
25
+ const PAIRS = { '(': ')', '[': ']', '{': '}', '"': '"', "'": "'", '`': '`' }
26
+ const CLOSERS = new Set([')', ']', '}', '"', "'", '`'])
27
+ const QUOTES = new Set(['"', "'", '`'])
28
+
29
+ function useHighlighted (value, diagnostics) {
30
+ return useMemo(
31
+ () =>
32
+ tokenize(value).map((token, index) => {
33
+ const end = token.start + token.value.length
34
+ const diagnostic = diagnostics.find((d) => d.start < end && d.end > token.start)
35
+
36
+ return (
37
+ <span
38
+ key={index}
39
+ style={{
40
+ color: TOKEN_COLORS[token.type] ?? TOKEN_COLORS.punctuation,
41
+ textDecoration: diagnostic ? 'underline wavy' : undefined,
42
+ textDecorationColor: diagnostic
43
+ ? diagnostic.severity === 'error' ? '#dc2626' : '#d97706'
44
+ : undefined,
45
+ textUnderlineOffset: diagnostic ? '3px' : undefined
46
+ }}
47
+ >
48
+ {token.value}
49
+ </span>
50
+ )
51
+ }),
52
+ [value, diagnostics]
53
+ )
54
+ }
55
+
56
+ const CodeEditor = ({
57
+ value,
58
+ onChange,
59
+ diagnostics = [],
60
+ onSubmit,
61
+ suggest,
62
+ suggestAll,
63
+ minHeight = 170,
64
+ maxHeight = 260,
65
+ ariaLabel = 'Code editor'
66
+ }) => {
67
+ const textareaRef = useRef(null)
68
+ const highlightRef = useRef(null)
69
+ const [suggestions, setSuggestions] = useState(NO_SUGGESTIONS)
70
+ const [activeIndex, setActiveIndex] = useState(0)
71
+ const [caret, setCaret] = useState(0)
72
+ const [scrollTop, setScrollTop] = useState(0)
73
+
74
+ const highlighted = useHighlighted(value, diagnostics)
75
+ const lineCount = useMemo(() => value.split('\n').length, [value])
76
+
77
+ useLayoutEffect(() => {
78
+ if (highlightRef.current) highlightRef.current.scrollTop = scrollTop
79
+ }, [scrollTop])
80
+
81
+ useEffect(() => setActiveIndex(0), [suggestions.items])
82
+
83
+ const closeSuggestions = () => setSuggestions(NO_SUGGESTIONS)
84
+
85
+ const refreshSuggestions = (text, position) => {
86
+ setSuggestions(suggest ? suggest(text, position) : NO_SUGGESTIONS)
87
+ }
88
+
89
+ const handleChange = (event) => {
90
+ const next = event.target.value
91
+ const position = event.target.selectionStart
92
+ onChange(next)
93
+ setCaret(position)
94
+ refreshSuggestions(next, position)
95
+ }
96
+
97
+ const applySuggestion = (item) => {
98
+ const next = value.slice(0, suggestions.replaceFrom) + item.insert + value.slice(caret)
99
+ const caretTarget = suggestions.replaceFrom + (item.caretOffset ?? item.insert.length)
100
+
101
+ onChange(next)
102
+ closeSuggestions()
103
+
104
+ requestAnimationFrame(() => {
105
+ const textarea = textareaRef.current
106
+ if (!textarea) return
107
+ textarea.focus()
108
+ textarea.setSelectionRange(caretTarget, caretTarget)
109
+ setCaret(caretTarget)
110
+ })
111
+ }
112
+
113
+ const handleKeyDown = (event) => {
114
+ const open = suggestions.items.length > 0
115
+
116
+ if (event.key === 'Escape' && open) {
117
+ event.preventDefault()
118
+ event.stopPropagation()
119
+ closeSuggestions()
120
+ return
121
+ }
122
+
123
+ if (open && (event.key === 'ArrowDown' || event.key === 'ArrowUp')) {
124
+ event.preventDefault()
125
+ const delta = event.key === 'ArrowDown' ? 1 : -1
126
+ setActiveIndex((current) => {
127
+ const next = current + delta
128
+ if (next < 0) return suggestions.items.length - 1
129
+ if (next >= suggestions.items.length) return 0
130
+ return next
131
+ })
132
+ return
133
+ }
134
+
135
+ if (open && (event.key === 'Enter' || event.key === 'Tab')) {
136
+ event.preventDefault()
137
+ applySuggestion(suggestions.items[activeIndex])
138
+ return
139
+ }
140
+
141
+ if (event.code === 'Space' && (event.ctrlKey || event.metaKey) && suggestAll) {
142
+ event.preventDefault()
143
+ const position = event.target.selectionStart
144
+ setCaret(position)
145
+ setSuggestions(suggestAll(value, position))
146
+ return
147
+ }
148
+
149
+ if (event.key === 'Enter' && (event.ctrlKey || event.metaKey)) {
150
+ event.preventDefault()
151
+ onSubmit?.()
152
+ return
153
+ }
154
+
155
+ if (event.key === 'Tab') {
156
+ event.preventDefault()
157
+ const start = event.target.selectionStart
158
+ const next = value.slice(0, start) + ' ' + value.slice(event.target.selectionEnd)
159
+ onChange(next)
160
+ requestAnimationFrame(() => textareaRef.current?.setSelectionRange(start + 2, start + 2))
161
+ return
162
+ }
163
+
164
+ const start = event.target.selectionStart
165
+ const end = event.target.selectionEnd
166
+ const setCaretTo = (from, to = from) =>
167
+ requestAnimationFrame(() => textareaRef.current?.setSelectionRange(from, to))
168
+
169
+ if (CLOSERS.has(event.key) && start === end && value[start] === event.key) {
170
+ event.preventDefault()
171
+ setCaret(start + 1)
172
+ setCaretTo(start + 1)
173
+ return
174
+ }
175
+
176
+ if (Object.prototype.hasOwnProperty.call(PAIRS, event.key)) {
177
+ const close = PAIRS[event.key]
178
+
179
+ if (start !== end) {
180
+ event.preventDefault()
181
+ const selected = value.slice(start, end)
182
+ onChange(`${value.slice(0, start)}${event.key}${selected}${close}${value.slice(end)}`)
183
+ setCaretTo(start + 1, end + 1)
184
+ return
185
+ }
186
+
187
+ const previous = value[start - 1] ?? ''
188
+ if (QUOTES.has(event.key) && /[A-Za-z0-9_$]/.test(previous)) return
189
+
190
+ event.preventDefault()
191
+ onChange(`${value.slice(0, start)}${event.key}${close}${value.slice(start)}`)
192
+ setCaret(start + 1)
193
+ setCaretTo(start + 1)
194
+ return
195
+ }
196
+
197
+ if (event.key === 'Backspace' && start === end && start > 0) {
198
+ const before = value[start - 1]
199
+ if (PAIRS[before] && value[start] === PAIRS[before]) {
200
+ event.preventDefault()
201
+ onChange(value.slice(0, start - 1) + value.slice(start + 1))
202
+ setCaret(start - 1)
203
+ setCaretTo(start - 1)
204
+ }
205
+ }
206
+ }
207
+
208
+ const handleSelect = (event) => {
209
+ const position = event.target.selectionStart
210
+ setCaret(position)
211
+ if (suggestions.items.length > 0) refreshSuggestions(value, position)
212
+ }
213
+
214
+ const caretLine = value.slice(0, suggestions.replaceFrom).split('\n').length - 1
215
+ const caretColumn =
216
+ suggestions.replaceFrom - (value.lastIndexOf('\n', suggestions.replaceFrom - 1) + 1)
217
+ const popupTop = PADDING_Y + (caretLine + 1) * LINE_HEIGHT - scrollTop + 4
218
+ const popupLeft = Math.min(GUTTER + PADDING_X + caretColumn * 7.22, 340)
219
+
220
+ const layerStyle = {
221
+ font: FONT,
222
+ padding: `${PADDING_Y}px ${PADDING_X}px`,
223
+ paddingLeft: GUTTER + PADDING_X
224
+ }
225
+
226
+ return (
227
+ <div className='relative'>
228
+ <div
229
+ className='relative overflow-hidden rounded-lg border border-slate-200 shadow-xs bg-slate-50'
230
+ >
231
+ {/* Line-number gutter */}
232
+ <div
233
+ aria-hidden='true'
234
+ className='absolute bottom-0 left-0 top-0 select-none text-right'
235
+ style={{
236
+ width: GUTTER,
237
+ padding: `${PADDING_Y}px 8px 0 0`,
238
+ font: FONT,
239
+ color: '#94a3b8',
240
+ background: '#f1f5f9',
241
+ borderRight: '1px solid #e2e8f0',
242
+ zIndex: 2
243
+ }}
244
+ >
245
+ <div style={{ transform: `translateY(-${scrollTop}px)` }}>
246
+ {Array.from({ length: lineCount }, (_, index) => (
247
+ <div key={index} style={{ height: LINE_HEIGHT }}>{index + 1}</div>
248
+ ))}
249
+ </div>
250
+ </div>
251
+
252
+ {/* Colour layer */}
253
+ <pre
254
+ ref={highlightRef}
255
+ aria-hidden='true'
256
+ className='m-0 overflow-hidden whitespace-pre-wrap break-words'
257
+ style={{ ...layerStyle, minHeight, maxHeight, color: '#0f172a' }}
258
+ >
259
+ {highlighted}
260
+ {'\n'}
261
+ </pre>
262
+
263
+ {/* Input layer */}
264
+ <textarea
265
+ ref={textareaRef}
266
+ value={value}
267
+ onChange={handleChange}
268
+ onKeyDown={handleKeyDown}
269
+ onSelect={handleSelect}
270
+ onScroll={(event) => setScrollTop(event.target.scrollTop)}
271
+ onBlur={() => setTimeout(closeSuggestions, 120)}
272
+ spellCheck='false'
273
+ autoComplete='off'
274
+ autoCorrect='off'
275
+ autoCapitalize='off'
276
+ aria-label={ariaLabel}
277
+ className='absolute inset-0 resize-none overflow-auto whitespace-pre-wrap break-words bg-transparent outline-none'
278
+ style={{ ...layerStyle, color: 'transparent', caretColor: '#0f172a' }}
279
+ />
280
+
281
+ {/* Completion popup */}
282
+ {suggestions.items.length > 0 && (
283
+ <div
284
+ className='absolute z-20 overflow-hidden rounded-md border border-slate-200 bg-white shadow-xl'
285
+ style={{
286
+ top: popupTop,
287
+ left: popupLeft,
288
+ width: 320
289
+ }}
290
+ >
291
+ <ul className='max-h-48 overflow-y-auto py-1'>
292
+ {suggestions.items.map((item, index) => (
293
+ <li key={item.label}>
294
+ <button
295
+ type='button'
296
+ onMouseDown={(event) => {
297
+ event.preventDefault()
298
+ applySuggestion(item)
299
+ }}
300
+ onMouseEnter={() => setActiveIndex(index)}
301
+ className='flex w-full items-baseline gap-2 px-3 py-1 text-left transition-colors'
302
+ style={{
303
+ font: FONT,
304
+ background: index === activeIndex ? '#ecfdf5' : 'transparent',
305
+ color: index === activeIndex ? '#059669' : '#1e293b'
306
+ }}
307
+ >
308
+ <span style={{ color: '#0284c7' }}>{item.label}</span>
309
+ <span className='truncate text-xs text-slate-400'>
310
+ {item.detail}
311
+ </span>
312
+ </button>
313
+ </li>
314
+ ))}
315
+ </ul>
316
+ </div>
317
+ )}
318
+ </div>
319
+
320
+ {diagnostics.length > 0 && (
321
+ <ul className='mt-2 space-y-1'>
322
+ {diagnostics.map((diagnostic, index) => (
323
+ <li
324
+ key={index}
325
+ className={`flex items-start gap-2 rounded-md border px-3 py-1.5 text-xs ${
326
+ diagnostic.severity === 'error'
327
+ ? 'border-red-200 bg-red-50 text-red-700'
328
+ : 'border-amber-200 bg-amber-50 text-amber-800'
329
+ }`}
330
+ >
331
+ <span className='mt-px font-bold'>
332
+ {diagnostic.severity === 'error' ? '✕' : '!'}
333
+ </span>
334
+ <span className='font-mono leading-relaxed'>{diagnostic.message}</span>
335
+ </li>
336
+ ))}
337
+ </ul>
338
+ )}
339
+ </div>
340
+ )
341
+ }
342
+
343
+ export default CodeEditor
@@ -0,0 +1,115 @@
1
+ import { useMemo } from 'react'
2
+ import CodeEditor from './CodeEditor'
3
+ import { validateDocument, QUERY_OPERATORS } from './queryLanguage'
4
+
5
+ const COMMON_FIELDS = [
6
+ 'name', 'title', 'email', 'status', 'role', 'type', 'age', 'price',
7
+ 'description', 'isActive', 'tags', 'metadata', 'userId', 'category', 'count'
8
+ ]
9
+
10
+ const COMMON_LITERALS = [
11
+ { label: 'true', detail: 'boolean', doc: 'Boolean true', insert: 'true' },
12
+ { label: 'false', detail: 'boolean', doc: 'Boolean false', insert: 'false' },
13
+ { label: 'null', detail: 'null', doc: 'Null value', insert: 'null' },
14
+ { label: 'array', detail: '[]', doc: 'Empty array literal', insert: '[]' },
15
+ { label: 'object', detail: '{}', doc: 'Empty object literal', insert: '{\n \n}' },
16
+ { label: '$set', detail: 'operator', doc: 'Update operator to set fields', insert: '$set: {}' },
17
+ { label: '$unset', detail: 'operator', doc: 'Update operator to delete fields', insert: '$unset: {}' },
18
+ { label: '$inc', detail: 'operator', doc: 'Increment numeric field', insert: '$inc: {}' },
19
+ { label: '$push', detail: 'operator', doc: 'Append value to array', insert: '$push: {}' }
20
+ ]
21
+
22
+ const getObjectSuggestions = (text, caret, fields = []) => {
23
+ const before = text.slice(0, caret)
24
+
25
+ // If typing an operator ($...)
26
+ const opMatch = /"?(\$[A-Za-z]*)$/.exec(before)
27
+ if (opMatch) {
28
+ const prefix = opMatch[1]
29
+ return {
30
+ items: QUERY_OPERATORS.filter((o) => o.label.startsWith(prefix)),
31
+ replaceFrom: caret - prefix.length,
32
+ prefix
33
+ }
34
+ }
35
+
36
+ // If typing a property/field name or literal
37
+ const idMatch = /([A-Za-z_][A-Za-z0-9_]*)$/.exec(before)
38
+ if (idMatch) {
39
+ const prefix = idMatch[1]
40
+ const candidateFields = fields && fields.length > 0 ? fields : COMMON_FIELDS
41
+ const items = []
42
+
43
+ candidateFields.forEach((f) => {
44
+ if (f.startsWith(prefix)) {
45
+ items.push({
46
+ label: f,
47
+ detail: 'field',
48
+ doc: `Document property "${f}"`,
49
+ insert: `${f}: `
50
+ })
51
+ }
52
+ })
53
+
54
+ COMMON_LITERALS.forEach((lit) => {
55
+ if (lit.label.startsWith(prefix)) {
56
+ items.push(lit)
57
+ }
58
+ })
59
+
60
+ if (items.length > 0) {
61
+ return {
62
+ items,
63
+ replaceFrom: caret - prefix.length,
64
+ prefix
65
+ }
66
+ }
67
+ }
68
+
69
+ return { items: [], replaceFrom: caret, prefix: '' }
70
+ }
71
+
72
+ const getAllObjectSuggestions = (text, caret, fields = []) => {
73
+ const candidateFields = fields && fields.length > 0 ? fields : COMMON_FIELDS
74
+ const items = candidateFields.map((f) => ({
75
+ label: f,
76
+ detail: 'field',
77
+ doc: `Document property "${f}"`,
78
+ insert: `${f}: `
79
+ }))
80
+
81
+ items.push(...COMMON_LITERALS)
82
+ return { items, replaceFrom: caret, prefix: '' }
83
+ }
84
+
85
+ /**
86
+ * Document editor: accepts JavaScript object-literal syntax (unquoted keys, single quotes, trailing commas)
87
+ * with full autocomplete suggestions for fields, operators, and literals.
88
+ */
89
+ const ObjectEditor = ({
90
+ value,
91
+ onChange,
92
+ onSubmit,
93
+ fields = [],
94
+ minHeight = 220,
95
+ maxHeight = 320
96
+ }) => {
97
+ const diagnostics = useMemo(() => validateDocument(value), [value])
98
+
99
+ return (
100
+ <CodeEditor
101
+ value={value}
102
+ onChange={onChange}
103
+ diagnostics={diagnostics}
104
+ onSubmit={onSubmit}
105
+ suggest={(text, caret) => getObjectSuggestions(text, caret, fields)}
106
+ suggestAll={(text, caret) => getAllObjectSuggestions(text, caret, fields)}
107
+ minHeight={minHeight}
108
+ maxHeight={maxHeight}
109
+ ariaLabel='Document editor'
110
+ />
111
+ )
112
+ }
113
+
114
+ export default ObjectEditor
115
+ export { validateDocument }
@@ -0,0 +1,44 @@
1
+ import { useMemo } from 'react'
2
+ import { formatLiteral, tokenize } from './queryLanguage'
3
+
4
+ /**
5
+ * Read-only, syntax-highlighted rendering of a value as a JavaScript object literal.
6
+ * Light VS Code / GitHub Light palette for maximum readability on white backgrounds.
7
+ */
8
+ const TOKEN_COLORS = {
9
+ key: '#0284c7', // sky-600
10
+ operator: '#9333ea', // purple-600
11
+ string: '#15803d', // green-700
12
+ number: '#b45309', // amber-700
13
+ literal: '#0369a1', // cyan-700
14
+ method: '#7c3aed', // violet-600
15
+ identifier: '#0f766e', // teal-700
16
+ punctuation: '#475569', // slate-600
17
+ space: 'inherit'
18
+ }
19
+
20
+ const ObjectView = ({ value, className = '', maxHeight = 220 }) => {
21
+ const nodes = useMemo(() => {
22
+ const source = typeof value === 'string' ? value : formatLiteral(value)
23
+ return tokenize(source).map((token, index) => (
24
+ <span key={index} style={{ color: TOKEN_COLORS[token.type] ?? TOKEN_COLORS.punctuation }}>
25
+ {token.value}
26
+ </span>
27
+ ))
28
+ }, [value])
29
+
30
+ return (
31
+ <pre
32
+ className={`m-0 overflow-auto rounded-lg px-3.5 py-3 text-[12px] leading-5 border border-slate-200 bg-slate-50 ${className}`}
33
+ style={{
34
+ color: '#0f172a',
35
+ maxHeight,
36
+ fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Consolas, monospace'
37
+ }}
38
+ >
39
+ {nodes}
40
+ </pre>
41
+ )
42
+ }
43
+
44
+ export default ObjectView
@@ -0,0 +1,32 @@
1
+ import CodeEditor from './CodeEditor'
2
+ import { getAllSuggestions, getSuggestions, validate } from './queryLanguage'
3
+
4
+ /**
5
+ * The Query Console's editor: the shared {@link CodeEditor} shell bound to the query
6
+ * language's completion, fields auto-completion, and validation.
7
+ */
8
+ const QueryEditor = ({
9
+ value,
10
+ onChange,
11
+ collectionName,
12
+ fields = [],
13
+ onSubmit,
14
+ diagnostics,
15
+ minHeight = 140,
16
+ maxHeight = 220
17
+ }) => (
18
+ <CodeEditor
19
+ value={value}
20
+ onChange={onChange}
21
+ diagnostics={diagnostics}
22
+ onSubmit={onSubmit}
23
+ minHeight={minHeight}
24
+ maxHeight={maxHeight}
25
+ ariaLabel='Query editor'
26
+ suggest={(text, caret) => getSuggestions(text, caret, collectionName, fields)}
27
+ suggestAll={(text, caret) => getAllSuggestions(text, caret, collectionName, fields)}
28
+ />
29
+ )
30
+
31
+ export default QueryEditor
32
+ export { validate }