@dxos/react-ui-form 0.8.4-main.a4bbb77 → 0.8.4-main.abd8ff62ef

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 (233) hide show
  1. package/README.md +0 -1
  2. package/dist/lib/browser/index.mjs +2776 -2282
  3. package/dist/lib/browser/index.mjs.map +4 -4
  4. package/dist/lib/browser/meta.json +1 -1
  5. package/dist/lib/browser/translations.mjs +82 -0
  6. package/dist/lib/browser/translations.mjs.map +7 -0
  7. package/dist/lib/node-esm/index.mjs +2776 -2282
  8. package/dist/lib/node-esm/index.mjs.map +4 -4
  9. package/dist/lib/node-esm/meta.json +1 -1
  10. package/dist/lib/node-esm/translations.mjs +84 -0
  11. package/dist/lib/node-esm/translations.mjs.map +7 -0
  12. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts +8 -7
  13. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
  14. package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts +68 -60
  15. package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -1
  16. package/dist/types/src/components/Form/Form.d.ts +109 -26
  17. package/dist/types/src/components/Form/Form.d.ts.map +1 -1
  18. package/dist/types/src/components/Form/Form.stories.d.ts +115 -138
  19. package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
  20. package/dist/types/src/components/Form/FormField.d.ts +43 -0
  21. package/dist/types/src/components/Form/FormField.d.ts.map +1 -0
  22. package/dist/types/src/components/Form/FormFieldComponent.d.ts +79 -0
  23. package/dist/types/src/components/Form/FormFieldComponent.d.ts.map +1 -0
  24. package/dist/types/src/components/Form/FormFieldComponent.stories.d.ts +9 -0
  25. package/dist/types/src/components/Form/FormFieldComponent.stories.d.ts.map +1 -0
  26. package/dist/types/src/components/Form/FormFieldSet.d.ts +15 -0
  27. package/dist/types/src/components/Form/FormFieldSet.d.ts.map +1 -0
  28. package/dist/types/src/components/Form/fields/ArrayField.d.ts +18 -0
  29. package/dist/types/src/components/Form/fields/ArrayField.d.ts.map +1 -0
  30. package/dist/types/src/components/Form/fields/BooleanField.d.ts +4 -0
  31. package/dist/types/src/components/Form/fields/BooleanField.d.ts.map +1 -0
  32. package/dist/types/src/components/Form/fields/GeoPointField.d.ts +5 -0
  33. package/dist/types/src/components/Form/fields/GeoPointField.d.ts.map +1 -0
  34. package/dist/types/src/components/Form/fields/MarkdownField.d.ts +11 -0
  35. package/dist/types/src/components/Form/fields/MarkdownField.d.ts.map +1 -0
  36. package/dist/types/src/components/Form/fields/MarkdownField.stories.d.ts +84 -0
  37. package/dist/types/src/components/Form/fields/MarkdownField.stories.d.ts.map +1 -0
  38. package/dist/types/src/components/Form/fields/NumberField.d.ts +4 -0
  39. package/dist/types/src/components/Form/fields/NumberField.d.ts.map +1 -0
  40. package/dist/types/src/components/Form/fields/RefField.d.ts +22 -0
  41. package/dist/types/src/components/Form/fields/RefField.d.ts.map +1 -0
  42. package/dist/types/src/components/Form/fields/SelectField.d.ts +10 -0
  43. package/dist/types/src/components/Form/fields/SelectField.d.ts.map +1 -0
  44. package/dist/types/src/components/Form/fields/SelectOptionField.d.ts +5 -0
  45. package/dist/types/src/components/Form/fields/SelectOptionField.d.ts.map +1 -0
  46. package/dist/types/src/components/Form/fields/SelectOptionField.stories.d.ts +84 -0
  47. package/dist/types/src/components/Form/fields/SelectOptionField.stories.d.ts.map +1 -0
  48. package/dist/types/src/components/Form/fields/TextAreaField.d.ts +4 -0
  49. package/dist/types/src/components/Form/fields/TextAreaField.d.ts.map +1 -0
  50. package/dist/types/src/components/Form/fields/TextField.d.ts +4 -0
  51. package/dist/types/src/components/Form/fields/TextField.d.ts.map +1 -0
  52. package/dist/types/src/components/Form/fields/TupleField.d.ts +6 -0
  53. package/dist/types/src/components/Form/fields/TupleField.d.ts.map +1 -0
  54. package/dist/types/src/components/Form/fields/index.d.ts +12 -0
  55. package/dist/types/src/components/Form/fields/index.d.ts.map +1 -0
  56. package/dist/types/src/components/Form/index.d.ts +2 -4
  57. package/dist/types/src/components/Form/index.d.ts.map +1 -1
  58. package/dist/types/src/components/ObjectForm/ObjectForm.d.ts +9 -0
  59. package/dist/types/src/components/ObjectForm/ObjectForm.d.ts.map +1 -0
  60. package/dist/types/src/components/ObjectForm/index.d.ts +2 -0
  61. package/dist/types/src/components/ObjectForm/index.d.ts.map +1 -0
  62. package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts +68 -0
  63. package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts.map +1 -0
  64. package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts +87 -0
  65. package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts.map +1 -0
  66. package/dist/types/src/components/ObjectPicker/index.d.ts +2 -0
  67. package/dist/types/src/components/ObjectPicker/index.d.ts.map +1 -0
  68. package/dist/types/src/components/ObjectProperties/ObjectProperties.d.ts +9 -0
  69. package/dist/types/src/components/ObjectProperties/ObjectProperties.d.ts.map +1 -0
  70. package/dist/types/src/components/ObjectProperties/ObjectProperties.stories.d.ts +127 -0
  71. package/dist/types/src/components/ObjectProperties/ObjectProperties.stories.d.ts.map +1 -0
  72. package/dist/types/src/components/ObjectProperties/ObjectProperties.test.d.ts +2 -0
  73. package/dist/types/src/components/ObjectProperties/ObjectProperties.test.d.ts.map +1 -0
  74. package/dist/types/src/components/ObjectProperties/index.d.ts +2 -0
  75. package/dist/types/src/components/ObjectProperties/index.d.ts.map +1 -0
  76. package/dist/types/src/components/Settings/Settings.d.ts +51 -0
  77. package/dist/types/src/components/Settings/Settings.d.ts.map +1 -0
  78. package/dist/types/src/components/Settings/Settings.stories.d.ts +84 -0
  79. package/dist/types/src/components/Settings/Settings.stories.d.ts.map +1 -0
  80. package/dist/types/src/components/Settings/SettingsFieldSet.d.ts +43 -0
  81. package/dist/types/src/components/Settings/SettingsFieldSet.d.ts.map +1 -0
  82. package/dist/types/src/components/Settings/SettingsItem.d.ts +16 -0
  83. package/dist/types/src/components/Settings/SettingsItem.d.ts.map +1 -0
  84. package/dist/types/src/components/Settings/SettingsSelectField.d.ts +18 -0
  85. package/dist/types/src/components/Settings/SettingsSelectField.d.ts.map +1 -0
  86. package/dist/types/src/components/Settings/index.d.ts +4 -0
  87. package/dist/types/src/components/Settings/index.d.ts.map +1 -0
  88. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +17 -17
  89. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
  90. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +71 -65
  91. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
  92. package/dist/types/src/components/index.d.ts +4 -1
  93. package/dist/types/src/components/index.d.ts.map +1 -1
  94. package/dist/types/src/components/testing.d.ts +4 -6
  95. package/dist/types/src/components/testing.d.ts.map +1 -1
  96. package/dist/types/src/hooks/index.d.ts +2 -3
  97. package/dist/types/src/hooks/index.d.ts.map +1 -1
  98. package/dist/types/src/hooks/useFormHandler.d.ts +82 -0
  99. package/dist/types/src/hooks/useFormHandler.d.ts.map +1 -0
  100. package/dist/types/src/hooks/useKeyHandler.d.ts +7 -0
  101. package/dist/types/src/hooks/useKeyHandler.d.ts.map +1 -0
  102. package/dist/types/src/index.d.ts +0 -2
  103. package/dist/types/src/index.d.ts.map +1 -1
  104. package/dist/types/src/translations.d.ts +65 -60
  105. package/dist/types/src/translations.d.ts.map +1 -1
  106. package/dist/types/src/util/field-type.d.ts +19 -0
  107. package/dist/types/src/util/field-type.d.ts.map +1 -0
  108. package/dist/types/src/util/field.d.ts +3 -3
  109. package/dist/types/src/util/field.d.ts.map +1 -1
  110. package/dist/types/src/util/formatting.d.ts +2 -2
  111. package/dist/types/src/util/formatting.d.ts.map +1 -1
  112. package/dist/types/src/util/index.d.ts +2 -1
  113. package/dist/types/src/util/index.d.ts.map +1 -1
  114. package/dist/types/src/util/properties.d.ts +7 -0
  115. package/dist/types/src/util/properties.d.ts.map +1 -0
  116. package/dist/types/src/util/refs.d.ts +3 -3
  117. package/dist/types/src/util/refs.d.ts.map +1 -1
  118. package/dist/types/tsconfig.tsbuildinfo +1 -1
  119. package/package.json +56 -51
  120. package/src/components/FieldEditor/FieldEditor.stories.tsx +45 -26
  121. package/src/components/FieldEditor/FieldEditor.test.tsx +0 -1
  122. package/src/components/FieldEditor/FieldEditor.tsx +76 -79
  123. package/src/components/Form/AUDIT.md +17 -0
  124. package/src/components/Form/Form.stories.tsx +140 -252
  125. package/src/components/Form/Form.tsx +376 -99
  126. package/src/components/Form/FormField.tsx +306 -0
  127. package/src/components/Form/FormFieldComponent.stories.tsx +65 -0
  128. package/src/components/Form/FormFieldComponent.tsx +174 -0
  129. package/src/components/Form/FormFieldSet.tsx +144 -0
  130. package/src/components/Form/README.md +3 -0
  131. package/src/components/Form/fields/ArrayField.tsx +159 -0
  132. package/src/components/Form/fields/BooleanField.tsx +22 -0
  133. package/src/components/Form/fields/GeoPointField.tsx +121 -0
  134. package/src/components/Form/fields/MarkdownField.stories.tsx +206 -0
  135. package/src/components/Form/fields/MarkdownField.tsx +168 -0
  136. package/src/components/Form/fields/NumberField.tsx +39 -0
  137. package/src/components/Form/fields/RefField.tsx +229 -0
  138. package/src/components/Form/fields/SelectField.tsx +65 -0
  139. package/src/components/Form/{custom/SelectOptionsInput.stories.tsx → fields/SelectOptionField.stories.tsx} +24 -19
  140. package/src/components/Form/{custom/SelectOptionsInput.tsx → fields/SelectOptionField.tsx} +30 -24
  141. package/src/components/Form/fields/TextAreaField.tsx +38 -0
  142. package/src/components/Form/fields/TextField.tsx +38 -0
  143. package/src/components/Form/{custom/TupleInput.tsx → fields/TupleField.tsx} +16 -9
  144. package/src/components/Form/fields/index.ts +15 -0
  145. package/src/components/Form/index.ts +3 -4
  146. package/src/components/ObjectForm/ObjectForm.tsx +114 -0
  147. package/src/components/{ControlSection → ObjectForm}/index.ts +1 -1
  148. package/src/components/ObjectPicker/ObjectPicker.stories.tsx +212 -0
  149. package/src/components/ObjectPicker/ObjectPicker.tsx +202 -0
  150. package/src/components/ObjectPicker/index.ts +5 -0
  151. package/src/components/ObjectProperties/ObjectProperties.stories.tsx +362 -0
  152. package/src/components/ObjectProperties/ObjectProperties.test.tsx +101 -0
  153. package/src/components/ObjectProperties/ObjectProperties.tsx +141 -0
  154. package/src/components/ObjectProperties/index.ts +5 -0
  155. package/src/components/Settings/AUDIT.md +52 -0
  156. package/src/components/Settings/Settings.stories.tsx +104 -0
  157. package/src/components/Settings/Settings.tsx +82 -0
  158. package/src/components/Settings/SettingsFieldSet.tsx +257 -0
  159. package/src/components/Settings/SettingsItem.tsx +62 -0
  160. package/src/components/Settings/SettingsSelectField.tsx +59 -0
  161. package/src/components/Settings/index.ts +7 -0
  162. package/src/components/ViewEditor/ViewEditor.stories.tsx +75 -55
  163. package/src/components/ViewEditor/ViewEditor.test.tsx +9 -8
  164. package/src/components/ViewEditor/ViewEditor.tsx +320 -278
  165. package/src/components/index.ts +4 -1
  166. package/src/components/testing.tsx +24 -11
  167. package/src/hooks/index.ts +2 -3
  168. package/src/hooks/useFormHandler.ts +380 -0
  169. package/src/hooks/useKeyHandler.tsx +37 -0
  170. package/src/index.ts +0 -4
  171. package/src/translations.ts +70 -65
  172. package/src/util/field-type.ts +60 -0
  173. package/src/util/field.ts +25 -25
  174. package/src/util/formatting.ts +22 -22
  175. package/src/util/index.ts +2 -1
  176. package/src/util/properties.ts +18 -0
  177. package/src/util/refs.ts +17 -19
  178. package/dist/types/src/components/ControlSection/ControlSection.d.ts +0 -23
  179. package/dist/types/src/components/ControlSection/ControlSection.d.ts.map +0 -1
  180. package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts +0 -84
  181. package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts.map +0 -1
  182. package/dist/types/src/components/ControlSection/index.d.ts +0 -2
  183. package/dist/types/src/components/ControlSection/index.d.ts.map +0 -1
  184. package/dist/types/src/components/Form/ArrayField.d.ts +0 -16
  185. package/dist/types/src/components/Form/ArrayField.d.ts.map +0 -1
  186. package/dist/types/src/components/Form/Defaults.d.ts +0 -14
  187. package/dist/types/src/components/Form/Defaults.d.ts.map +0 -1
  188. package/dist/types/src/components/Form/FormActions.d.ts +0 -9
  189. package/dist/types/src/components/Form/FormActions.d.ts.map +0 -1
  190. package/dist/types/src/components/Form/FormContent.d.ts +0 -63
  191. package/dist/types/src/components/Form/FormContent.d.ts.map +0 -1
  192. package/dist/types/src/components/Form/FormContext.d.ts +0 -23
  193. package/dist/types/src/components/Form/FormContext.d.ts.map +0 -1
  194. package/dist/types/src/components/Form/Input.d.ts +0 -29
  195. package/dist/types/src/components/Form/Input.d.ts.map +0 -1
  196. package/dist/types/src/components/Form/RefField.d.ts +0 -20
  197. package/dist/types/src/components/Form/RefField.d.ts.map +0 -1
  198. package/dist/types/src/components/Form/custom/GeoPointInput.d.ts +0 -4
  199. package/dist/types/src/components/Form/custom/GeoPointInput.d.ts.map +0 -1
  200. package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts +0 -4
  201. package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts.map +0 -1
  202. package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts +0 -79
  203. package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts.map +0 -1
  204. package/dist/types/src/components/Form/custom/TupleInput.d.ts +0 -6
  205. package/dist/types/src/components/Form/custom/TupleInput.d.ts.map +0 -1
  206. package/dist/types/src/components/Form/custom/index.d.ts +0 -4
  207. package/dist/types/src/components/Form/custom/index.d.ts.map +0 -1
  208. package/dist/types/src/components/Form/factory.d.ts +0 -8
  209. package/dist/types/src/components/Form/factory.d.ts.map +0 -1
  210. package/dist/types/src/hooks/useForm.d.ts +0 -67
  211. package/dist/types/src/hooks/useForm.d.ts.map +0 -1
  212. package/dist/types/src/hooks/useQueryRefOptions.d.ts +0 -28
  213. package/dist/types/src/hooks/useQueryRefOptions.d.ts.map +0 -1
  214. package/dist/types/src/hooks/useRefQueryLookupHandler.d.ts +0 -8
  215. package/dist/types/src/hooks/useRefQueryLookupHandler.d.ts.map +0 -1
  216. package/dist/types/src/util/schema.d.ts +0 -3
  217. package/dist/types/src/util/schema.d.ts.map +0 -1
  218. package/src/components/ControlSection/ControlSection.stories.tsx +0 -38
  219. package/src/components/ControlSection/ControlSection.tsx +0 -153
  220. package/src/components/Form/ArrayField.tsx +0 -124
  221. package/src/components/Form/Defaults.tsx +0 -205
  222. package/src/components/Form/FormActions.tsx +0 -54
  223. package/src/components/Form/FormContent.tsx +0 -320
  224. package/src/components/Form/FormContext.tsx +0 -110
  225. package/src/components/Form/Input.tsx +0 -54
  226. package/src/components/Form/RefField.tsx +0 -363
  227. package/src/components/Form/custom/GeoPointInput.tsx +0 -80
  228. package/src/components/Form/custom/index.ts +0 -7
  229. package/src/components/Form/factory.tsx +0 -31
  230. package/src/hooks/useForm.ts +0 -294
  231. package/src/hooks/useQueryRefOptions.ts +0 -59
  232. package/src/hooks/useRefQueryLookupHandler.ts +0 -39
  233. package/src/util/schema.ts +0 -14
@@ -2,54 +2,53 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { Array, Match, Option, Schema } from 'effect';
6
- import React, { forwardRef, useCallback, useImperativeHandle, useMemo, useState } from 'react';
7
-
8
- import { type SchemaRegistry } from '@dxos/echo-db';
9
- import { EchoSchema, Format, type JsonProp, isMutable, toJsonSchema } from '@dxos/echo-schema';
5
+ import { RegistryContext } from '@effect-atom/atom-react';
6
+ import * as Array from 'effect/Array';
7
+ import * as Match from 'effect/Match';
8
+ import * as Option from 'effect/Option';
9
+ import * as Schema from 'effect/Schema';
10
+ import React, { forwardRef, useCallback, useContext, useImperativeHandle, useMemo, useState } from 'react';
11
+
12
+ import { Entity, Feed, Filter, Format, Obj, Query, QueryAST, Ref, type SchemaRegistry } from '@dxos/echo';
13
+ import { View } from '@dxos/echo';
14
+ import { EchoSchema, type JsonProp, isMutable, toJsonSchema } from '@dxos/echo/internal';
10
15
  import { invariant } from '@dxos/invariant';
11
- import {
12
- Callout,
13
- IconButton,
14
- Input,
15
- type ThemedClassName,
16
- useDensityContext,
17
- useElevationContext,
18
- useThemeContext,
19
- useTranslation,
20
- } from '@dxos/react-ui';
21
- import { Editor, createBasicExtensions, createThemeExtensions } from '@dxos/react-ui-editor';
16
+ import { useObject, useQuery } from '@dxos/react-client/echo';
17
+ import { IconButton, Input, Message, type ThemedClassName, useTranslation } from '@dxos/react-ui';
18
+ import { QueryForm, type QueryFormProps } from '@dxos/react-ui-components';
22
19
  import { List } from '@dxos/react-ui-list';
23
- import { cardSpacing } from '@dxos/react-ui-stack';
24
- import { inputTheme } from '@dxos/react-ui-theme';
25
- import { inputTextLabel, mx, subtleHover } from '@dxos/react-ui-theme';
26
20
  import {
27
- type DataType,
28
- FieldSchema,
29
- type FieldType,
21
+ ParentLabelAnnotation,
30
22
  ProjectionModel,
31
23
  VIEW_FIELD_LIMIT,
32
- typenameFromQuery,
24
+ createEchoChangeCallback,
25
+ getTypenameFromQuery,
33
26
  } from '@dxos/schema';
27
+ import { mx, osTranslations, subtleHover } from '@dxos/ui-theme';
34
28
 
35
- import { translationKey } from '../../translations';
36
- import { FieldEditor } from '../FieldEditor';
37
- import { Form, type FormProps, type InputComponent, InputHeader, type InputProps } from '../Form';
29
+ import { translationKey } from '#translations';
38
30
 
39
- const listGrid = 'grid grid-cols-[min-content_1fr_min-content_min-content_min-content]';
40
- const listItemGrid = 'grid grid-cols-subgrid col-span-5';
31
+ import { FieldEditor } from '../FieldEditor';
32
+ import {
33
+ Form,
34
+ type FormFieldComponent,
35
+ type FormFieldComponentProps,
36
+ FormFieldLabel,
37
+ type FormFieldMap,
38
+ type FormRootProps,
39
+ } from '../Form';
41
40
 
42
41
  export type ViewEditorProps = ThemedClassName<
43
42
  {
44
43
  schema: Schema.Schema.AnyNoContext;
45
- view: DataType.View;
46
- mode?: 'schema' | 'query';
47
- registry?: SchemaRegistry;
48
- readonly?: boolean;
44
+ view: View.View;
45
+ mode?: 'schema' | 'tag';
46
+ registry?: SchemaRegistry.SchemaRegistry;
49
47
  showHeading?: boolean;
50
- onQueryChanged?: (query: string, target?: string) => void;
48
+ onQueryChanged?: (query: QueryAST.Query, target?: string) => void;
51
49
  onDelete?: (fieldId: string) => void;
52
- } & Pick<FormProps<any>, 'outerSpacing'>
50
+ } & Pick<QueryFormProps, 'types' | 'tags'> &
51
+ Pick<FormRootProps<any>, 'readonly' | 'db'>
53
52
  >;
54
53
 
55
54
  /**
@@ -63,317 +62,360 @@ export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
63
62
  view,
64
63
  mode = 'schema',
65
64
  registry,
65
+ db,
66
66
  readonly,
67
67
  showHeading = false,
68
+ types,
69
+ tags,
68
70
  onQueryChanged,
69
71
  onDelete,
70
- outerSpacing = true,
71
72
  },
72
73
  forwardedRef,
73
74
  ) => {
75
+ const atomRegistry = useContext(RegistryContext);
74
76
  const schemaReadonly = !isMutable(schema);
75
77
  const { t } = useTranslation(translationKey);
78
+
76
79
  const projectionModel = useMemo(() => {
77
80
  // Use reactive and mutable version of json schema when schema is mutable.
78
81
  const jsonSchema = schema instanceof EchoSchema ? schema.jsonSchema : toJsonSchema(schema);
79
- return new ProjectionModel(jsonSchema, view.projection);
80
- }, [schema, JSON.stringify(view.projection)]);
82
+
83
+ // Always use createEchoChangeCallback since the view is ECHO-backed.
84
+ // Pass schema only when mutable to allow schema mutations.
85
+ const change = createEchoChangeCallback(view, schema instanceof EchoSchema ? schema : undefined);
86
+
87
+ const model = new ProjectionModel({
88
+ registry: atomRegistry,
89
+ view,
90
+ baseSchema: jsonSchema,
91
+ change,
92
+ });
93
+
94
+ return model;
95
+ }, [atomRegistry, schema, view]);
96
+
81
97
  useImperativeHandle(forwardedRef, () => projectionModel, [projectionModel]);
82
- const [expandedField, setExpandedField] = useState<FieldType['id']>();
83
-
84
- const serializedQuery = Match.value(mode).pipe(
85
- Match.when('schema', () => typenameFromQuery(view.query.ast)),
86
- Match.when('query', () => {
87
- if (view.query.string) {
88
- return view.query.string;
89
- } else {
90
- return 'Serializing query AST is not currently supported.';
91
- }
92
- }),
93
- Match.exhaustive,
94
- );
95
98
 
96
99
  const queueTarget = Match.value(view.query.ast).pipe(
97
- Match.when({ type: 'options' }, ({ options }) => {
98
- return Option.fromNullable(options.queues).pipe(
100
+ Match.when({ type: 'from' }, ({ from }) => {
101
+ if (from._tag !== 'scope') {
102
+ return undefined;
103
+ }
104
+ return Option.fromNullable(from.scope.queues).pipe(
99
105
  Option.flatMap((queues) => Array.head(queues)),
106
+ Option.map(String),
100
107
  Option.getOrUndefined,
101
108
  );
102
109
  }),
103
110
  Match.orElse(() => undefined),
104
111
  );
105
112
 
113
+ const feeds = useQuery(db, Filter.type(Feed.Feed));
114
+
115
+ const targetRef = useMemo(() => {
116
+ if (!queueTarget) {
117
+ return undefined;
118
+ }
119
+ const feed = feeds.find((feed) => Feed.getQueueDxn(feed)?.toString() === queueTarget);
120
+ return feed ? Ref.fromDXN(Entity.getDXN(feed)) : undefined;
121
+ }, [queueTarget, feeds]);
122
+
106
123
  const viewSchema = useMemo(() => {
107
124
  const base = Schema.Struct({
108
125
  query:
109
126
  mode === 'schema'
110
127
  ? Format.URL.annotations({ title: 'Record type' })
111
- : Schema.String.annotations({ title: 'Query' }),
128
+ : QueryAST.Query.annotations({ title: 'Query' }),
112
129
  });
113
130
 
114
- if (mode === 'query') {
131
+ if (mode === 'tag') {
115
132
  return Schema.Struct({
116
- name: Schema.optional(Schema.String.annotations({ title: 'Name' })),
117
133
  ...base.fields,
118
- target: Schema.optional(Schema.String.annotations({ title: 'Target Queue' })),
134
+ // TODO(wittjosiah): Replace Type.Feed with Dataset.Dataset when Ref.Ref supports unions.
135
+ target: Ref.Ref(Feed.Feed).pipe(
136
+ Schema.annotations({ title: 'Target Feed' }),
137
+ ParentLabelAnnotation.set(true),
138
+ Schema.optional,
139
+ ),
119
140
  }).pipe(Schema.mutable);
120
141
  }
121
142
 
122
143
  return base.pipe(Schema.mutable);
123
144
  }, [mode]);
145
+
124
146
  // TODO(burdon): Need to warn user of possible consequences of editing.
125
147
  // TODO(burdon): Settings should have domain name owned by user.
126
148
  const viewValues = useMemo(
127
- () => ({ name: view.name, query: serializedQuery, target: queueTarget }),
128
- [view.name, serializedQuery, queueTarget],
149
+ () => ({
150
+ query: mode === 'schema' ? getTypenameFromQuery(view.query.ast) : view.query.ast,
151
+ target: targetRef,
152
+ }),
153
+ [mode, view.query.ast, targetRef],
129
154
  );
130
155
 
131
- const handleToggleField = useCallback(
132
- (field: FieldType) => {
133
- setExpandedField((prevExpandedFieldId) => (prevExpandedFieldId === field.id ? undefined : field.id));
134
- },
135
- [readonly],
156
+ const fieldMap = useMemo<FormFieldMap | undefined>(
157
+ () => (mode === 'tag' ? customFields({ types, tags }) : undefined),
158
+ [mode, types, tags],
136
159
  );
137
160
 
138
- // TODO(burdon): Check if mutable; variant of useCallback that return undefined if readonly?
139
-
140
- const handleAdd = useCallback(() => {
141
- invariant(!readonly);
142
- const field = projectionModel.createFieldProjection();
143
- setExpandedField(field.id);
144
- }, [schema, projectionModel, readonly]);
145
-
146
161
  const handleUpdate = useCallback(
147
- (values: Schema.Schema.Type<typeof viewSchema>) => {
148
- invariant(!readonly);
149
- requestAnimationFrame(() => {
150
- if ('name' in values && view.name !== values.name) {
151
- view.name = values.name;
162
+ (values: any) => {
163
+ const targetValue = values.target;
164
+ let queueDxn: string | undefined;
165
+
166
+ if (Ref.isRef(targetValue)) {
167
+ const feedDxn = targetValue.dxn.toString();
168
+ const feed = feeds.find((feed) => Obj.getDXN(feed).toString() === feedDxn);
169
+ if (feed) {
170
+ queueDxn = Feed.getQueueDxn(feed)?.toString();
152
171
  }
172
+ }
153
173
 
154
- const queryChanged = serializedQuery !== values.query;
155
- const targetValue = 'target' in values ? values.target : undefined;
156
- const targetChanged = 'target' in values && values.target && queueTarget !== targetValue;
157
-
158
- if ((queryChanged || targetChanged) && !readonly) {
159
- onQueryChanged?.(values.query, targetValue);
160
- }
161
- });
174
+ // TODO(wittjosiah): Deep-clone the AST to plain JS or ECHO proxy arrays become objects with numeric keys.
175
+ const query =
176
+ mode === 'schema'
177
+ ? Query.select(Filter.typename(values.query)).ast
178
+ : JSON.parse(JSON.stringify(values.query));
179
+ onQueryChanged?.(query, queueDxn);
162
180
  },
163
- [serializedQuery, onQueryChanged, readonly, view, queueTarget],
181
+ [onQueryChanged, mode, feeds],
164
182
  );
165
183
 
166
184
  const handleDelete = useCallback(
167
185
  (fieldId: string) => {
168
186
  invariant(!readonly);
169
- if (fieldId === expandedField) {
170
- setExpandedField(undefined);
171
- }
172
-
173
187
  onDelete?.(fieldId);
174
188
  },
175
- [expandedField, onDelete, readonly],
189
+ [onDelete, readonly],
176
190
  );
177
191
 
178
- const handleMove = useCallback(
179
- (fromIndex: number, toIndex: number) => {
180
- invariant(!readonly);
192
+ return (
193
+ <div role='none' className={mx(classNames)}>
194
+ {/* If readonly is set, then the callout is not needed. */}
195
+ {schemaReadonly && !readonly && (
196
+ <Message.Root valence='info' classNames='my-form-padding'>
197
+ <Message.Title>{t('system-schema.description')}</Message.Title>
198
+ </Message.Root>
199
+ )}
200
+
201
+ {/* TODO(burdon): Is the form read-only or just the schema? */}
202
+ <Form.Root schema={viewSchema} values={viewValues} fieldMap={fieldMap} db={db} onValuesChanged={handleUpdate}>
203
+ <Form.FieldSet />
204
+
205
+ <FormFieldLabel label={t('fields.label')} asChild />
206
+ <FieldList
207
+ schema={schema}
208
+ view={view}
209
+ registry={registry}
210
+ readonly={readonly}
211
+ showHeading={showHeading}
212
+ onDelete={handleDelete}
213
+ />
214
+ </Form.Root>
215
+ </div>
216
+ );
217
+ },
218
+ );
219
+
220
+ type FieldListProps = Pick<ViewEditorProps, 'schema' | 'view' | 'registry' | 'readonly' | 'showHeading' | 'onDelete'>;
221
+
222
+ const FieldList = ({ schema, view, registry, readonly, showHeading = false, onDelete }: FieldListProps) => {
223
+ const atomRegistry = useContext(RegistryContext);
224
+ const schemaReadonly = !isMutable(schema);
225
+ const { t } = useTranslation(translationKey);
226
+
227
+ // Subscribe to view changes for reactivity.
228
+ const [viewSnapshot] = useObject(view);
229
+
230
+ const projectionModel = useMemo(() => {
231
+ // Use reactive and mutable version of json schema when schema is mutable.
232
+ const jsonSchema = schema instanceof EchoSchema ? schema.jsonSchema : toJsonSchema(schema);
233
+
234
+ // Always use createEchoChangeCallback since the view is ECHO-backed.
235
+ // Pass schema only when mutable to allow schema mutations.
236
+ const change = createEchoChangeCallback(view, schema instanceof EchoSchema ? schema : undefined);
237
+
238
+ const model = new ProjectionModel({
239
+ registry: atomRegistry,
240
+ view,
241
+ baseSchema: jsonSchema,
242
+ change,
243
+ });
244
+
245
+ return model;
246
+ }, [atomRegistry, schema, view]);
247
+
248
+ const [expandedField, setExpandedField] = useState<View.FieldType['id']>();
249
+
250
+ const handleAdd = useCallback(() => {
251
+ invariant(!readonly);
252
+ const field = projectionModel.createFieldProjection();
253
+ setExpandedField(field.id);
254
+ }, [projectionModel, readonly]);
255
+
256
+ const handleToggleField = useCallback(
257
+ (field: View.FieldType) => {
258
+ setExpandedField((prevExpandedFieldId) => (prevExpandedFieldId === field.id ? undefined : field.id));
259
+ },
260
+ [readonly],
261
+ );
262
+
263
+ const handleDelete = useCallback(
264
+ (fieldId: string) => {
265
+ invariant(!readonly);
266
+ if (fieldId === expandedField) {
267
+ setExpandedField(undefined);
268
+ }
269
+
270
+ onDelete?.(fieldId);
271
+ },
272
+ [expandedField, onDelete, readonly],
273
+ );
274
+
275
+ const handleMove = useCallback(
276
+ (fromIndex: number, toIndex: number) => {
277
+ invariant(!readonly);
278
+ Obj.update(view, (view) => {
181
279
  // NOTE(ZaymonFC): Using arrayMove here causes a race condition with the kanban model.
182
280
  const fields = [...view.projection.fields];
183
281
  const [moved] = fields.splice(fromIndex, 1);
184
282
  fields.splice(toIndex, 0, moved);
185
283
  view.projection.fields = fields;
186
- },
187
- [view.projection.fields, readonly],
188
- );
189
-
190
- const handleClose = useCallback(() => setExpandedField(undefined), []);
284
+ });
285
+ },
286
+ [view, readonly],
287
+ );
191
288
 
192
- const handleHide = useCallback(
193
- (fieldId: string) => {
194
- setExpandedField(undefined);
195
- projectionModel.hideFieldProjection(fieldId);
196
- },
197
- [projectionModel],
198
- );
289
+ const handleClose = useCallback(() => setExpandedField(undefined), []);
199
290
 
200
- const handleShow = useCallback(
201
- (property: string) => {
202
- setExpandedField(undefined);
203
- projectionModel.showFieldProjection(property as JsonProp);
204
- },
205
- [projectionModel],
206
- );
291
+ const handleHide = useCallback(
292
+ (fieldId: string) => {
293
+ setExpandedField(undefined);
294
+ projectionModel.hideFieldProjection(fieldId);
295
+ },
296
+ [projectionModel],
297
+ );
207
298
 
208
- const custom: Partial<Record<string, InputComponent>> = useMemo(
209
- () =>
210
- mode === 'query'
211
- ? {
212
- ['query' satisfies keyof Schema.Schema.Type<typeof viewSchema>]: (props: InputProps) => {
213
- const { themeMode } = useThemeContext();
214
- const density = useDensityContext();
215
- const elevation = useElevationContext();
216
-
217
- // TODO(wittjosiah): Including props.onValueChange in deps causes infinite loop.
218
- const handleChange = useCallback((text: string) => props.onValueChange('string', text), []);
219
-
220
- const extensions = useMemo(
221
- () => [
222
- createBasicExtensions({ placeholder: t('query placeholder') }),
223
- createThemeExtensions({ themeMode }),
224
- ],
225
- [],
226
- );
227
-
228
- // TODO(wittjosiah): This is probably not the right way to do these styles.
229
- return (
230
- <Input.Root>
231
- <InputHeader label={props.label} />
232
- <Editor
233
- classNames={mx(
234
- inputTheme.input({ density, elevation }),
235
- 'flex items-center',
236
- 'focus-within:bg-focusSurface focus-within:border-separator focus-within:hover:bg-focusSurface',
237
- )}
238
- extensions={extensions}
239
- value={props.getValue()}
240
- onChange={handleChange}
299
+ const handleShow = useCallback(
300
+ (property: string) => {
301
+ setExpandedField(undefined);
302
+ projectionModel.showFieldProjection(property as JsonProp);
303
+ },
304
+ [projectionModel],
305
+ );
306
+
307
+ if (!viewSnapshot) {
308
+ return null;
309
+ }
310
+
311
+ return (
312
+ <List.Root<View.FieldType>
313
+ items={viewSnapshot.projection.fields as View.FieldType[]}
314
+ isItem={Schema.is(View.FieldSchema)}
315
+ getId={(field) => field.id}
316
+ onMove={readonly ? undefined : handleMove}
317
+ readonly={readonly}
318
+ >
319
+ {({ items: fields }) => (
320
+ <>
321
+ {showHeading && <h3 className='text-sm'>{t('field-path.label')}</h3>}
322
+ <div role='list' className='grid grid-cols-[min-content_1fr_min-content_min-content_min-content]'>
323
+ {fields?.map((field) => {
324
+ const hidden = field.visible === false;
325
+ return (
326
+ <List.Item<View.FieldType>
327
+ key={field.id}
328
+ item={field}
329
+ classNames={'grid grid-cols-subgrid col-span-5'}
330
+ aria-expanded={expandedField === field.id}
331
+ >
332
+ <div
333
+ role='none'
334
+ className={mx(
335
+ subtleHover,
336
+ 'grid grid-cols-subgrid col-span-5',
337
+ 'rounded-xs cursor-pointer min-h-10',
338
+ )}
339
+ >
340
+ <List.ItemDragHandle disabled={readonly || schemaReadonly} />
341
+ <List.ItemTitle classNames={hidden && 'text-subdued'} onClick={() => handleToggleField(field)}>
342
+ {field.path}
343
+ </List.ItemTitle>
344
+ <List.ItemIconButton
345
+ label={t(hidden ? 'show-field.label' : 'hide-field.label')}
346
+ data-testid={hidden ? 'show-field-button' : 'hide-field-button'}
347
+ icon={hidden ? 'ph--eye-closed--regular' : 'ph--eye--regular'}
348
+ autoHide={false}
349
+ disabled={readonly || (!hidden && projectionModel.getFields().length <= 1)}
350
+ onClick={() => (hidden ? handleShow(field.path) : handleHide(field.id))}
241
351
  />
242
- {/* TODO(wittjosiah): Support query editor.
243
- <QueryEditor
244
- classNames={mx(
245
- inputTheme.input({ density, elevation }),
246
- 'flex items-center',
247
- 'focus-within:bg-focusSurface focus-within:border-separator focus-within:hover:bg-focusSurface',
248
- )}
249
- space={space}
250
- value={props.getValue()}
251
- onChange={handleChange}
252
- /> */}
253
- </Input.Root>
254
- );
255
- },
256
- }
257
- : {},
258
- [],
352
+ {!readonly && (
353
+ <>
354
+ <List.ItemDeleteButton
355
+ label={t('delete-field.label')}
356
+ autoHide={false}
357
+ disabled={readonly || schemaReadonly || viewSnapshot.projection.fields.length <= 1}
358
+ onClick={() => handleDelete(field.id)}
359
+ data-testid='field.delete'
360
+ />
361
+ <List.ItemIconButton
362
+ iconOnly
363
+ variant='ghost'
364
+ label={t('toggle-expand.label', { ns: osTranslations })}
365
+ icon={expandedField === field.id ? 'ph--caret-down--regular' : 'ph--caret-right--regular'}
366
+ onClick={() => handleToggleField(field)}
367
+ data-testid='field.toggle'
368
+ />
369
+ </>
370
+ )}
371
+ </div>
372
+ {expandedField === field.id && !readonly && (
373
+ <div role='none' className='col-span-full mt-1 mb-1 border border-separator rounded-md'>
374
+ <FieldEditor
375
+ readonly={readonly || schemaReadonly}
376
+ registry={registry}
377
+ projection={projectionModel}
378
+ field={field}
379
+ onSave={handleClose}
380
+ />
381
+ </div>
382
+ )}
383
+ </List.Item>
384
+ );
385
+ })}
386
+ </div>
387
+ {!readonly && !expandedField && (
388
+ <div role='none' className='my-form-padding'>
389
+ <IconButton
390
+ icon='ph--plus--regular'
391
+ label={t('add-property-button.label')}
392
+ onClick={handleAdd}
393
+ disabled={viewSnapshot.projection.fields.length >= VIEW_FIELD_LIMIT}
394
+ classNames='w-full'
395
+ />
396
+ </div>
397
+ )}
398
+ </>
399
+ )}
400
+ </List.Root>
401
+ );
402
+ };
403
+
404
+ const customFields = ({
405
+ types,
406
+ tags,
407
+ }: Pick<ViewEditorProps, 'types' | 'tags'>): Record<string, FormFieldComponent> => ({
408
+ query: ({ type, readonly, label, getValue, onValueChange }: FormFieldComponentProps) => {
409
+ const handleChange = useCallback<NonNullable<QueryFormProps['onChange']>>(
410
+ (query) => onValueChange(type, query.ast),
411
+ [onValueChange],
259
412
  );
260
413
 
261
414
  return (
262
- <div role='none' className={mx(classNames)}>
263
- {schemaReadonly && mode === 'schema' && (
264
- <Callout.Root valence='info' classNames={['mlb-cardSpacingBlock', outerSpacing && 'mli-cardSpacingInline']}>
265
- <Callout.Title>{t('system schema description')}</Callout.Title>
266
- </Callout.Root>
267
- )}
268
-
269
- {/* TODO(burdon): Is the form read-only or just the schema? */}
270
- {/* TODO(burdon): Readonly fields should take up the same space as editable fields (just be ghosted). */}
271
- <Form<Schema.Schema.Type<typeof viewSchema>>
272
- autoSave
273
- schema={viewSchema}
274
- values={viewValues}
275
- readonly={readonly ? 'disabled-input' : false}
276
- onSave={handleUpdate}
277
- outerSpacing={outerSpacing}
278
- Custom={custom}
279
- />
280
-
281
- <div role='none' className={outerSpacing ? cardSpacing : 'mlb-cardSpacingBlock'}>
282
- <h2 className={mx(inputTextLabel)}>{t('fields label')}</h2>
283
-
284
- <List.Root<FieldType>
285
- items={view.projection.fields}
286
- isItem={Schema.is(FieldSchema)}
287
- getId={(field) => field.id}
288
- onMove={readonly ? undefined : handleMove}
289
- readonly={readonly}
290
- >
291
- {({ items: fields }) => (
292
- <>
293
- {showHeading && <h3 className='text-sm'>{t('field path label')}</h3>}
294
- <div role='list' className={listGrid}>
295
- {fields?.map((field) => {
296
- const hidden = field.visible === false;
297
- return (
298
- <List.Item<FieldType>
299
- key={field.id}
300
- item={field}
301
- classNames={listItemGrid}
302
- aria-expanded={expandedField === field.id}
303
- >
304
- <div
305
- role='none'
306
- className={mx(subtleHover, listItemGrid, 'rounded-sm cursor-pointer min-bs-10')}
307
- >
308
- <List.ItemDragHandle disabled={readonly} />
309
- <List.ItemTitle
310
- classNames={hidden && 'text-subdued'}
311
- onClick={() => handleToggleField(field)}
312
- >
313
- {field.path}
314
- </List.ItemTitle>
315
- <List.ItemButton
316
- label={t(hidden ? 'show field label' : 'hide field label')}
317
- data-testid={hidden ? 'show-field-button' : 'hide-field-button'}
318
- icon={hidden ? 'ph--eye-closed--regular' : 'ph--eye--regular'}
319
- autoHide={false}
320
- disabled={readonly || (!hidden && projectionModel.fields.length <= 1)}
321
- onClick={() => (hidden ? handleShow(field.path) : handleHide(field.id))}
322
- />
323
- {mode === 'schema' && (
324
- <>
325
- <List.ItemDeleteButton
326
- label={t('delete field label')}
327
- autoHide={false}
328
- disabled={readonly || schemaReadonly || view.projection.fields.length <= 1}
329
- onClick={() => handleDelete(field.id)}
330
- data-testid='field.delete'
331
- />
332
- <IconButton
333
- iconOnly
334
- variant='ghost'
335
- label={t('toggle expand label', { ns: 'os' })}
336
- icon={
337
- expandedField === field.id ? 'ph--caret-down--regular' : 'ph--caret-right--regular'
338
- }
339
- onClick={() => handleToggleField(field)}
340
- />
341
- </>
342
- )}
343
- </div>
344
- {expandedField === field.id && mode === 'schema' && (
345
- <div role='none' className='col-span-5 mbs-1 mbe-1 border border-separator rounded-md'>
346
- <FieldEditor
347
- readonly={readonly || schemaReadonly ? 'disabled-input' : false}
348
- projection={projectionModel}
349
- field={field}
350
- registry={registry}
351
- onSave={handleClose}
352
- />
353
- </div>
354
- )}
355
- </List.Item>
356
- );
357
- })}
358
- </div>
359
- </>
360
- )}
361
- </List.Root>
362
- </div>
363
-
364
- {!readonly && !expandedField && mode === 'schema' && (
365
- <div role='none' className={outerSpacing ? cardSpacing : 'mlb-cardSpacingBlock'}>
366
- <IconButton
367
- icon='ph--plus--regular'
368
- label={t('button add property')}
369
- onClick={readonly ? undefined : handleAdd}
370
- // TODO(burdon): Show field limit in ux (not tooltip).
371
- disabled={view.projection.fields.length >= VIEW_FIELD_LIMIT}
372
- classNames='is-full'
373
- />
374
- </div>
375
- )}
376
- </div>
415
+ <Input.Root>
416
+ <FormFieldLabel readonly={readonly} label={label} />
417
+ <QueryForm initialQuery={getValue()} types={types} tags={tags} onChange={handleChange} />
418
+ </Input.Root>
377
419
  );
378
420
  },
379
- );
421
+ });