@dxos/react-ui-form 0.8.4-main.f9ba587 → 0.8.4-main.fffef41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/browser/index.mjs +884 -637
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +884 -637
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/components/ControlSection/ControlSection.d.ts +2 -2
- package/dist/types/src/components/ControlSection/ControlSection.d.ts.map +1 -1
- package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts +80 -5
- package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts.map +1 -1
- package/dist/types/src/components/FieldEditor/FieldEditor.d.ts +4 -5
- package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts +77 -7
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/ArrayField.d.ts +2 -2
- package/dist/types/src/components/Form/ArrayField.d.ts.map +1 -1
- package/dist/types/src/components/Form/Defaults.d.ts +5 -5
- package/dist/types/src/components/Form/Defaults.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.d.ts +8 -6
- package/dist/types/src/components/Form/Form.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.stories.d.ts +128 -32
- package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormActions.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormContent.d.ts +17 -11
- package/dist/types/src/components/Form/FormContent.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormContext.d.ts +3 -3
- package/dist/types/src/components/Form/FormContext.d.ts.map +1 -1
- package/dist/types/src/components/Form/Input.d.ts +5 -2
- package/dist/types/src/components/Form/Input.d.ts.map +1 -1
- package/dist/types/src/components/Form/RefField.d.ts +15 -5
- package/dist/types/src/components/Form/RefField.d.ts.map +1 -1
- package/dist/types/src/components/Form/custom/GeoPointInput.d.ts +1 -1
- package/dist/types/src/components/Form/custom/GeoPointInput.d.ts.map +1 -1
- package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts +1 -1
- package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts.map +1 -1
- package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts +75 -4
- package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/custom/TupleInput.d.ts +1 -1
- package/dist/types/src/components/Form/factory.d.ts +1 -1
- package/dist/types/src/components/Form/factory.d.ts.map +1 -1
- package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts +41 -0
- package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts.map +1 -0
- package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts +81 -0
- package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts.map +1 -0
- package/dist/types/src/components/ObjectPicker/index.d.ts +2 -0
- package/dist/types/src/components/ObjectPicker/index.d.ts.map +1 -0
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +20 -6
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +80 -9
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +1 -1
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/components/testing.d.ts +6 -3
- package/dist/types/src/components/testing.d.ts.map +1 -1
- package/dist/types/src/hooks/useForm.d.ts +3 -3
- package/dist/types/src/hooks/useForm.d.ts.map +1 -1
- package/dist/types/src/hooks/useQueryRefOptions.d.ts +9 -3
- package/dist/types/src/hooks/useQueryRefOptions.d.ts.map +1 -1
- package/dist/types/src/hooks/useRefQueryLookupHandler.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +13 -8
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/util/field.d.ts +1 -1
- package/dist/types/src/util/field.d.ts.map +1 -1
- package/dist/types/src/util/formatting.d.ts +1 -1
- package/dist/types/src/util/formatting.d.ts.map +1 -1
- package/dist/types/src/util/refs.d.ts +1 -1
- package/dist/types/src/util/refs.d.ts.map +1 -1
- package/dist/types/src/util/schema.d.ts +1 -1
- package/dist/types/src/util/schema.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +42 -36
- package/src/components/ControlSection/ControlSection.stories.tsx +11 -14
- package/src/components/ControlSection/ControlSection.tsx +26 -12
- package/src/components/FieldEditor/FieldEditor.stories.tsx +26 -24
- package/src/components/FieldEditor/FieldEditor.test.tsx +14 -13
- package/src/components/FieldEditor/FieldEditor.tsx +21 -23
- package/src/components/Form/ArrayField.tsx +13 -12
- package/src/components/Form/Defaults.tsx +23 -21
- package/src/components/Form/Form.stories.tsx +135 -99
- package/src/components/Form/Form.tsx +30 -16
- package/src/components/Form/FormActions.tsx +4 -3
- package/src/components/Form/FormContent.tsx +171 -49
- package/src/components/Form/FormContext.tsx +8 -8
- package/src/components/Form/Input.tsx +5 -2
- package/src/components/Form/RefField.tsx +111 -41
- package/src/components/Form/custom/GeoPointInput.tsx +29 -21
- package/src/components/Form/custom/SelectOptionsInput.stories.tsx +11 -11
- package/src/components/Form/custom/SelectOptionsInput.tsx +31 -32
- package/src/components/Form/custom/TupleInput.tsx +2 -2
- package/src/components/Form/factory.tsx +2 -2
- package/src/components/ObjectPicker/ObjectPicker.stories.tsx +207 -0
- package/src/components/ObjectPicker/ObjectPicker.tsx +161 -0
- package/src/components/ObjectPicker/index.ts +5 -0
- package/src/components/ViewEditor/ViewEditor.stories.tsx +130 -49
- package/src/components/ViewEditor/ViewEditor.test.tsx +19 -19
- package/src/components/ViewEditor/ViewEditor.tsx +281 -233
- package/src/components/index.ts +1 -2
- package/src/components/testing.tsx +15 -9
- package/src/hooks/useForm.ts +11 -6
- package/src/hooks/useQueryRefOptions.ts +15 -6
- package/src/hooks/useRefQueryLookupHandler.ts +3 -3
- package/src/translations.ts +15 -9
- package/src/util/field.ts +9 -19
- package/src/util/formatting.ts +1 -1
- package/src/util/refs.ts +7 -7
- package/src/util/schema.ts +2 -4
- package/dist/types/src/components/deprecated/Form.d.ts +0 -15
- package/dist/types/src/components/deprecated/Form.d.ts.map +0 -1
- package/dist/types/src/components/deprecated/Form.stories.d.ts +0 -13
- package/dist/types/src/components/deprecated/Form.stories.d.ts.map +0 -1
- package/dist/types/src/components/deprecated/FormInput.d.ts +0 -16
- package/dist/types/src/components/deprecated/FormInput.d.ts.map +0 -1
- package/dist/types/src/components/deprecated/index.d.ts +0 -2
- package/dist/types/src/components/deprecated/index.d.ts.map +0 -1
- package/src/components/deprecated/Form.stories.tsx +0 -62
- package/src/components/deprecated/Form.tsx +0 -119
- package/src/components/deprecated/FormInput.tsx +0 -71
- package/src/components/deprecated/index.ts +0 -5
|
@@ -2,284 +2,332 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
6
|
-
import
|
|
5
|
+
import * as Array from 'effect/Array';
|
|
6
|
+
import * as Match from 'effect/Match';
|
|
7
|
+
import * as Option from 'effect/Option';
|
|
8
|
+
import * as Schema from 'effect/Schema';
|
|
9
|
+
import React, { forwardRef, useCallback, useImperativeHandle, useMemo, useState } from 'react';
|
|
7
10
|
|
|
8
|
-
import {
|
|
9
|
-
import { EchoSchema, Format, type JsonProp, isMutable, toJsonSchema } from '@dxos/echo
|
|
11
|
+
import { QueryAST } from '@dxos/echo';
|
|
12
|
+
import { EchoSchema, Format, type JsonProp, isMutable, toJsonSchema } from '@dxos/echo/internal';
|
|
13
|
+
import { Filter, Query, type SchemaRegistry } from '@dxos/echo-db';
|
|
10
14
|
import { invariant } from '@dxos/invariant';
|
|
11
|
-
import { Callout, IconButton, type ThemedClassName, useTranslation } from '@dxos/react-ui';
|
|
15
|
+
import { Callout, IconButton, Input, type ThemedClassName, useTranslation } from '@dxos/react-ui';
|
|
16
|
+
import { QueryForm, type QueryFormProps } from '@dxos/react-ui-components';
|
|
12
17
|
import { List } from '@dxos/react-ui-list';
|
|
13
18
|
import { cardSpacing } from '@dxos/react-ui-stack';
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
19
|
+
import { inputTextLabel, mx, subtleHover } from '@dxos/react-ui-theme';
|
|
20
|
+
import {
|
|
21
|
+
FieldSchema,
|
|
22
|
+
type FieldType,
|
|
23
|
+
ProjectionModel,
|
|
24
|
+
VIEW_FIELD_LIMIT,
|
|
25
|
+
type View,
|
|
26
|
+
getTypenameFromQuery,
|
|
27
|
+
} from '@dxos/schema';
|
|
16
28
|
|
|
17
29
|
import { translationKey } from '../../translations';
|
|
18
30
|
import { FieldEditor } from '../FieldEditor';
|
|
19
|
-
import { Form, type FormProps } from '../Form';
|
|
31
|
+
import { Form, type FormProps, type InputComponent, InputHeader, type InputProps } from '../Form';
|
|
20
32
|
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
const ViewMetaSchema = Schema.Struct({
|
|
24
|
-
name: Schema.String.annotations({
|
|
25
|
-
title: 'View',
|
|
26
|
-
}),
|
|
27
|
-
typename: Format.URL.annotations({
|
|
28
|
-
title: 'Typename',
|
|
29
|
-
}),
|
|
30
|
-
}).pipe(Schema.mutable);
|
|
31
|
-
|
|
32
|
-
type ViewMetaType = Schema.Schema.Type<typeof ViewMetaSchema>;
|
|
33
|
+
const listGrid = 'grid grid-cols-[min-content_1fr_min-content_min-content_min-content]';
|
|
34
|
+
const listItemGrid = 'grid grid-cols-subgrid col-span-5';
|
|
33
35
|
|
|
34
36
|
export type ViewEditorProps = ThemedClassName<
|
|
35
37
|
{
|
|
36
38
|
schema: Schema.Schema.AnyNoContext;
|
|
37
|
-
view:
|
|
39
|
+
view: View.View;
|
|
40
|
+
mode?: 'schema' | 'query' | 'named-query';
|
|
38
41
|
registry?: SchemaRegistry;
|
|
39
42
|
readonly?: boolean;
|
|
40
43
|
showHeading?: boolean;
|
|
41
|
-
|
|
44
|
+
onQueryChanged?: (query: QueryAST.Query, target?: string) => void;
|
|
42
45
|
onDelete?: (fieldId: string) => void;
|
|
43
|
-
} & Pick<FormProps<any>, 'outerSpacing'>
|
|
46
|
+
} & Pick<FormProps<any>, 'outerSpacing'> &
|
|
47
|
+
Pick<QueryFormProps, 'types' | 'tags'>
|
|
44
48
|
>;
|
|
45
49
|
|
|
46
50
|
/**
|
|
47
51
|
* Schema-based object form.
|
|
48
52
|
*/
|
|
49
|
-
export const ViewEditor = (
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
53
|
+
export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
|
|
54
|
+
(
|
|
55
|
+
{
|
|
56
|
+
classNames,
|
|
57
|
+
schema,
|
|
58
|
+
view,
|
|
59
|
+
mode = 'schema',
|
|
60
|
+
registry,
|
|
61
|
+
readonly,
|
|
62
|
+
showHeading = false,
|
|
63
|
+
types,
|
|
64
|
+
tags,
|
|
65
|
+
onQueryChanged,
|
|
66
|
+
onDelete,
|
|
67
|
+
outerSpacing = true,
|
|
68
|
+
},
|
|
69
|
+
forwardedRef,
|
|
70
|
+
) => {
|
|
71
|
+
const schemaReadonly = !isMutable(schema);
|
|
72
|
+
const { t } = useTranslation(translationKey);
|
|
73
|
+
const projectionModel = useMemo(() => {
|
|
74
|
+
// Use reactive and mutable version of json schema when schema is mutable.
|
|
75
|
+
const jsonSchema = schema instanceof EchoSchema ? schema.jsonSchema : toJsonSchema(schema);
|
|
76
|
+
return new ProjectionModel(jsonSchema, view.projection);
|
|
77
|
+
}, [schema, JSON.stringify(view.projection)]);
|
|
78
|
+
useImperativeHandle(forwardedRef, () => projectionModel, [projectionModel]);
|
|
79
|
+
const [expandedField, setExpandedField] = useState<FieldType['id']>();
|
|
68
80
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
81
|
+
const queueTarget = Match.value(view.query.ast).pipe(
|
|
82
|
+
Match.when({ type: 'options' }, ({ options }) => {
|
|
83
|
+
return Option.fromNullable(options.queues).pipe(
|
|
84
|
+
Option.flatMap((queues) => Array.head(queues)),
|
|
85
|
+
Option.getOrUndefined,
|
|
86
|
+
);
|
|
87
|
+
}),
|
|
88
|
+
Match.orElse(() => undefined),
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const viewSchema = useMemo(() => {
|
|
92
|
+
const base = Schema.Struct({
|
|
93
|
+
query:
|
|
94
|
+
mode === 'schema'
|
|
95
|
+
? Format.URL.annotations({ title: 'Record type' })
|
|
96
|
+
: QueryAST.Query.annotations({ title: 'Query' }),
|
|
97
|
+
});
|
|
78
98
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
99
|
+
if (mode === 'query' || mode === 'named-query') {
|
|
100
|
+
const name = Schema.Struct({
|
|
101
|
+
name: Schema.optional(Schema.String.annotations({ title: 'Name' })),
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
return Schema.Struct({
|
|
105
|
+
...(mode === 'named-query' ? { ...name.fields, ...base.fields } : base.fields),
|
|
106
|
+
target: Schema.optional(Schema.String.annotations({ title: 'Target Queue' })),
|
|
107
|
+
}).pipe(Schema.mutable);
|
|
83
108
|
}
|
|
84
|
-
setField((f) => (f === field ? undefined : field));
|
|
85
|
-
},
|
|
86
|
-
[readonly],
|
|
87
|
-
);
|
|
88
109
|
|
|
89
|
-
|
|
110
|
+
return base.pipe(Schema.mutable);
|
|
111
|
+
}, [mode]);
|
|
90
112
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
113
|
+
// TODO(burdon): Need to warn user of possible consequences of editing.
|
|
114
|
+
// TODO(burdon): Settings should have domain name owned by user.
|
|
115
|
+
const viewValues = useMemo(
|
|
116
|
+
() => ({
|
|
117
|
+
name: view.name,
|
|
118
|
+
query: mode === 'schema' ? getTypenameFromQuery(view.query.ast) : view.query.ast,
|
|
119
|
+
target: queueTarget,
|
|
120
|
+
}),
|
|
121
|
+
[view.name, mode, view.query.ast, queueTarget],
|
|
122
|
+
);
|
|
96
123
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
124
|
+
const handleToggleField = useCallback(
|
|
125
|
+
(field: FieldType) => {
|
|
126
|
+
setExpandedField((prevExpandedFieldId) => (prevExpandedFieldId === field.id ? undefined : field.id));
|
|
127
|
+
},
|
|
128
|
+
[readonly],
|
|
129
|
+
);
|
|
104
130
|
|
|
105
|
-
|
|
106
|
-
onTypenameChanged?.(typename);
|
|
107
|
-
}
|
|
108
|
-
});
|
|
109
|
-
},
|
|
110
|
-
[schema, view, onTypenameChanged, readonly],
|
|
111
|
-
);
|
|
131
|
+
// TODO(burdon): Check if mutable; variant of useCallback that return undefined if readonly?
|
|
112
132
|
|
|
113
|
-
|
|
114
|
-
(fieldId: string) => {
|
|
133
|
+
const handleAdd = useCallback(() => {
|
|
115
134
|
invariant(!readonly);
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
135
|
+
const field = projectionModel.createFieldProjection();
|
|
136
|
+
setExpandedField(field.id);
|
|
137
|
+
}, [schema, projectionModel, readonly]);
|
|
119
138
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
139
|
+
const handleUpdate = useCallback(
|
|
140
|
+
(values: any) => {
|
|
141
|
+
invariant(!readonly);
|
|
142
|
+
requestAnimationFrame(() => {
|
|
143
|
+
if ('name' in values && view.name !== values.name) {
|
|
144
|
+
view.name = values.name;
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
124
147
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
fields.splice(toIndex, 0, moved);
|
|
132
|
-
view.fields = fields;
|
|
133
|
-
},
|
|
134
|
-
[schema, view.fields, readonly],
|
|
135
|
-
);
|
|
148
|
+
const query = mode === 'schema' ? Query.select(Filter.typename(values.query)).ast : values.query;
|
|
149
|
+
onQueryChanged?.(query, values.target);
|
|
150
|
+
});
|
|
151
|
+
},
|
|
152
|
+
[onQueryChanged, readonly, view, queueTarget, mode],
|
|
153
|
+
);
|
|
136
154
|
|
|
137
|
-
|
|
155
|
+
const handleDelete = useCallback(
|
|
156
|
+
(fieldId: string) => {
|
|
157
|
+
invariant(!readonly);
|
|
158
|
+
if (fieldId === expandedField) {
|
|
159
|
+
setExpandedField(undefined);
|
|
160
|
+
}
|
|
138
161
|
|
|
139
|
-
|
|
162
|
+
onDelete?.(fieldId);
|
|
163
|
+
},
|
|
164
|
+
[expandedField, onDelete, readonly],
|
|
165
|
+
);
|
|
140
166
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
167
|
+
const handleMove = useCallback(
|
|
168
|
+
(fromIndex: number, toIndex: number) => {
|
|
169
|
+
invariant(!readonly);
|
|
170
|
+
// NOTE(ZaymonFC): Using arrayMove here causes a race condition with the kanban model.
|
|
171
|
+
const fields = [...view.projection.fields];
|
|
172
|
+
const [moved] = fields.splice(fromIndex, 1);
|
|
173
|
+
fields.splice(toIndex, 0, moved);
|
|
174
|
+
view.projection.fields = fields;
|
|
175
|
+
},
|
|
176
|
+
[view.projection.fields, readonly],
|
|
177
|
+
);
|
|
148
178
|
|
|
149
|
-
|
|
150
|
-
(property: string) => {
|
|
151
|
-
setField(undefined);
|
|
152
|
-
projection.showFieldProjection(property as JsonProp);
|
|
153
|
-
},
|
|
154
|
-
[projection],
|
|
155
|
-
);
|
|
179
|
+
const handleClose = useCallback(() => setExpandedField(undefined), []);
|
|
156
180
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
</div>
|
|
165
|
-
)}
|
|
181
|
+
const handleHide = useCallback(
|
|
182
|
+
(fieldId: string) => {
|
|
183
|
+
setExpandedField(undefined);
|
|
184
|
+
projectionModel.hideFieldProjection(fieldId);
|
|
185
|
+
},
|
|
186
|
+
[projectionModel],
|
|
187
|
+
);
|
|
166
188
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
onSave={handleUpdate}
|
|
175
|
-
outerSpacing={outerSpacing}
|
|
176
|
-
/>
|
|
189
|
+
const handleShow = useCallback(
|
|
190
|
+
(property: string) => {
|
|
191
|
+
setExpandedField(undefined);
|
|
192
|
+
projectionModel.showFieldProjection(property as JsonProp);
|
|
193
|
+
},
|
|
194
|
+
[projectionModel],
|
|
195
|
+
);
|
|
177
196
|
|
|
178
|
-
|
|
179
|
-
<label className={mx(inputTextLabel)}>{t('fields label')}</label>
|
|
197
|
+
const custom = useMemo(() => (mode === 'query' ? customFields({ types, tags }) : undefined), [types, tags, mode]);
|
|
180
198
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
{({ items: fields }) => (
|
|
189
|
-
<>
|
|
190
|
-
{showHeading && (
|
|
191
|
-
<div role='heading' className={grid}>
|
|
192
|
-
<div />
|
|
193
|
-
<div className='flex items-center text-sm'>{t('field path label')}</div>
|
|
194
|
-
</div>
|
|
195
|
-
)}
|
|
199
|
+
return (
|
|
200
|
+
<div role='none' className={mx(classNames)}>
|
|
201
|
+
{schemaReadonly && mode === 'schema' && (
|
|
202
|
+
<Callout.Root valence='info' classNames={['mlb-cardSpacingBlock', outerSpacing && 'mli-cardSpacingInline']}>
|
|
203
|
+
<Callout.Title>{t('system schema description')}</Callout.Title>
|
|
204
|
+
</Callout.Root>
|
|
205
|
+
)}
|
|
196
206
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
icon='ph--eye-slash--regular'
|
|
209
|
-
// TDOO(burdon): Is this the correct test?
|
|
210
|
-
disabled={view.fields.length <= 1}
|
|
211
|
-
onClick={() => handleHide(field.id)}
|
|
212
|
-
/>
|
|
213
|
-
{/* TODO(burdon): Remove unless implement undo. */}
|
|
214
|
-
{!readonly && (
|
|
215
|
-
<List.ItemDeleteButton
|
|
216
|
-
disabled={view.fields.length <= 1}
|
|
217
|
-
onClick={() => handleDelete(field.id)}
|
|
218
|
-
/>
|
|
219
|
-
)}
|
|
220
|
-
</List.Item>
|
|
221
|
-
))}
|
|
222
|
-
</div>
|
|
223
|
-
</>
|
|
224
|
-
)}
|
|
225
|
-
</List.Root>
|
|
207
|
+
{/* TODO(burdon): Is the form read-only or just the schema? */}
|
|
208
|
+
{/* TODO(burdon): Readonly fields should take up the same space as editable fields (just be ghosted). */}
|
|
209
|
+
<Form<Schema.Schema.Type<typeof viewSchema>>
|
|
210
|
+
autoSave
|
|
211
|
+
schema={viewSchema}
|
|
212
|
+
values={viewValues}
|
|
213
|
+
readonly={readonly ? 'disabled-input' : false}
|
|
214
|
+
onSave={handleUpdate}
|
|
215
|
+
outerSpacing={outerSpacing}
|
|
216
|
+
Custom={custom}
|
|
217
|
+
/>
|
|
226
218
|
|
|
227
|
-
{
|
|
228
|
-
|
|
229
|
-
<label className={mx(inputTextLabel)}>{t('hidden fields label')}</label>
|
|
219
|
+
<div role='none' className={outerSpacing ? cardSpacing : 'mlb-cardSpacingBlock'}>
|
|
220
|
+
<h2 className={mx(inputTextLabel)}>{t('fields label')}</h2>
|
|
230
221
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
<List.
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
222
|
+
<List.Root<FieldType>
|
|
223
|
+
items={view.projection.fields}
|
|
224
|
+
isItem={Schema.is(FieldSchema)}
|
|
225
|
+
getId={(field) => field.id}
|
|
226
|
+
onMove={readonly ? undefined : handleMove}
|
|
227
|
+
readonly={readonly}
|
|
228
|
+
>
|
|
229
|
+
{({ items: fields }) => (
|
|
230
|
+
<>
|
|
231
|
+
{showHeading && <h3 className='text-sm'>{t('field path label')}</h3>}
|
|
232
|
+
<div role='list' className={listGrid}>
|
|
233
|
+
{fields?.map((field) => {
|
|
234
|
+
const hidden = field.visible === false;
|
|
235
|
+
return (
|
|
236
|
+
<List.Item<FieldType>
|
|
237
|
+
key={field.id}
|
|
238
|
+
item={field}
|
|
239
|
+
classNames={listItemGrid}
|
|
240
|
+
aria-expanded={expandedField === field.id}
|
|
241
|
+
>
|
|
242
|
+
<div
|
|
243
|
+
role='none'
|
|
244
|
+
className={mx(subtleHover, listItemGrid, 'rounded-sm cursor-pointer min-bs-10')}
|
|
245
|
+
>
|
|
246
|
+
<List.ItemDragHandle disabled={readonly} />
|
|
247
|
+
<List.ItemTitle
|
|
248
|
+
classNames={hidden && 'text-subdued'}
|
|
249
|
+
onClick={() => handleToggleField(field)}
|
|
250
|
+
>
|
|
251
|
+
{field.path}
|
|
252
|
+
</List.ItemTitle>
|
|
253
|
+
<List.ItemButton
|
|
254
|
+
label={t(hidden ? 'show field label' : 'hide field label')}
|
|
255
|
+
data-testid={hidden ? 'show-field-button' : 'hide-field-button'}
|
|
256
|
+
icon={hidden ? 'ph--eye-closed--regular' : 'ph--eye--regular'}
|
|
257
|
+
autoHide={false}
|
|
258
|
+
disabled={readonly || (!hidden && projectionModel.fields.length <= 1)}
|
|
259
|
+
onClick={() => (hidden ? handleShow(field.path) : handleHide(field.id))}
|
|
260
|
+
/>
|
|
261
|
+
{mode === 'schema' && (
|
|
262
|
+
<>
|
|
263
|
+
<List.ItemDeleteButton
|
|
264
|
+
label={t('delete field label')}
|
|
265
|
+
autoHide={false}
|
|
266
|
+
disabled={readonly || schemaReadonly || view.projection.fields.length <= 1}
|
|
267
|
+
onClick={() => handleDelete(field.id)}
|
|
268
|
+
data-testid='field.delete'
|
|
269
|
+
/>
|
|
270
|
+
<IconButton
|
|
271
|
+
iconOnly
|
|
272
|
+
variant='ghost'
|
|
273
|
+
label={t('toggle expand label', { ns: 'os' })}
|
|
274
|
+
icon={
|
|
275
|
+
expandedField === field.id ? 'ph--caret-down--regular' : 'ph--caret-right--regular'
|
|
276
|
+
}
|
|
277
|
+
onClick={() => handleToggleField(field)}
|
|
278
|
+
/>
|
|
279
|
+
</>
|
|
280
|
+
)}
|
|
281
|
+
</div>
|
|
282
|
+
{expandedField === field.id && mode === 'schema' && (
|
|
283
|
+
<div role='none' className='col-span-5 mbs-1 mbe-1 border border-separator rounded-md'>
|
|
284
|
+
<FieldEditor
|
|
285
|
+
readonly={readonly || schemaReadonly ? 'disabled-input' : false}
|
|
286
|
+
projection={projectionModel}
|
|
287
|
+
field={field}
|
|
288
|
+
registry={registry}
|
|
289
|
+
onSave={handleClose}
|
|
290
|
+
/>
|
|
291
|
+
</div>
|
|
292
|
+
)}
|
|
293
|
+
</List.Item>
|
|
294
|
+
);
|
|
295
|
+
})}
|
|
253
296
|
</div>
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
297
|
+
</>
|
|
298
|
+
)}
|
|
299
|
+
</List.Root>
|
|
300
|
+
</div>
|
|
301
|
+
|
|
302
|
+
{!readonly && !expandedField && mode === 'schema' && (
|
|
303
|
+
<div role='none' className={outerSpacing ? cardSpacing : 'mlb-cardSpacingBlock'}>
|
|
304
|
+
<IconButton
|
|
305
|
+
icon='ph--plus--regular'
|
|
306
|
+
label={t('add property button')}
|
|
307
|
+
onClick={readonly ? undefined : handleAdd}
|
|
308
|
+
// TODO(burdon): Show field limit in ux (not tooltip).
|
|
309
|
+
disabled={view.projection.fields.length >= VIEW_FIELD_LIMIT}
|
|
310
|
+
classNames='is-full'
|
|
311
|
+
/>
|
|
312
|
+
</div>
|
|
257
313
|
)}
|
|
258
314
|
</div>
|
|
315
|
+
);
|
|
316
|
+
},
|
|
317
|
+
);
|
|
259
318
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
registry={registry}
|
|
267
|
-
onSave={handleClose}
|
|
268
|
-
/>
|
|
269
|
-
)}
|
|
319
|
+
const customFields = ({ types, tags }: Pick<ViewEditorProps, 'types' | 'tags'>): Record<string, InputComponent> => ({
|
|
320
|
+
query: (props: InputProps) => {
|
|
321
|
+
const handleChange = useCallback(
|
|
322
|
+
(query: Query.Any) => props.onValueChange('object', query.ast),
|
|
323
|
+
[props.onValueChange],
|
|
324
|
+
);
|
|
270
325
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
classNames='flex is-full'
|
|
280
|
-
/>
|
|
281
|
-
</div>
|
|
282
|
-
)}
|
|
283
|
-
</div>
|
|
284
|
-
);
|
|
285
|
-
};
|
|
326
|
+
return (
|
|
327
|
+
<Input.Root>
|
|
328
|
+
<InputHeader label={props.label} />
|
|
329
|
+
<QueryForm initialQuery={props.getValue()} types={types} tags={tags} onChange={handleChange} />
|
|
330
|
+
</Input.Root>
|
|
331
|
+
);
|
|
332
|
+
},
|
|
333
|
+
});
|
package/src/components/index.ts
CHANGED
|
@@ -9,16 +9,22 @@ import { Card } from '@dxos/react-ui-stack';
|
|
|
9
9
|
import { JsonFilter } from '@dxos/react-ui-syntax-highlighter';
|
|
10
10
|
import { mx, textBlockWidth } from '@dxos/react-ui-theme';
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
<div className='w-full h-full grid grid-cols-[1fr_1fr] gap-2 overflow-hidden'>
|
|
14
|
-
<div className={mx('p-4 justify-center overflow-y-auto', classNames)}>{children}</div>
|
|
15
|
-
<JsonFilter data={json} classNames='w-full text-xs' testId='debug' />
|
|
16
|
-
</div>
|
|
17
|
-
);
|
|
12
|
+
type TestLayoutProps = ThemedClassName<PropsWithChildren<{ json?: any }>>;
|
|
18
13
|
|
|
19
|
-
export const
|
|
20
|
-
|
|
21
|
-
|
|
14
|
+
export const TestLayout = ({ classNames, children, json }: TestLayoutProps) => {
|
|
15
|
+
return (
|
|
16
|
+
<div className='is-full bs-full grid grid-cols-[1fr_1fr] gap-2 overflow-hidden'>
|
|
17
|
+
<div className={mx('p-4 justify-center overflow-y-auto', classNames)}>{children}</div>
|
|
18
|
+
<JsonFilter data={json} classNames='is-full text-xs' testId='debug' />
|
|
19
|
+
</div>
|
|
20
|
+
);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
type TestPanelProps = ThemedClassName<PropsWithChildren>;
|
|
24
|
+
|
|
25
|
+
export const TestPanel = ({ classNames, children }: TestPanelProps) => {
|
|
26
|
+
return <Card.StaticRoot classNames={[textBlockWidth, classNames]}>{children}</Card.StaticRoot>;
|
|
27
|
+
};
|
|
22
28
|
|
|
23
29
|
// Symbol for accessing debug objects in tests.
|
|
24
30
|
export const VIEW_EDITOR_DEBUG_SYMBOL = Symbol('viewEditorDebug');
|