@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,46 +1,46 @@
1
- import type { FieldController, ListSortDescriptor } from '../../../../types'
2
-
3
- export type RelationshipValue = {
4
- id: string
5
- label: string | null
6
- data?: Record<string, unknown>
7
- built: undefined | boolean
8
- }
9
-
10
- export type SingleRelationshipValue = {
11
- kind: 'one'
12
- id: null | string
13
- initialValue: RelationshipValue | null
14
- value: RelationshipValue | null
15
- }
16
-
17
- export type ManyRelationshipValue = {
18
- kind: 'many'
19
- id: null | string
20
- initialValue: RelationshipValue[]
21
- value: RelationshipValue[]
22
- }
23
-
24
- export type CountRelationshipValue = {
25
- kind: 'count'
26
- id: string
27
- count: number
28
- }
29
-
30
- export type RelationshipController = FieldController<
31
- ManyRelationshipValue | SingleRelationshipValue | CountRelationshipValue,
32
- string[] | (string | null) // | number // TODO: count
33
- > & {
34
- display: 'select' | 'count' | 'table'
35
- listKey: string
36
- refListKey: string
37
- refFieldKey?: string
38
- refLabelField: string
39
- refSearchFields: string[]
40
- hideCreate: boolean
41
- many: boolean
42
- columns: string[] | null
43
- initialSort: ListSortDescriptor<string> | null
44
- selectFilter: Record<string, any> | null
45
- selectSort: ListSortDescriptor<string> | null
46
- }
1
+ import type { FieldController, CollectionSortDescriptor } from '../../../../types'
2
+
3
+ export type RelationshipValue = {
4
+ id: string
5
+ label: string | null
6
+ data?: Record<string, unknown>
7
+ built: undefined | boolean
8
+ }
9
+
10
+ export type SingleRelationshipValue = {
11
+ kind: 'one'
12
+ id: null | string
13
+ initialValue: RelationshipValue | null
14
+ value: RelationshipValue | null
15
+ }
16
+
17
+ export type ManyRelationshipValue = {
18
+ kind: 'many'
19
+ id: null | string
20
+ initialValue: RelationshipValue[]
21
+ value: RelationshipValue[]
22
+ }
23
+
24
+ export type CountRelationshipValue = {
25
+ kind: 'count'
26
+ id: string
27
+ count: number
28
+ }
29
+
30
+ export type RelationshipController = FieldController<
31
+ ManyRelationshipValue | SingleRelationshipValue | CountRelationshipValue,
32
+ string[] | (string | null) // | number // TODO: count
33
+ > & {
34
+ display: 'select' | 'count' | 'table'
35
+ listKey: string
36
+ refListKey: string
37
+ refFieldKey?: string
38
+ refLabelField: string
39
+ refSearchFields: string[]
40
+ hideCreate: boolean
41
+ many: boolean
42
+ columns: string[] | null
43
+ initialSort: CollectionSortDescriptor<string> | null
44
+ selectFilter: Record<string, any> | null
45
+ selectSort: CollectionSortDescriptor<string> | null
46
+ }
@@ -1,185 +1,185 @@
1
- import { useEffect, useMemo, useState } from 'react'
2
- import isDeepEqual from 'fast-deep-equal'
3
-
4
- import type { ListMeta, ListSortDescriptor } from '../../../../types'
5
- import {
6
- type TypedDocumentNode,
7
- ApolloClient,
8
- gql,
9
- InMemoryCache,
10
- useApolloClient,
11
- useQuery,
12
- } from '../../../../admin-ui/apollo'
13
- import { useSearchFilter } from './useFilter'
14
- import type { RelationshipValue } from './types'
15
-
16
- function useDebouncedValue<T>(value: T, limitMs: number) {
17
- const [debouncedValue, setDebouncedValue] = useState(() => value)
18
-
19
- useEffect(() => {
20
- const timeout = setTimeout(() => {
21
- setDebouncedValue(() => value)
22
- }, limitMs)
23
- return () => clearTimeout(timeout)
24
- }, [value, limitMs])
25
-
26
- return debouncedValue
27
- }
28
-
29
- export function useApolloQuery(args: {
30
- labelField: string
31
- list: ListMeta
32
- searchFields: string[]
33
- sort?: ListSortDescriptor<string> | null
34
- filter?: Record<string, any> | null
35
- state:
36
- | { kind: 'many'; value: RelationshipValue[] }
37
- | { kind: 'one'; value: RelationshipValue | null }
38
- }) {
39
- const { labelField, list, searchFields, state } = args
40
- const [search, setSearch] = useState(() => {
41
- if (state.kind === 'one' && state.value?.label) return state.value?.label
42
- return ''
43
- })
44
-
45
- const QUERY: TypedDocumentNode<
46
- { items: { id: string; label: string | null }[]; count: number },
47
- {
48
- where: Record<string, any>
49
- take: number
50
- skip: number
51
- orderBy: Record<string, any> | undefined
52
- }
53
- > = gql`
54
- query RelationshipSelect($where: ${list.graphql.names.whereInputName}!, $take: Int!, $skip: Int!, $orderBy: [${list.graphql.names.listOrderName}!]) {
55
- items: ${list.graphql.names.listQueryName}(where: $where, take: $take, skip: $skip, orderBy: $orderBy) {
56
- id: id
57
- label: ${labelField}
58
- }
59
- count: ${list.graphql.names.listQueryCountName}(where: $where)
60
- }
61
- `
62
-
63
- const debouncedSearch = useDebouncedValue(search, 200)
64
- const manipulatedSearch =
65
- state.kind === 'one' && state.value?.label === debouncedSearch ? '' : debouncedSearch
66
-
67
- const searchFilter = useSearchFilter(manipulatedSearch, list, searchFields)
68
- const _where = {
69
- OR: searchFilter,
70
- }
71
- const where = args.filter ? { AND: [_where, args.filter] } : _where
72
-
73
- const orderBy = useMemo(() => {
74
- return args.sort ? { [args.sort.field]: args.sort.direction.toLowerCase() } : undefined
75
- }, [args.sort])
76
-
77
- const link = useApolloClient().link
78
- // we're using a local apollo client here because writing a global implementation of the typePolicies
79
- // would require making assumptions about how pagination should work which won't always be right
80
- const apolloClient = useMemo(
81
- () =>
82
- new ApolloClient({
83
- link,
84
- cache: new InMemoryCache({
85
- typePolicies: {
86
- Query: {
87
- fields: {
88
- [list.graphql.names.listQueryName]: {
89
- keyArgs: ['where'],
90
- merge: (existing: readonly unknown[], incoming: readonly unknown[], { args }) => {
91
- const merged = existing ? existing.slice() : []
92
- const { skip } = args!
93
- for (let i = 0; i < incoming.length; ++i) {
94
- merged[skip + i] = incoming[i]
95
- }
96
- return merged
97
- },
98
- },
99
- },
100
- },
101
- },
102
- }),
103
- }),
104
- [link, list.graphql.names.listQueryName]
105
- )
106
-
107
- const initialItemsToLoad = Math.min(list.pageSize, 10)
108
- const subsequentItemsToLoad = Math.min(list.pageSize, 50)
109
- const { data, previousData, error, loading, fetchMore } = useQuery(QUERY, {
110
- fetchPolicy: 'network-only',
111
- variables: { where, take: initialItemsToLoad, skip: 0, orderBy },
112
- client: apolloClient,
113
- })
114
-
115
- // we want to avoid fetching more again and `loading` from Apollo
116
- // doesn't seem to become true when fetching more
117
- const [lastFetchMore, setLastFetchMore] = useState<{
118
- where: Record<string, any>
119
- list: ListMeta
120
- skip: number
121
- } | null>(null)
122
-
123
- const count = data?.count || 0
124
- const onLoadMore = () => {
125
- const skip = data?.items.length
126
- if (
127
- !loading &&
128
- skip &&
129
- data.items.length < count &&
130
- (!isDeepEqual(lastFetchMore?.where, where) ||
131
- lastFetchMore?.list !== list ||
132
- lastFetchMore?.skip !== skip)
133
- ) {
134
- const QUERY: TypedDocumentNode<
135
- { items: { id: string; label: string | null }[] },
136
- {
137
- where: Record<string, any>
138
- take: number
139
- skip: number
140
- orderBy: Record<string, any> | undefined
141
- }
142
- > = gql`
143
- query RelationshipSelectMore($where: ${list.graphql.names.whereInputName}!, $take: Int!, $skip: Int!, $orderBy: [${list.graphql.names.listOrderName}!]) {
144
- items: ${list.graphql.names.listQueryName}(where: $where, take: $take, skip: $skip, orderBy: $orderBy) {
145
- id
146
- label: ${labelField}
147
- }
148
- }
149
- `
150
- setLastFetchMore({ list, skip, where })
151
- fetchMore({
152
- query: QUERY,
153
- variables: {
154
- where,
155
- take: subsequentItemsToLoad,
156
- skip,
157
- orderBy,
158
- },
159
- })
160
- .then(() => setLastFetchMore(null))
161
- .catch(() => setLastFetchMore(null))
162
- }
163
- }
164
-
165
- return {
166
- data: loading ? previousData : data,
167
- error,
168
- loading,
169
- loadingState: getLoadingState({ loading, search }),
170
- search,
171
- setSearch,
172
- onLoadMore,
173
- }
174
- }
175
-
176
- function getLoadingState(options: { loading: boolean; search: string }): LoadingState {
177
- if (options.loading) {
178
- if (options.search.length) return 'filtering'
179
- return 'loading'
180
- }
181
-
182
- return 'idle'
183
- }
184
-
185
- type LoadingState = 'loading' | 'sorting' | 'loadingMore' | 'error' | 'idle' | 'filtering'
1
+ import { useEffect, useMemo, useState } from 'react'
2
+ import isDeepEqual from 'fast-deep-equal'
3
+
4
+ import type { CollectionMeta, CollectionSortDescriptor } from '../../../../types'
5
+ import {
6
+ type TypedDocumentNode,
7
+ ApolloClient,
8
+ gql,
9
+ InMemoryCache,
10
+ useApolloClient,
11
+ useQuery,
12
+ } from '../../../../admin-ui/apollo'
13
+ import { useSearchFilter } from './useFilter'
14
+ import type { RelationshipValue } from './types'
15
+
16
+ function useDebouncedValue<T>(value: T, limitMs: number) {
17
+ const [debouncedValue, setDebouncedValue] = useState(() => value)
18
+
19
+ useEffect(() => {
20
+ const timeout = setTimeout(() => {
21
+ setDebouncedValue(() => value)
22
+ }, limitMs)
23
+ return () => clearTimeout(timeout)
24
+ }, [value, limitMs])
25
+
26
+ return debouncedValue
27
+ }
28
+
29
+ export function useApolloQuery(args: {
30
+ labelField: string
31
+ list: CollectionMeta
32
+ searchFields: string[]
33
+ sort?: CollectionSortDescriptor<string> | null
34
+ filter?: Record<string, any> | null
35
+ state:
36
+ | { kind: 'many'; value: RelationshipValue[] }
37
+ | { kind: 'one'; value: RelationshipValue | null }
38
+ }) {
39
+ const { labelField, list, searchFields, state } = args
40
+ const [search, setSearch] = useState(() => {
41
+ if (state.kind === 'one' && state.value?.label) return state.value?.label
42
+ return ''
43
+ })
44
+
45
+ const QUERY: TypedDocumentNode<
46
+ { items: { id: string; label: string | null }[]; count: number },
47
+ {
48
+ where: Record<string, any>
49
+ take: number
50
+ skip: number
51
+ orderBy: Record<string, any> | undefined
52
+ }
53
+ > = gql`
54
+ query RelationshipSelect($where: ${list.graphql.names.whereInputName}!, $take: Int!, $skip: Int!, $orderBy: [${list.graphql.names.listOrderName}!]) {
55
+ items: ${list.graphql.names.listQueryName}(where: $where, take: $take, skip: $skip, orderBy: $orderBy) {
56
+ id: id
57
+ label: ${labelField}
58
+ }
59
+ count: ${list.graphql.names.listQueryCountName}(where: $where)
60
+ }
61
+ `
62
+
63
+ const debouncedSearch = useDebouncedValue(search, 200)
64
+ const manipulatedSearch =
65
+ state.kind === 'one' && state.value?.label === debouncedSearch ? '' : debouncedSearch
66
+
67
+ const searchFilter = useSearchFilter(manipulatedSearch, list, searchFields)
68
+ const _where = {
69
+ OR: searchFilter,
70
+ }
71
+ const where = args.filter ? { AND: [_where, args.filter] } : _where
72
+
73
+ const orderBy = useMemo(() => {
74
+ return args.sort ? { [args.sort.field]: args.sort.direction.toLowerCase() } : undefined
75
+ }, [args.sort])
76
+
77
+ const link = useApolloClient().link
78
+ // we're using a local apollo client here because writing a global implementation of the typePolicies
79
+ // would require making assumptions about how pagination should work which won't always be right
80
+ const apolloClient = useMemo(
81
+ () =>
82
+ new ApolloClient({
83
+ link,
84
+ cache: new InMemoryCache({
85
+ typePolicies: {
86
+ Query: {
87
+ fields: {
88
+ [list.graphql.names.listQueryName]: {
89
+ keyArgs: ['where'],
90
+ merge: (existing: readonly unknown[], incoming: readonly unknown[], { args }) => {
91
+ const merged = existing ? existing.slice() : []
92
+ const { skip } = args!
93
+ for (let i = 0; i < incoming.length; ++i) {
94
+ merged[skip + i] = incoming[i]
95
+ }
96
+ return merged
97
+ },
98
+ },
99
+ },
100
+ },
101
+ },
102
+ }),
103
+ }),
104
+ [link, list.graphql.names.listQueryName]
105
+ )
106
+
107
+ const initialItemsToLoad = Math.min(list.pageSize, 10)
108
+ const subsequentItemsToLoad = Math.min(list.pageSize, 50)
109
+ const { data, previousData, error, loading, fetchMore } = useQuery(QUERY, {
110
+ fetchPolicy: 'network-only',
111
+ variables: { where, take: initialItemsToLoad, skip: 0, orderBy },
112
+ client: apolloClient,
113
+ })
114
+
115
+ // we want to avoid fetching more again and `loading` from Apollo
116
+ // doesn't seem to become true when fetching more
117
+ const [lastFetchMore, setLastFetchMore] = useState<{
118
+ where: Record<string, any>
119
+ list: CollectionMeta
120
+ skip: number
121
+ } | null>(null)
122
+
123
+ const count = data?.count || 0
124
+ const onLoadMore = () => {
125
+ const skip = data?.items.length
126
+ if (
127
+ !loading &&
128
+ skip &&
129
+ data.items.length < count &&
130
+ (!isDeepEqual(lastFetchMore?.where, where) ||
131
+ lastFetchMore?.list !== list ||
132
+ lastFetchMore?.skip !== skip)
133
+ ) {
134
+ const QUERY: TypedDocumentNode<
135
+ { items: { id: string; label: string | null }[] },
136
+ {
137
+ where: Record<string, any>
138
+ take: number
139
+ skip: number
140
+ orderBy: Record<string, any> | undefined
141
+ }
142
+ > = gql`
143
+ query RelationshipSelectMore($where: ${list.graphql.names.whereInputName}!, $take: Int!, $skip: Int!, $orderBy: [${list.graphql.names.listOrderName}!]) {
144
+ items: ${list.graphql.names.listQueryName}(where: $where, take: $take, skip: $skip, orderBy: $orderBy) {
145
+ id
146
+ label: ${labelField}
147
+ }
148
+ }
149
+ `
150
+ setLastFetchMore({ list, skip, where })
151
+ fetchMore({
152
+ query: QUERY,
153
+ variables: {
154
+ where,
155
+ take: subsequentItemsToLoad,
156
+ skip,
157
+ orderBy,
158
+ },
159
+ })
160
+ .then(() => setLastFetchMore(null))
161
+ .catch(() => setLastFetchMore(null))
162
+ }
163
+ }
164
+
165
+ return {
166
+ data: loading ? previousData : data,
167
+ error,
168
+ loading,
169
+ loadingState: getLoadingState({ loading, search }),
170
+ search,
171
+ setSearch,
172
+ onLoadMore,
173
+ }
174
+ }
175
+
176
+ function getLoadingState(options: { loading: boolean; search: string }): LoadingState {
177
+ if (options.loading) {
178
+ if (options.search.length) return 'filtering'
179
+ return 'loading'
180
+ }
181
+
182
+ return 'idle'
183
+ }
184
+
185
+ type LoadingState = 'loading' | 'sorting' | 'loadingMore' | 'error' | 'idle' | 'filtering'