@nixxie-cms/core 1.0.3 → 1.1.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 (202) hide show
  1. package/CHANGES-1.1.md +134 -0
  2. package/context/dist/nixxie-cms-core-context.cjs.js +4 -3
  3. package/context/dist/nixxie-cms-core-context.esm.js +3 -2
  4. package/dist/declarations/src/access.d.ts +2 -2
  5. package/dist/declarations/src/access.d.ts.map +1 -1
  6. package/dist/declarations/src/admin-ui/components/Navigation.d.ts +2 -2
  7. package/dist/declarations/src/admin-ui/components/Navigation.d.ts.map +1 -1
  8. package/dist/declarations/src/admin-ui/context.d.ts +6 -6
  9. package/dist/declarations/src/admin-ui/context.d.ts.map +1 -1
  10. package/dist/declarations/src/admin-ui/utils/Fields.d.ts +3 -3
  11. package/dist/declarations/src/admin-ui/utils/Fields.d.ts.map +1 -1
  12. package/dist/declarations/src/admin-ui/utils/filters.d.ts +5 -5
  13. package/dist/declarations/src/admin-ui/utils/filters.d.ts.map +1 -1
  14. package/dist/declarations/src/admin-ui/utils/useCreateItem.d.ts +3 -3
  15. package/dist/declarations/src/admin-ui/utils/useCreateItem.d.ts.map +1 -1
  16. package/dist/declarations/src/admin-ui/utils/utils.d.ts +2 -2
  17. package/dist/declarations/src/admin-ui/utils/utils.d.ts.map +1 -1
  18. package/dist/declarations/src/context.d.ts +1 -1
  19. package/dist/declarations/src/context.d.ts.map +1 -1
  20. package/dist/declarations/src/fields/types/bigInt/index.d.ts +3 -3
  21. package/dist/declarations/src/fields/types/bigInt/index.d.ts.map +1 -1
  22. package/dist/declarations/src/fields/types/bytes/index.d.ts +3 -3
  23. package/dist/declarations/src/fields/types/bytes/index.d.ts.map +1 -1
  24. package/dist/declarations/src/fields/types/calendarDay/index.d.ts +3 -3
  25. package/dist/declarations/src/fields/types/calendarDay/index.d.ts.map +1 -1
  26. package/dist/declarations/src/fields/types/checkbox/index.d.ts +3 -3
  27. package/dist/declarations/src/fields/types/checkbox/index.d.ts.map +1 -1
  28. package/dist/declarations/src/fields/types/decimal/index.d.ts +3 -3
  29. package/dist/declarations/src/fields/types/decimal/index.d.ts.map +1 -1
  30. package/dist/declarations/src/fields/types/file/index.d.ts +4 -4
  31. package/dist/declarations/src/fields/types/file/index.d.ts.map +1 -1
  32. package/dist/declarations/src/fields/types/float/index.d.ts +3 -3
  33. package/dist/declarations/src/fields/types/float/index.d.ts.map +1 -1
  34. package/dist/declarations/src/fields/types/image/index.d.ts +4 -4
  35. package/dist/declarations/src/fields/types/image/index.d.ts.map +1 -1
  36. package/dist/declarations/src/fields/types/integer/index.d.ts +3 -3
  37. package/dist/declarations/src/fields/types/integer/index.d.ts.map +1 -1
  38. package/dist/declarations/src/fields/types/json/index.d.ts +3 -3
  39. package/dist/declarations/src/fields/types/json/index.d.ts.map +1 -1
  40. package/dist/declarations/src/fields/types/multiselect/index.d.ts +3 -3
  41. package/dist/declarations/src/fields/types/multiselect/index.d.ts.map +1 -1
  42. package/dist/declarations/src/fields/types/multiselect/views/index.d.ts.map +1 -1
  43. package/dist/declarations/src/fields/types/password/index.d.ts +3 -3
  44. package/dist/declarations/src/fields/types/password/index.d.ts.map +1 -1
  45. package/dist/declarations/src/fields/types/relationship/index.d.ts +8 -8
  46. package/dist/declarations/src/fields/types/relationship/index.d.ts.map +1 -1
  47. package/dist/declarations/src/fields/types/relationship/views/ComboboxMany.d.ts +3 -3
  48. package/dist/declarations/src/fields/types/relationship/views/ComboboxMany.d.ts.map +1 -1
  49. package/dist/declarations/src/fields/types/relationship/views/ComboboxSingle.d.ts +3 -3
  50. package/dist/declarations/src/fields/types/relationship/views/ComboboxSingle.d.ts.map +1 -1
  51. package/dist/declarations/src/fields/types/relationship/views/index.d.ts +3 -3
  52. package/dist/declarations/src/fields/types/relationship/views/index.d.ts.map +1 -1
  53. package/dist/declarations/src/fields/types/relationship/views/types.d.ts +3 -3
  54. package/dist/declarations/src/fields/types/relationship/views/types.d.ts.map +1 -1
  55. package/dist/declarations/src/fields/types/select/index.d.ts +3 -3
  56. package/dist/declarations/src/fields/types/select/index.d.ts.map +1 -1
  57. package/dist/declarations/src/fields/types/text/index.d.ts +3 -3
  58. package/dist/declarations/src/fields/types/text/index.d.ts.map +1 -1
  59. package/dist/declarations/src/fields/types/timestamp/index.d.ts +3 -3
  60. package/dist/declarations/src/fields/types/timestamp/index.d.ts.map +1 -1
  61. package/dist/declarations/src/fields/types/virtual/index.d.ts +7 -7
  62. package/dist/declarations/src/fields/types/virtual/index.d.ts.map +1 -1
  63. package/dist/declarations/src/helpers.d.ts +249 -13
  64. package/dist/declarations/src/helpers.d.ts.map +1 -1
  65. package/dist/declarations/src/index.d.ts +9 -4
  66. package/dist/declarations/src/index.d.ts.map +1 -1
  67. package/dist/declarations/src/internal-unstable/admin-ui/pages/ListPage/index.d.ts.map +1 -1
  68. package/dist/declarations/src/lib/admin-meta.d.ts +11 -11
  69. package/dist/declarations/src/lib/admin-meta.d.ts.map +1 -1
  70. package/dist/declarations/src/lib/core/access-control.d.ts +18 -18
  71. package/dist/declarations/src/lib/core/access-control.d.ts.map +1 -1
  72. package/dist/declarations/src/lib/core/cascade.d.ts +47 -0
  73. package/dist/declarations/src/lib/core/cascade.d.ts.map +1 -0
  74. package/dist/declarations/src/lib/core/initialise-lists.d.ts +27 -24
  75. package/dist/declarations/src/lib/core/initialise-lists.d.ts.map +1 -1
  76. package/dist/declarations/src/lib/env.d.ts +9 -0
  77. package/dist/declarations/src/lib/env.d.ts.map +1 -0
  78. package/dist/declarations/src/lib/system.d.ts +1 -1
  79. package/dist/declarations/src/lib/system.d.ts.map +1 -1
  80. package/dist/declarations/src/list-features.d.ts +162 -0
  81. package/dist/declarations/src/list-features.d.ts.map +1 -0
  82. package/dist/declarations/src/schema.d.ts +24 -23
  83. package/dist/declarations/src/schema.d.ts.map +1 -1
  84. package/dist/declarations/src/session.d.ts +75 -0
  85. package/dist/declarations/src/session.d.ts.map +1 -1
  86. package/dist/declarations/src/types/admin-meta.d.ts +11 -11
  87. package/dist/declarations/src/types/admin-meta.d.ts.map +1 -1
  88. package/dist/declarations/src/types/config/access-control.d.ts +42 -42
  89. package/dist/declarations/src/types/config/access-control.d.ts.map +1 -1
  90. package/dist/declarations/src/types/config/fields.d.ts +19 -19
  91. package/dist/declarations/src/types/config/fields.d.ts.map +1 -1
  92. package/dist/declarations/src/types/config/hooks.d.ts +131 -131
  93. package/dist/declarations/src/types/config/hooks.d.ts.map +1 -1
  94. package/dist/declarations/src/types/config/index.d.ts +171 -8
  95. package/dist/declarations/src/types/config/index.d.ts.map +1 -1
  96. package/dist/declarations/src/types/config/lists.d.ts +146 -108
  97. package/dist/declarations/src/types/config/lists.d.ts.map +1 -1
  98. package/dist/declarations/src/types/context.d.ts +349 -47
  99. package/dist/declarations/src/types/context.d.ts.map +1 -1
  100. package/dist/declarations/src/types/next-fields.d.ts +28 -28
  101. package/dist/declarations/src/types/next-fields.d.ts.map +1 -1
  102. package/dist/declarations/src/types/type-info.d.ts +3 -3
  103. package/dist/declarations/src/types/type-info.d.ts.map +1 -1
  104. package/dist/{express-7559ca2d.esm.js → express-0abbce07.esm.js} +6 -6
  105. package/dist/{express-455ae20c.cjs.js → express-7ca6f76a.cjs.js} +6 -6
  106. package/dist/{index-15c8f81e.esm.js → index-5d8b0b4e.esm.js} +363 -183
  107. package/dist/index-6055753b.cjs.js +393 -0
  108. package/dist/{index-42045902.cjs.js → index-ac29f382.cjs.js} +363 -185
  109. package/dist/index-f1703b7b.esm.js +386 -0
  110. package/dist/nixxie-cms-core.cjs.js +1387 -30
  111. package/dist/nixxie-cms-core.esm.js +1361 -24
  112. package/dist/{non-null-graphql-add6bb3d.cjs.js → non-null-graphql-4a44c122.cjs.js} +1 -1
  113. package/dist/{non-null-graphql-a84ed64d.esm.js → non-null-graphql-8c5feaae.esm.js} +1 -1
  114. package/dist/{resolve-hooks-165a9ce2.cjs.js → resolve-hooks-10a5f84c.cjs.js} +240 -6
  115. package/dist/{resolve-hooks-6813a045.esm.js → resolve-hooks-9e676794.esm.js} +238 -7
  116. package/dist/{system-03e49e4f.esm.js → system-4d2a2648.esm.js} +32 -7
  117. package/dist/{system-a321642d.cjs.js → system-69e1a285.cjs.js} +32 -7
  118. package/fields/dist/nixxie-cms-core-fields.cjs.js +29 -576
  119. package/fields/dist/nixxie-cms-core-fields.esm.js +18 -565
  120. package/fields/types/bytes/dist/nixxie-cms-core-fields-types-bytes.cjs.js +4 -2
  121. package/fields/types/bytes/dist/nixxie-cms-core-fields-types-bytes.esm.js +4 -2
  122. package/fields/types/multiselect/views/dist/nixxie-cms-core-fields-types-multiselect-views.cjs.js +1 -6
  123. package/fields/types/multiselect/views/dist/nixxie-cms-core-fields-types-multiselect-views.esm.js +1 -6
  124. package/fields/types/password/dist/nixxie-cms-core-fields-types-password.cjs.js +4 -2
  125. package/fields/types/password/dist/nixxie-cms-core-fields-types-password.esm.js +4 -2
  126. package/internal-unstable/artifacts/dist/nixxie-cms-core-internal-unstable-artifacts.cjs.js +4 -3
  127. package/internal-unstable/artifacts/dist/nixxie-cms-core-internal-unstable-artifacts.esm.js +4 -3
  128. package/package.json +4 -4
  129. package/scripts/cli/dist/nixxie-cms-core-scripts-cli.cjs.js +4 -3
  130. package/scripts/cli/dist/nixxie-cms-core-scripts-cli.esm.js +4 -3
  131. package/scripts/dist/nixxie-cms-core-scripts.cjs.js +4 -3
  132. package/scripts/dist/nixxie-cms-core-scripts.esm.js +4 -3
  133. package/session/dist/nixxie-cms-core-session.cjs.js +286 -0
  134. package/session/dist/nixxie-cms-core-session.esm.js +279 -1
  135. package/src/access.ts +25 -25
  136. package/src/admin-ui/admin-meta-graphql.ts +5 -5
  137. package/src/admin-ui/components/CreateButtonLink.tsx +46 -46
  138. package/src/admin-ui/components/Navigation.tsx +3 -3
  139. package/src/admin-ui/context.tsx +6 -6
  140. package/src/admin-ui/utils/Fields.tsx +241 -241
  141. package/src/admin-ui/utils/actionData.ts +36 -36
  142. package/src/admin-ui/utils/filters.ts +148 -148
  143. package/src/admin-ui/utils/useCreateItem.ts +171 -171
  144. package/src/admin-ui/utils/utils.tsx +127 -127
  145. package/src/context.ts +1 -1
  146. package/src/fields/non-null-graphql.ts +115 -115
  147. package/src/fields/types/bigInt/index.ts +6 -6
  148. package/src/fields/types/bytes/index.ts +6 -6
  149. package/src/fields/types/calendarDay/index.ts +18 -19
  150. package/src/fields/types/checkbox/index.ts +6 -6
  151. package/src/fields/types/decimal/index.ts +6 -6
  152. package/src/fields/types/file/index.ts +8 -8
  153. package/src/fields/types/float/index.ts +6 -6
  154. package/src/fields/types/image/index.ts +8 -8
  155. package/src/fields/types/integer/index.ts +6 -6
  156. package/src/fields/types/json/index.ts +5 -5
  157. package/src/fields/types/multiselect/index.ts +7 -7
  158. package/src/fields/types/multiselect/views/index.tsx +149 -151
  159. package/src/fields/types/password/index.ts +6 -6
  160. package/src/fields/types/relationship/index.ts +13 -13
  161. package/src/fields/types/relationship/views/ComboboxMany.tsx +110 -110
  162. package/src/fields/types/relationship/views/ComboboxSingle.tsx +115 -115
  163. package/src/fields/types/relationship/views/ContextualActions.tsx +139 -139
  164. package/src/fields/types/relationship/views/index.tsx +492 -492
  165. package/src/fields/types/relationship/views/types.ts +46 -46
  166. package/src/fields/types/relationship/views/useApolloQuery.ts +185 -185
  167. package/src/fields/types/relationship/views/useFilter.tsx +109 -109
  168. package/src/fields/types/select/index.ts +6 -6
  169. package/src/fields/types/text/index.ts +6 -6
  170. package/src/fields/types/timestamp/index.ts +23 -21
  171. package/src/fields/types/virtual/index.ts +11 -11
  172. package/src/helpers.ts +773 -42
  173. package/src/index.ts +66 -24
  174. package/src/internal-unstable/admin-ui/pages/ItemPage/common.tsx +4 -4
  175. package/src/internal-unstable/admin-ui/pages/ItemPage/index.tsx +5 -5
  176. package/src/internal-unstable/admin-ui/pages/ListPage/index.tsx +8 -8
  177. package/src/lib/admin-meta.ts +369 -369
  178. package/src/lib/context/createContext.ts +5 -0
  179. package/src/lib/core/access-control.ts +434 -434
  180. package/src/lib/core/cascade.ts +236 -0
  181. package/src/lib/core/initialise-lists.ts +49 -33
  182. package/src/lib/core/mutations/index.ts +7 -0
  183. package/src/lib/core/mutations/nested-mutation-many-input-resolvers.ts +145 -145
  184. package/src/lib/core/mutations/nested-mutation-one-input-resolvers.ts +71 -71
  185. package/src/lib/core/queries/output-field.ts +178 -178
  186. package/src/lib/env.ts +50 -0
  187. package/src/lib/id-field.ts +2 -2
  188. package/src/lib/system.ts +221 -207
  189. package/src/lib/typescript-schema-printer.ts +227 -227
  190. package/src/list-features.ts +476 -0
  191. package/src/schema.ts +91 -22
  192. package/src/session.ts +225 -0
  193. package/src/types/admin-meta.ts +218 -218
  194. package/src/types/config/access-control.ts +186 -186
  195. package/src/types/config/fields.ts +96 -96
  196. package/src/types/config/hooks.ts +529 -529
  197. package/src/types/config/index.ts +185 -7
  198. package/src/types/config/lists.ts +606 -565
  199. package/src/types/context.ts +426 -55
  200. package/src/types/next-fields.ts +31 -31
  201. package/src/types/type-info.ts +38 -38
  202. 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
+ }