@nixxie-cms/core 1.0.3 → 2.0.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.
Files changed (203) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/CHANGES-1.1.md +134 -0
  3. package/context/dist/nixxie-cms-core-context.cjs.js +4 -3
  4. package/context/dist/nixxie-cms-core-context.esm.js +3 -2
  5. package/dist/declarations/src/access.d.ts +2 -2
  6. package/dist/declarations/src/access.d.ts.map +1 -1
  7. package/dist/declarations/src/admin-ui/components/Navigation.d.ts +2 -2
  8. package/dist/declarations/src/admin-ui/components/Navigation.d.ts.map +1 -1
  9. package/dist/declarations/src/admin-ui/context.d.ts +6 -6
  10. package/dist/declarations/src/admin-ui/context.d.ts.map +1 -1
  11. package/dist/declarations/src/admin-ui/utils/Fields.d.ts +3 -3
  12. package/dist/declarations/src/admin-ui/utils/Fields.d.ts.map +1 -1
  13. package/dist/declarations/src/admin-ui/utils/filters.d.ts +5 -5
  14. package/dist/declarations/src/admin-ui/utils/filters.d.ts.map +1 -1
  15. package/dist/declarations/src/admin-ui/utils/useCreateItem.d.ts +3 -3
  16. package/dist/declarations/src/admin-ui/utils/useCreateItem.d.ts.map +1 -1
  17. package/dist/declarations/src/admin-ui/utils/utils.d.ts +2 -2
  18. package/dist/declarations/src/admin-ui/utils/utils.d.ts.map +1 -1
  19. package/dist/declarations/src/context.d.ts +1 -1
  20. package/dist/declarations/src/context.d.ts.map +1 -1
  21. package/dist/declarations/src/fields/types/bigInt/index.d.ts +3 -3
  22. package/dist/declarations/src/fields/types/bigInt/index.d.ts.map +1 -1
  23. package/dist/declarations/src/fields/types/bytes/index.d.ts +3 -3
  24. package/dist/declarations/src/fields/types/bytes/index.d.ts.map +1 -1
  25. package/dist/declarations/src/fields/types/calendarDay/index.d.ts +3 -3
  26. package/dist/declarations/src/fields/types/calendarDay/index.d.ts.map +1 -1
  27. package/dist/declarations/src/fields/types/checkbox/index.d.ts +3 -3
  28. package/dist/declarations/src/fields/types/checkbox/index.d.ts.map +1 -1
  29. package/dist/declarations/src/fields/types/decimal/index.d.ts +3 -3
  30. package/dist/declarations/src/fields/types/decimal/index.d.ts.map +1 -1
  31. package/dist/declarations/src/fields/types/file/index.d.ts +4 -4
  32. package/dist/declarations/src/fields/types/file/index.d.ts.map +1 -1
  33. package/dist/declarations/src/fields/types/float/index.d.ts +3 -3
  34. package/dist/declarations/src/fields/types/float/index.d.ts.map +1 -1
  35. package/dist/declarations/src/fields/types/image/index.d.ts +4 -4
  36. package/dist/declarations/src/fields/types/image/index.d.ts.map +1 -1
  37. package/dist/declarations/src/fields/types/integer/index.d.ts +3 -3
  38. package/dist/declarations/src/fields/types/integer/index.d.ts.map +1 -1
  39. package/dist/declarations/src/fields/types/json/index.d.ts +3 -3
  40. package/dist/declarations/src/fields/types/json/index.d.ts.map +1 -1
  41. package/dist/declarations/src/fields/types/multiselect/index.d.ts +3 -3
  42. package/dist/declarations/src/fields/types/multiselect/index.d.ts.map +1 -1
  43. package/dist/declarations/src/fields/types/multiselect/views/index.d.ts.map +1 -1
  44. package/dist/declarations/src/fields/types/password/index.d.ts +3 -3
  45. package/dist/declarations/src/fields/types/password/index.d.ts.map +1 -1
  46. package/dist/declarations/src/fields/types/relationship/index.d.ts +8 -8
  47. package/dist/declarations/src/fields/types/relationship/index.d.ts.map +1 -1
  48. package/dist/declarations/src/fields/types/relationship/views/ComboboxMany.d.ts +3 -3
  49. package/dist/declarations/src/fields/types/relationship/views/ComboboxMany.d.ts.map +1 -1
  50. package/dist/declarations/src/fields/types/relationship/views/ComboboxSingle.d.ts +3 -3
  51. package/dist/declarations/src/fields/types/relationship/views/ComboboxSingle.d.ts.map +1 -1
  52. package/dist/declarations/src/fields/types/relationship/views/index.d.ts +3 -3
  53. package/dist/declarations/src/fields/types/relationship/views/index.d.ts.map +1 -1
  54. package/dist/declarations/src/fields/types/relationship/views/types.d.ts +3 -3
  55. package/dist/declarations/src/fields/types/relationship/views/types.d.ts.map +1 -1
  56. package/dist/declarations/src/fields/types/select/index.d.ts +3 -3
  57. package/dist/declarations/src/fields/types/select/index.d.ts.map +1 -1
  58. package/dist/declarations/src/fields/types/text/index.d.ts +3 -3
  59. package/dist/declarations/src/fields/types/text/index.d.ts.map +1 -1
  60. package/dist/declarations/src/fields/types/timestamp/index.d.ts +3 -3
  61. package/dist/declarations/src/fields/types/timestamp/index.d.ts.map +1 -1
  62. package/dist/declarations/src/fields/types/virtual/index.d.ts +7 -7
  63. package/dist/declarations/src/fields/types/virtual/index.d.ts.map +1 -1
  64. package/dist/declarations/src/helpers.d.ts +249 -13
  65. package/dist/declarations/src/helpers.d.ts.map +1 -1
  66. package/dist/declarations/src/index.d.ts +9 -4
  67. package/dist/declarations/src/index.d.ts.map +1 -1
  68. package/dist/declarations/src/internal-unstable/admin-ui/pages/ListPage/index.d.ts.map +1 -1
  69. package/dist/declarations/src/lib/admin-meta.d.ts +11 -11
  70. package/dist/declarations/src/lib/admin-meta.d.ts.map +1 -1
  71. package/dist/declarations/src/lib/core/access-control.d.ts +18 -18
  72. package/dist/declarations/src/lib/core/access-control.d.ts.map +1 -1
  73. package/dist/declarations/src/lib/core/cascade.d.ts +47 -0
  74. package/dist/declarations/src/lib/core/cascade.d.ts.map +1 -0
  75. package/dist/declarations/src/lib/core/initialise-lists.d.ts +27 -24
  76. package/dist/declarations/src/lib/core/initialise-lists.d.ts.map +1 -1
  77. package/dist/declarations/src/lib/env.d.ts +9 -0
  78. package/dist/declarations/src/lib/env.d.ts.map +1 -0
  79. package/dist/declarations/src/lib/system.d.ts +1 -1
  80. package/dist/declarations/src/lib/system.d.ts.map +1 -1
  81. package/dist/declarations/src/list-features.d.ts +162 -0
  82. package/dist/declarations/src/list-features.d.ts.map +1 -0
  83. package/dist/declarations/src/schema.d.ts +24 -23
  84. package/dist/declarations/src/schema.d.ts.map +1 -1
  85. package/dist/declarations/src/session.d.ts +75 -0
  86. package/dist/declarations/src/session.d.ts.map +1 -1
  87. package/dist/declarations/src/types/admin-meta.d.ts +11 -11
  88. package/dist/declarations/src/types/admin-meta.d.ts.map +1 -1
  89. package/dist/declarations/src/types/config/access-control.d.ts +42 -42
  90. package/dist/declarations/src/types/config/access-control.d.ts.map +1 -1
  91. package/dist/declarations/src/types/config/fields.d.ts +19 -19
  92. package/dist/declarations/src/types/config/fields.d.ts.map +1 -1
  93. package/dist/declarations/src/types/config/hooks.d.ts +131 -131
  94. package/dist/declarations/src/types/config/hooks.d.ts.map +1 -1
  95. package/dist/declarations/src/types/config/index.d.ts +190 -8
  96. package/dist/declarations/src/types/config/index.d.ts.map +1 -1
  97. package/dist/declarations/src/types/config/lists.d.ts +146 -108
  98. package/dist/declarations/src/types/config/lists.d.ts.map +1 -1
  99. package/dist/declarations/src/types/context.d.ts +507 -47
  100. package/dist/declarations/src/types/context.d.ts.map +1 -1
  101. package/dist/declarations/src/types/next-fields.d.ts +28 -28
  102. package/dist/declarations/src/types/next-fields.d.ts.map +1 -1
  103. package/dist/declarations/src/types/type-info.d.ts +3 -3
  104. package/dist/declarations/src/types/type-info.d.ts.map +1 -1
  105. package/dist/{express-455ae20c.cjs.js → express-84d534c2.cjs.js} +6 -6
  106. package/dist/{express-7559ca2d.esm.js → express-d0a4ce99.esm.js} +6 -6
  107. package/dist/{index-15c8f81e.esm.js → index-5d8b0b4e.esm.js} +363 -183
  108. package/dist/index-6055753b.cjs.js +393 -0
  109. package/dist/{index-42045902.cjs.js → index-ac29f382.cjs.js} +363 -185
  110. package/dist/index-f1703b7b.esm.js +386 -0
  111. package/dist/nixxie-cms-core.cjs.js +1388 -30
  112. package/dist/nixxie-cms-core.esm.js +1362 -24
  113. package/dist/{non-null-graphql-add6bb3d.cjs.js → non-null-graphql-4a44c122.cjs.js} +1 -1
  114. package/dist/{non-null-graphql-a84ed64d.esm.js → non-null-graphql-8c5feaae.esm.js} +1 -1
  115. package/dist/{resolve-hooks-165a9ce2.cjs.js → resolve-hooks-10a5f84c.cjs.js} +240 -6
  116. package/dist/{resolve-hooks-6813a045.esm.js → resolve-hooks-9e676794.esm.js} +238 -7
  117. package/dist/{system-a321642d.cjs.js → system-6b37a5f8.cjs.js} +33 -7
  118. package/dist/{system-03e49e4f.esm.js → system-e591d821.esm.js} +33 -7
  119. package/fields/dist/nixxie-cms-core-fields.cjs.js +29 -576
  120. package/fields/dist/nixxie-cms-core-fields.esm.js +18 -565
  121. package/fields/types/bytes/dist/nixxie-cms-core-fields-types-bytes.cjs.js +4 -2
  122. package/fields/types/bytes/dist/nixxie-cms-core-fields-types-bytes.esm.js +4 -2
  123. package/fields/types/multiselect/views/dist/nixxie-cms-core-fields-types-multiselect-views.cjs.js +1 -6
  124. package/fields/types/multiselect/views/dist/nixxie-cms-core-fields-types-multiselect-views.esm.js +1 -6
  125. package/fields/types/password/dist/nixxie-cms-core-fields-types-password.cjs.js +4 -2
  126. package/fields/types/password/dist/nixxie-cms-core-fields-types-password.esm.js +4 -2
  127. package/internal-unstable/artifacts/dist/nixxie-cms-core-internal-unstable-artifacts.cjs.js +4 -3
  128. package/internal-unstable/artifacts/dist/nixxie-cms-core-internal-unstable-artifacts.esm.js +4 -3
  129. package/package.json +4 -4
  130. package/scripts/cli/dist/nixxie-cms-core-scripts-cli.cjs.js +4 -3
  131. package/scripts/cli/dist/nixxie-cms-core-scripts-cli.esm.js +4 -3
  132. package/scripts/dist/nixxie-cms-core-scripts.cjs.js +4 -3
  133. package/scripts/dist/nixxie-cms-core-scripts.esm.js +4 -3
  134. package/session/dist/nixxie-cms-core-session.cjs.js +286 -0
  135. package/session/dist/nixxie-cms-core-session.esm.js +279 -1
  136. package/src/access.ts +25 -25
  137. package/src/admin-ui/admin-meta-graphql.ts +5 -5
  138. package/src/admin-ui/components/CreateButtonLink.tsx +46 -46
  139. package/src/admin-ui/components/Navigation.tsx +3 -3
  140. package/src/admin-ui/context.tsx +6 -6
  141. package/src/admin-ui/utils/Fields.tsx +241 -241
  142. package/src/admin-ui/utils/actionData.ts +36 -36
  143. package/src/admin-ui/utils/filters.ts +148 -148
  144. package/src/admin-ui/utils/useCreateItem.ts +171 -171
  145. package/src/admin-ui/utils/utils.tsx +127 -127
  146. package/src/context.ts +1 -1
  147. package/src/fields/non-null-graphql.ts +115 -115
  148. package/src/fields/types/bigInt/index.ts +6 -6
  149. package/src/fields/types/bytes/index.ts +6 -6
  150. package/src/fields/types/calendarDay/index.ts +18 -19
  151. package/src/fields/types/checkbox/index.ts +6 -6
  152. package/src/fields/types/decimal/index.ts +6 -6
  153. package/src/fields/types/file/index.ts +8 -8
  154. package/src/fields/types/float/index.ts +6 -6
  155. package/src/fields/types/image/index.ts +8 -8
  156. package/src/fields/types/integer/index.ts +6 -6
  157. package/src/fields/types/json/index.ts +5 -5
  158. package/src/fields/types/multiselect/index.ts +7 -7
  159. package/src/fields/types/multiselect/views/index.tsx +149 -151
  160. package/src/fields/types/password/index.ts +6 -6
  161. package/src/fields/types/relationship/index.ts +13 -13
  162. package/src/fields/types/relationship/views/ComboboxMany.tsx +110 -110
  163. package/src/fields/types/relationship/views/ComboboxSingle.tsx +115 -115
  164. package/src/fields/types/relationship/views/ContextualActions.tsx +139 -139
  165. package/src/fields/types/relationship/views/index.tsx +492 -492
  166. package/src/fields/types/relationship/views/types.ts +46 -46
  167. package/src/fields/types/relationship/views/useApolloQuery.ts +185 -185
  168. package/src/fields/types/relationship/views/useFilter.tsx +109 -109
  169. package/src/fields/types/select/index.ts +6 -6
  170. package/src/fields/types/text/index.ts +6 -6
  171. package/src/fields/types/timestamp/index.ts +23 -21
  172. package/src/fields/types/virtual/index.ts +11 -11
  173. package/src/helpers.ts +773 -42
  174. package/src/index.ts +66 -24
  175. package/src/internal-unstable/admin-ui/pages/ItemPage/common.tsx +4 -4
  176. package/src/internal-unstable/admin-ui/pages/ItemPage/index.tsx +5 -5
  177. package/src/internal-unstable/admin-ui/pages/ListPage/index.tsx +8 -8
  178. package/src/lib/admin-meta.ts +369 -369
  179. package/src/lib/context/createContext.ts +6 -0
  180. package/src/lib/core/access-control.ts +434 -434
  181. package/src/lib/core/cascade.ts +236 -0
  182. package/src/lib/core/initialise-lists.ts +49 -33
  183. package/src/lib/core/mutations/index.ts +7 -0
  184. package/src/lib/core/mutations/nested-mutation-many-input-resolvers.ts +145 -145
  185. package/src/lib/core/mutations/nested-mutation-one-input-resolvers.ts +71 -71
  186. package/src/lib/core/queries/output-field.ts +178 -178
  187. package/src/lib/env.ts +50 -0
  188. package/src/lib/id-field.ts +2 -2
  189. package/src/lib/system.ts +221 -207
  190. package/src/lib/typescript-schema-printer.ts +227 -227
  191. package/src/list-features.ts +476 -0
  192. package/src/schema.ts +92 -22
  193. package/src/session.ts +225 -0
  194. package/src/types/admin-meta.ts +218 -218
  195. package/src/types/config/access-control.ts +186 -186
  196. package/src/types/config/fields.ts +96 -96
  197. package/src/types/config/hooks.ts +529 -529
  198. package/src/types/config/index.ts +206 -7
  199. package/src/types/config/lists.ts +606 -565
  200. package/src/types/context.ts +592 -55
  201. package/src/types/next-fields.ts +31 -31
  202. package/src/types/type-info.ts +38 -38
  203. package/src/types/type-tests.ts +21 -21
@@ -1,148 +1,148 @@
1
- import type {
2
- ActionMeta,
3
- BaseListTypeInfo,
4
- ConditionalFilter,
5
- ConditionalFilterCase,
6
- FieldMeta,
7
- } from '../../types'
8
-
9
- type SerializedItem = Record<string, unknown>
10
- type FieldFilter = {
11
- equals?: unknown
12
- in?: unknown[]
13
- not?: {
14
- equals?: unknown
15
- in?: unknown[]
16
- }
17
- }
18
-
19
- function isConditionalFilterOperator(key: string): key is 'AND' | 'OR' | 'NOT' {
20
- return key === 'AND' || key === 'OR' || key === 'NOT'
21
- }
22
-
23
- function isFieldFilter(value: unknown): value is FieldFilter {
24
- return typeof value === 'object' && value !== null && !Array.isArray(value)
25
- }
26
-
27
- function applyFilter<T>(
28
- filter: {
29
- equals?: T
30
- in?: T[]
31
- },
32
- value: T
33
- ): boolean {
34
- // WARNING: this is implicit AND
35
- if (filter.equals !== undefined && value !== filter.equals) return false
36
- if (filter.in !== undefined && !filter.in.includes(value)) return false
37
- return true
38
- }
39
-
40
- export function testFilter(
41
- filter: ConditionalFilterCase<BaseListTypeInfo> | undefined,
42
- serialized: SerializedItem
43
- ): boolean {
44
- if (filter === undefined) return false
45
- if (typeof filter === 'boolean') return filter
46
-
47
- if (filter.AND !== undefined && !filter.AND.every(filter => testFilter(filter, serialized))) {
48
- return false
49
- }
50
- if (filter.OR !== undefined && !filter.OR.some(filter => testFilter(filter, serialized))) {
51
- return false
52
- }
53
- if (filter.NOT !== undefined && testFilter(filter.NOT, serialized)) {
54
- return false
55
- }
56
-
57
- for (const [key, fieldFilter] of Object.entries(filter)) {
58
- if (!isFieldFilter(fieldFilter)) continue
59
- if (isConditionalFilterOperator(key)) continue
60
-
61
- const serializedValue = serialized[key]
62
- if (!applyFilter(fieldFilter, serializedValue)) return false
63
- if (fieldFilter.not !== undefined && applyFilter(fieldFilter.not, serializedValue)) {
64
- return false
65
- }
66
- }
67
- return true
68
- }
69
-
70
- export function serializeItemForConditionalFilters(
71
- fields: Record<string, FieldMeta>,
72
- itemValue: Record<string, unknown>
73
- ): SerializedItem {
74
- const serialized: SerializedItem = {}
75
- for (const [fieldKey, field] of Object.entries(fields)) {
76
- Object.assign(serialized, field.controller.serialize(itemValue[fieldKey]))
77
- }
78
- return serialized
79
- }
80
-
81
- export function resolveActionMode(
82
- actionMode: ConditionalFilter<
83
- 'enabled' | 'disabled' | 'hidden',
84
- 'disabled' | 'hidden',
85
- BaseListTypeInfo
86
- >,
87
- serialized: SerializedItem
88
- ): 'enabled' | 'disabled' | 'hidden' {
89
- if (typeof actionMode === 'string') return actionMode
90
- if (testFilter(actionMode.hidden, serialized)) return 'hidden'
91
- if (testFilter(actionMode.disabled, serialized)) return 'disabled'
92
- return 'enabled'
93
- }
94
-
95
- export function resolveFieldMode(
96
- fieldMode: ConditionalFilter<'edit' | 'read' | 'hidden', 'read' | 'hidden', BaseListTypeInfo>,
97
- serialized: SerializedItem,
98
- view: 'createView' | 'itemView'
99
- ) {
100
- if (typeof fieldMode === 'string') return fieldMode
101
- if (testFilter(fieldMode.hidden, serialized)) return 'hidden'
102
- if (view === 'itemView' && testFilter(fieldMode.read, serialized)) return 'read'
103
- return 'edit'
104
- }
105
-
106
- export function isActionAvailable(
107
- action: ActionMeta,
108
- { actionMode }: ActionMeta['listView'] | ActionMeta['itemView']
109
- ): boolean {
110
- if (action.graphql.arguments.some(a => !a.source)) return false
111
-
112
- // conditional filters cannot be prefiltered ahead of time
113
- if (typeof actionMode !== 'string') return true
114
- return actionMode !== 'hidden'
115
- }
116
-
117
- export function getConditionalFilterFieldKeys(
118
- actionMode: ConditionalFilter<string, string, BaseListTypeInfo>
119
- ): string[] {
120
- if (typeof actionMode === 'string') return []
121
-
122
- const fieldKeys = new Set<string>()
123
-
124
- function collectFieldKeys(filter: ConditionalFilterCase<BaseListTypeInfo> | undefined) {
125
- if (!filter || typeof filter !== 'object') return
126
-
127
- if (filter.AND !== undefined) {
128
- filter.AND.forEach(collectFieldKeys)
129
- }
130
- if (filter.OR !== undefined) {
131
- filter.OR.forEach(collectFieldKeys)
132
- }
133
- if (filter.NOT !== undefined) {
134
- collectFieldKeys(filter.NOT)
135
- }
136
-
137
- for (const key of Object.keys(filter)) {
138
- if (isConditionalFilterOperator(key)) continue
139
- fieldKeys.add(key)
140
- }
141
- }
142
-
143
- for (const conditionalFilter of Object.values(actionMode)) {
144
- collectFieldKeys(conditionalFilter)
145
- }
146
-
147
- return [...fieldKeys]
148
- }
1
+ import type {
2
+ ActionMeta,
3
+ BaseCollectionTypeInfo,
4
+ ConditionalFilter,
5
+ ConditionalFilterCase,
6
+ FieldMeta,
7
+ } from '../../types'
8
+
9
+ type SerializedItem = Record<string, unknown>
10
+ type FieldFilter = {
11
+ equals?: unknown
12
+ in?: unknown[]
13
+ not?: {
14
+ equals?: unknown
15
+ in?: unknown[]
16
+ }
17
+ }
18
+
19
+ function isConditionalFilterOperator(key: string): key is 'AND' | 'OR' | 'NOT' {
20
+ return key === 'AND' || key === 'OR' || key === 'NOT'
21
+ }
22
+
23
+ function isFieldFilter(value: unknown): value is FieldFilter {
24
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
25
+ }
26
+
27
+ function applyFilter<T>(
28
+ filter: {
29
+ equals?: T
30
+ in?: T[]
31
+ },
32
+ value: T
33
+ ): boolean {
34
+ // WARNING: this is implicit AND
35
+ if (filter.equals !== undefined && value !== filter.equals) return false
36
+ if (filter.in !== undefined && !filter.in.includes(value)) return false
37
+ return true
38
+ }
39
+
40
+ export function testFilter(
41
+ filter: ConditionalFilterCase<BaseCollectionTypeInfo> | undefined,
42
+ serialized: SerializedItem
43
+ ): boolean {
44
+ if (filter === undefined) return false
45
+ if (typeof filter === 'boolean') return filter
46
+
47
+ if (filter.AND !== undefined && !filter.AND.every(filter => testFilter(filter, serialized))) {
48
+ return false
49
+ }
50
+ if (filter.OR !== undefined && !filter.OR.some(filter => testFilter(filter, serialized))) {
51
+ return false
52
+ }
53
+ if (filter.NOT !== undefined && testFilter(filter.NOT, serialized)) {
54
+ return false
55
+ }
56
+
57
+ for (const [key, fieldFilter] of Object.entries(filter)) {
58
+ if (!isFieldFilter(fieldFilter)) continue
59
+ if (isConditionalFilterOperator(key)) continue
60
+
61
+ const serializedValue = serialized[key]
62
+ if (!applyFilter(fieldFilter, serializedValue)) return false
63
+ if (fieldFilter.not !== undefined && applyFilter(fieldFilter.not, serializedValue)) {
64
+ return false
65
+ }
66
+ }
67
+ return true
68
+ }
69
+
70
+ export function serializeItemForConditionalFilters(
71
+ fields: Record<string, FieldMeta>,
72
+ itemValue: Record<string, unknown>
73
+ ): SerializedItem {
74
+ const serialized: SerializedItem = {}
75
+ for (const [fieldKey, field] of Object.entries(fields)) {
76
+ Object.assign(serialized, field.controller.serialize(itemValue[fieldKey]))
77
+ }
78
+ return serialized
79
+ }
80
+
81
+ export function resolveActionMode(
82
+ actionMode: ConditionalFilter<
83
+ 'enabled' | 'disabled' | 'hidden',
84
+ 'disabled' | 'hidden',
85
+ BaseCollectionTypeInfo
86
+ >,
87
+ serialized: SerializedItem
88
+ ): 'enabled' | 'disabled' | 'hidden' {
89
+ if (typeof actionMode === 'string') return actionMode
90
+ if (testFilter(actionMode.hidden, serialized)) return 'hidden'
91
+ if (testFilter(actionMode.disabled, serialized)) return 'disabled'
92
+ return 'enabled'
93
+ }
94
+
95
+ export function resolveFieldMode(
96
+ fieldMode: ConditionalFilter<'edit' | 'read' | 'hidden', 'read' | 'hidden', BaseCollectionTypeInfo>,
97
+ serialized: SerializedItem,
98
+ view: 'createView' | 'itemView'
99
+ ) {
100
+ if (typeof fieldMode === 'string') return fieldMode
101
+ if (testFilter(fieldMode.hidden, serialized)) return 'hidden'
102
+ if (view === 'itemView' && testFilter(fieldMode.read, serialized)) return 'read'
103
+ return 'edit'
104
+ }
105
+
106
+ export function isActionAvailable(
107
+ action: ActionMeta,
108
+ { actionMode }: ActionMeta['listView'] | ActionMeta['itemView']
109
+ ): boolean {
110
+ if (action.graphql.arguments.some(a => !a.source)) return false
111
+
112
+ // conditional filters cannot be prefiltered ahead of time
113
+ if (typeof actionMode !== 'string') return true
114
+ return actionMode !== 'hidden'
115
+ }
116
+
117
+ export function getConditionalFilterFieldKeys(
118
+ actionMode: ConditionalFilter<string, string, BaseCollectionTypeInfo>
119
+ ): string[] {
120
+ if (typeof actionMode === 'string') return []
121
+
122
+ const fieldKeys = new Set<string>()
123
+
124
+ function collectFieldKeys(filter: ConditionalFilterCase<BaseCollectionTypeInfo> | undefined) {
125
+ if (!filter || typeof filter !== 'object') return
126
+
127
+ if (filter.AND !== undefined) {
128
+ filter.AND.forEach(collectFieldKeys)
129
+ }
130
+ if (filter.OR !== undefined) {
131
+ filter.OR.forEach(collectFieldKeys)
132
+ }
133
+ if (filter.NOT !== undefined) {
134
+ collectFieldKeys(filter.NOT)
135
+ }
136
+
137
+ for (const key of Object.keys(filter)) {
138
+ if (isConditionalFilterOperator(key)) continue
139
+ fieldKeys.add(key)
140
+ }
141
+ }
142
+
143
+ for (const conditionalFilter of Object.values(actionMode)) {
144
+ collectFieldKeys(conditionalFilter)
145
+ }
146
+
147
+ return [...fieldKeys]
148
+ }
@@ -1,171 +1,171 @@
1
- import { useRouter } from 'next/router'
2
- import { type ComponentProps, useEffect, useMemo, useRef, useState } from 'react'
3
-
4
- import { toastQueue } from '@keystar/ui/toast'
5
-
6
- import type { Fields } from '.'
7
- import {
8
- makeDefaultValueState,
9
- serializeValueToOperationItem,
10
- useHasChanges,
11
- useInvalidFields,
12
- } from '../../admin-ui/utils'
13
- import type { ListMeta } from '../../types'
14
- import { type ErrorLike, gql, type TypedDocumentNode, useMutation } from '../apollo'
15
- import { usePreventNavigation } from './usePreventNavigation'
16
-
17
- type CreateItemHookResult = {
18
- state: 'editing' | 'loading' | 'created'
19
- shouldPreventNavigation: boolean
20
- error?: ErrorLike
21
- props: ComponentProps<typeof Fields>
22
- create: () => Promise<{ id: string; label: string | null } | undefined>
23
- }
24
-
25
- export function useCreateItem(list: ListMeta): CreateItemHookResult {
26
- const router = useRouter()
27
- const [tryCreateItem, { loading, error, data: returnedData }] = useMutation(
28
- gql`mutation($data: ${list.graphql.names.createInputName}!) {
29
- item: ${list.graphql.names.createMutationName}(data: $data) {
30
- id
31
- label: ${list.labelField}
32
- }
33
- }` as TypedDocumentNode<
34
- { item: { id: string; label: string | null } },
35
- { data: Record<string, unknown> }
36
- >
37
- )
38
-
39
- const isRequireds = useMemo(
40
- () =>
41
- Object.fromEntries(
42
- Object.entries(list.fields).map(([key, field]) => [key, field.createView.isRequired])
43
- ),
44
- [list.fields]
45
- )
46
-
47
- const [forceValidation, setForceValidation] = useState(false)
48
- const [value, setValue] = useState(() => makeDefaultValueState(list.fields))
49
- const invalidFields = useInvalidFields(list.fields, value, isRequireds)
50
-
51
- const hasChangedFields = useHasChanges(
52
- 'create',
53
- list.fields,
54
- value,
55
- makeDefaultValueState(list.fields)
56
- )
57
- const shouldPreventNavigation = !returnedData?.item && hasChangedFields
58
- const shouldPreventNavigationRef = useRef(shouldPreventNavigation)
59
-
60
- useEffect(() => {
61
- shouldPreventNavigationRef.current = shouldPreventNavigation
62
- }, [shouldPreventNavigation])
63
- usePreventNavigation(shouldPreventNavigationRef)
64
-
65
- return {
66
- state: loading ? 'loading' : !returnedData?.item ? 'created' : 'editing',
67
- shouldPreventNavigation,
68
- error,
69
- props: {
70
- view: 'createView',
71
- position: 'form',
72
- fields: list.fields,
73
- groups: list.groups,
74
- forceValidation,
75
- invalidFields,
76
- value,
77
- isRequireds,
78
- onChange: newItemValue => setValue(newItemValue),
79
- },
80
- async create(): Promise<{ id: string; label: string | null } | undefined> {
81
- const newForceValidation = invalidFields.size !== 0
82
- setForceValidation(newForceValidation)
83
-
84
- if (newForceValidation) return
85
-
86
- let outputData: { item: { id: string; label: string | null } }
87
- try {
88
- outputData = await tryCreateItem({
89
- variables: {
90
- data: serializeValueToOperationItem('create', list.fields, value),
91
- },
92
- update(cache, { data }) {
93
- if (typeof data?.item?.id === 'string') {
94
- cache.evict({
95
- id: 'ROOT_QUERY',
96
- fieldName: `${list.graphql.names.itemQueryName}(${JSON.stringify({
97
- where: { id: data.item.id },
98
- })})`,
99
- })
100
- }
101
- },
102
- }).then(x => {
103
- if (x.data) return x.data
104
- if (x.error) throw x.error
105
- throw new Error('Something went wrong during item creation.')
106
- })
107
- } catch {
108
- toastQueue.critical(`Unable to create ${list.singular.toLocaleLowerCase()}`)
109
- return
110
- }
111
-
112
- shouldPreventNavigationRef.current = false
113
- toastQueue.positive(`${list.singular} created`, {
114
- timeout: 5000,
115
- actionLabel: 'Create another',
116
- onAction: () => {
117
- router.push(`/${list.path}/create`)
118
- },
119
- shouldCloseOnAction: true,
120
- })
121
-
122
- return outputData.item
123
- },
124
- }
125
- }
126
-
127
- type BuildItemHookResult = {
128
- state: 'editing'
129
- error?: ErrorLike
130
- props: ComponentProps<typeof Fields>
131
- build: () => Promise<Record<string, unknown> | undefined>
132
- }
133
-
134
- export function useBuildItem(list: ListMeta, fieldKeys: string[] = []): BuildItemHookResult {
135
- const [forceValidation, setForceValidation] = useState(false)
136
- const [value, setValue] = useState(() => makeDefaultValueState(list.fields))
137
- const fields = fieldKeys.length
138
- ? Object.fromEntries(Object.entries(list.fields).filter(([key]) => fieldKeys.includes(key)))
139
- : list.fields
140
-
141
- const isRequireds = useMemo(
142
- () =>
143
- Object.fromEntries(
144
- Object.entries(list.fields).map(([key, field]) => [key, field.createView.isRequired])
145
- ),
146
- [list.fields]
147
- )
148
-
149
- const invalidFields = useInvalidFields(list.fields, value, isRequireds)
150
-
151
- return {
152
- state: 'editing',
153
- props: {
154
- view: 'createView',
155
- position: 'form',
156
- fields,
157
- groups: list.groups,
158
- forceValidation,
159
- invalidFields,
160
- value,
161
- isRequireds,
162
- onChange: newItemValue => setValue(newItemValue),
163
- },
164
- async build() {
165
- const newForceValidation = invalidFields.size !== 0
166
- setForceValidation(newForceValidation)
167
- if (newForceValidation) return
168
- return serializeValueToOperationItem('create', list.fields, value)
169
- },
170
- }
171
- }
1
+ import { useRouter } from 'next/router'
2
+ import { type ComponentProps, useEffect, useMemo, useRef, useState } from 'react'
3
+
4
+ import { toastQueue } from '@keystar/ui/toast'
5
+
6
+ import type { Fields } from '.'
7
+ import {
8
+ makeDefaultValueState,
9
+ serializeValueToOperationItem,
10
+ useHasChanges,
11
+ useInvalidFields,
12
+ } from '../../admin-ui/utils'
13
+ import type { CollectionMeta } from '../../types'
14
+ import { type ErrorLike, gql, type TypedDocumentNode, useMutation } from '../apollo'
15
+ import { usePreventNavigation } from './usePreventNavigation'
16
+
17
+ type CreateItemHookResult = {
18
+ state: 'editing' | 'loading' | 'created'
19
+ shouldPreventNavigation: boolean
20
+ error?: ErrorLike
21
+ props: ComponentProps<typeof Fields>
22
+ create: () => Promise<{ id: string; label: string | null } | undefined>
23
+ }
24
+
25
+ export function useCreateItem(list: CollectionMeta): CreateItemHookResult {
26
+ const router = useRouter()
27
+ const [tryCreateItem, { loading, error, data: returnedData }] = useMutation(
28
+ gql`mutation($data: ${list.graphql.names.createInputName}!) {
29
+ item: ${list.graphql.names.createMutationName}(data: $data) {
30
+ id
31
+ label: ${list.labelField}
32
+ }
33
+ }` as TypedDocumentNode<
34
+ { item: { id: string; label: string | null } },
35
+ { data: Record<string, unknown> }
36
+ >
37
+ )
38
+
39
+ const isRequireds = useMemo(
40
+ () =>
41
+ Object.fromEntries(
42
+ Object.entries(list.fields).map(([key, field]) => [key, field.createView.isRequired])
43
+ ),
44
+ [list.fields]
45
+ )
46
+
47
+ const [forceValidation, setForceValidation] = useState(false)
48
+ const [value, setValue] = useState(() => makeDefaultValueState(list.fields))
49
+ const invalidFields = useInvalidFields(list.fields, value, isRequireds)
50
+
51
+ const hasChangedFields = useHasChanges(
52
+ 'create',
53
+ list.fields,
54
+ value,
55
+ makeDefaultValueState(list.fields)
56
+ )
57
+ const shouldPreventNavigation = !returnedData?.item && hasChangedFields
58
+ const shouldPreventNavigationRef = useRef(shouldPreventNavigation)
59
+
60
+ useEffect(() => {
61
+ shouldPreventNavigationRef.current = shouldPreventNavigation
62
+ }, [shouldPreventNavigation])
63
+ usePreventNavigation(shouldPreventNavigationRef)
64
+
65
+ return {
66
+ state: loading ? 'loading' : !returnedData?.item ? 'created' : 'editing',
67
+ shouldPreventNavigation,
68
+ error,
69
+ props: {
70
+ view: 'createView',
71
+ position: 'form',
72
+ fields: list.fields,
73
+ groups: list.groups,
74
+ forceValidation,
75
+ invalidFields,
76
+ value,
77
+ isRequireds,
78
+ onChange: newItemValue => setValue(newItemValue),
79
+ },
80
+ async create(): Promise<{ id: string; label: string | null } | undefined> {
81
+ const newForceValidation = invalidFields.size !== 0
82
+ setForceValidation(newForceValidation)
83
+
84
+ if (newForceValidation) return
85
+
86
+ let outputData: { item: { id: string; label: string | null } }
87
+ try {
88
+ outputData = await tryCreateItem({
89
+ variables: {
90
+ data: serializeValueToOperationItem('create', list.fields, value),
91
+ },
92
+ update(cache, { data }) {
93
+ if (typeof data?.item?.id === 'string') {
94
+ cache.evict({
95
+ id: 'ROOT_QUERY',
96
+ fieldName: `${list.graphql.names.itemQueryName}(${JSON.stringify({
97
+ where: { id: data.item.id },
98
+ })})`,
99
+ })
100
+ }
101
+ },
102
+ }).then(x => {
103
+ if (x.data) return x.data
104
+ if (x.error) throw x.error
105
+ throw new Error('Something went wrong during item creation.')
106
+ })
107
+ } catch {
108
+ toastQueue.critical(`Unable to create ${list.singular.toLocaleLowerCase()}`)
109
+ return
110
+ }
111
+
112
+ shouldPreventNavigationRef.current = false
113
+ toastQueue.positive(`${list.singular} created`, {
114
+ timeout: 5000,
115
+ actionLabel: 'Create another',
116
+ onAction: () => {
117
+ router.push(`/${list.path}/create`)
118
+ },
119
+ shouldCloseOnAction: true,
120
+ })
121
+
122
+ return outputData.item
123
+ },
124
+ }
125
+ }
126
+
127
+ type BuildItemHookResult = {
128
+ state: 'editing'
129
+ error?: ErrorLike
130
+ props: ComponentProps<typeof Fields>
131
+ build: () => Promise<Record<string, unknown> | undefined>
132
+ }
133
+
134
+ export function useBuildItem(list: CollectionMeta, fieldKeys: string[] = []): BuildItemHookResult {
135
+ const [forceValidation, setForceValidation] = useState(false)
136
+ const [value, setValue] = useState(() => makeDefaultValueState(list.fields))
137
+ const fields = fieldKeys.length
138
+ ? Object.fromEntries(Object.entries(list.fields).filter(([key]) => fieldKeys.includes(key)))
139
+ : list.fields
140
+
141
+ const isRequireds = useMemo(
142
+ () =>
143
+ Object.fromEntries(
144
+ Object.entries(list.fields).map(([key, field]) => [key, field.createView.isRequired])
145
+ ),
146
+ [list.fields]
147
+ )
148
+
149
+ const invalidFields = useInvalidFields(list.fields, value, isRequireds)
150
+
151
+ return {
152
+ state: 'editing',
153
+ props: {
154
+ view: 'createView',
155
+ position: 'form',
156
+ fields,
157
+ groups: list.groups,
158
+ forceValidation,
159
+ invalidFields,
160
+ value,
161
+ isRequireds,
162
+ onChange: newItemValue => setValue(newItemValue),
163
+ },
164
+ async build() {
165
+ const newForceValidation = invalidFields.size !== 0
166
+ setForceValidation(newForceValidation)
167
+ if (newForceValidation) return
168
+ return serializeValueToOperationItem('create', list.fields, value)
169
+ },
170
+ }
171
+ }