@tinacms/app 0.0.11 → 0.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/appFiles/index.dev.html +23 -0
- package/appFiles/lib/formify/formify-utils.ts +299 -0
- package/appFiles/lib/formify/formify.ts +574 -0
- package/appFiles/lib/formify/index.ts +67 -0
- package/appFiles/lib/formify/types.ts +130 -0
- package/appFiles/lib/formify/util.ts +598 -0
- package/appFiles/lib/machines/document-machine.ts +351 -0
- package/appFiles/lib/machines/query-machine.ts +728 -0
- package/appFiles/lib/machines/util.ts +205 -0
- package/appFiles/src/App.tsx +12 -3
- package/appFiles/src/index.css +3 -0
- package/appFiles/src/main.tsx +1 -0
- package/appFiles/src/preview/index.tsx +150 -0
- package/appFiles/src/vite-env.d.ts +2 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +332 -10
- package/package.json +24 -10
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
Copyright 2021 Forestry.io Holdings, Inc.
|
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License.
|
|
5
|
+
You may obtain a copy of the License at
|
|
6
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
Unless required by applicable law or agreed to in writing, software
|
|
8
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
9
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
10
|
+
See the License for the specific language governing permissions and
|
|
11
|
+
limitations under the License.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type * as G from 'graphql'
|
|
15
|
+
|
|
16
|
+
export type FormifiedDocumentNode = {
|
|
17
|
+
id: string
|
|
18
|
+
_internalSys: {
|
|
19
|
+
path: string
|
|
20
|
+
relativePath: string
|
|
21
|
+
collection: {
|
|
22
|
+
name
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
_values: object
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type ChangeMutation = { type: 'change' }
|
|
29
|
+
export type ReferenceChangeMutation = { type: 'referenceChange' }
|
|
30
|
+
export type InsertMutation = { type: 'insert'; at: number }
|
|
31
|
+
export type MoveMutation = { type: 'move'; from: number; to: number }
|
|
32
|
+
export type RemoveMutation = { type: 'remove'; at: number }
|
|
33
|
+
export type ResetMutation = { type: 'reset' }
|
|
34
|
+
export type GlobalMutation = { type: 'global' }
|
|
35
|
+
|
|
36
|
+
type MutationType =
|
|
37
|
+
| ChangeMutation
|
|
38
|
+
| ReferenceChangeMutation
|
|
39
|
+
| InsertMutation
|
|
40
|
+
| MoveMutation
|
|
41
|
+
| RemoveMutation
|
|
42
|
+
| ResetMutation
|
|
43
|
+
| GlobalMutation
|
|
44
|
+
|
|
45
|
+
export type OnChangeEvent = {
|
|
46
|
+
type: 'forms:fields:onChange' | 'forms:reset'
|
|
47
|
+
value: unknown
|
|
48
|
+
previousValue: unknown
|
|
49
|
+
mutationType: MutationType
|
|
50
|
+
formId: string
|
|
51
|
+
field: {
|
|
52
|
+
data: {
|
|
53
|
+
tinaField: {
|
|
54
|
+
name: string
|
|
55
|
+
type: 'string' | 'reference' | 'object'
|
|
56
|
+
list?: boolean
|
|
57
|
+
parentTypename: string
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
name: string
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export type ChangeSet = {
|
|
65
|
+
path: string
|
|
66
|
+
// event: OnChangeEvent
|
|
67
|
+
value: unknown
|
|
68
|
+
formId: string
|
|
69
|
+
fieldDefinition: {
|
|
70
|
+
name: string
|
|
71
|
+
type: 'string' | 'reference' | 'object'
|
|
72
|
+
list?: boolean
|
|
73
|
+
}
|
|
74
|
+
mutationType: MutationType
|
|
75
|
+
name: string
|
|
76
|
+
displaceIndex?: boolean // FIXME: this should be the index, not a boolean
|
|
77
|
+
formNode: FormNode
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export type BlueprintPath = {
|
|
81
|
+
name: string
|
|
82
|
+
alias: string
|
|
83
|
+
parentTypename?: string
|
|
84
|
+
list?: boolean
|
|
85
|
+
isNode?: boolean
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export type DocumentBlueprint = {
|
|
89
|
+
/** The stringified representation of a path relative to root or it's parent document */
|
|
90
|
+
id: string
|
|
91
|
+
/** The path to a field node */
|
|
92
|
+
path: BlueprintPath[]
|
|
93
|
+
/** The GraphQL SelectionNode, useful for re-fetching the given node */
|
|
94
|
+
selection: G.SelectionNode
|
|
95
|
+
fields: FieldBlueprint[]
|
|
96
|
+
/** For now, only top-level, non-list nodes will be shown in the sidebar */
|
|
97
|
+
showInSidebar: boolean
|
|
98
|
+
/** these 2 are not traditional GraphQL fields but need be kept in-sync regardless */
|
|
99
|
+
hasDataJSONField: boolean
|
|
100
|
+
hasValuesField: boolean
|
|
101
|
+
/** this blueprint is not the result of a reference */
|
|
102
|
+
isTopLevel: boolean
|
|
103
|
+
}
|
|
104
|
+
export type FieldBlueprint = {
|
|
105
|
+
/** The stringified representation of a path relative to root or it's parent document */
|
|
106
|
+
id: string
|
|
107
|
+
documentBlueprintId: string
|
|
108
|
+
/** The path to a field node */
|
|
109
|
+
path: BlueprintPath[]
|
|
110
|
+
}
|
|
111
|
+
export type FormNode = {
|
|
112
|
+
/** The stringified path with location values injected (eg. 'getBlockPageList.edges.0.node.data.social.1.relatedPage') */
|
|
113
|
+
documentFormId: string
|
|
114
|
+
documentBlueprintId: string
|
|
115
|
+
/** Coordinates for the DocumentBlueprint's '[]' values */
|
|
116
|
+
location: number[]
|
|
117
|
+
}
|
|
118
|
+
/** The document ID is the true ID 'content/pages/hello-world.md') */
|
|
119
|
+
|
|
120
|
+
export type State = {
|
|
121
|
+
schema: G.GraphQLSchema
|
|
122
|
+
query: G.DocumentNode
|
|
123
|
+
queryString: string
|
|
124
|
+
status: 'idle' | 'initialized' | 'formified' | 'ready' | 'done'
|
|
125
|
+
count: number
|
|
126
|
+
data: object
|
|
127
|
+
changeSets: ChangeSet[]
|
|
128
|
+
blueprints: DocumentBlueprint[]
|
|
129
|
+
formNodes: FormNode[]
|
|
130
|
+
}
|
|
@@ -0,0 +1,598 @@
|
|
|
1
|
+
/**
|
|
2
|
+
Copyright 2021 Forestry.io Holdings, Inc.
|
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License.
|
|
5
|
+
You may obtain a copy of the License at
|
|
6
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
Unless required by applicable law or agreed to in writing, software
|
|
8
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
9
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
10
|
+
See the License for the specific language governing permissions and
|
|
11
|
+
limitations under the License.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { Form, Field, FormOptions, TinaCMS, AnyField, TinaField } from 'tinacms'
|
|
15
|
+
import { getIn } from 'final-form'
|
|
16
|
+
|
|
17
|
+
import type {
|
|
18
|
+
DocumentBlueprint,
|
|
19
|
+
FieldBlueprint,
|
|
20
|
+
OnChangeEvent,
|
|
21
|
+
FormNode,
|
|
22
|
+
State,
|
|
23
|
+
ChangeSet,
|
|
24
|
+
BlueprintPath,
|
|
25
|
+
} from './types'
|
|
26
|
+
import { TinaSchema, resolveForm } from '@tinacms/schema-tools'
|
|
27
|
+
|
|
28
|
+
// import {
|
|
29
|
+
// generateFormCreators,
|
|
30
|
+
// formifyCallback,
|
|
31
|
+
// transformDocumentIntoMutationRequestPayload,
|
|
32
|
+
// onSubmitArgs,
|
|
33
|
+
// } from '../use-graphql-forms'
|
|
34
|
+
|
|
35
|
+
interface RecursiveFormifiedDocumentNode<T extends object>
|
|
36
|
+
extends Array<RecursiveFormifiedDocumentNode<T> | T> {}
|
|
37
|
+
export const getValueForBlueprint = <T extends object>(
|
|
38
|
+
state: object,
|
|
39
|
+
path: string
|
|
40
|
+
): RecursiveFormifiedDocumentNode<T> | T => {
|
|
41
|
+
const pathArray = path.split('.')
|
|
42
|
+
let latest: object | undefined = state
|
|
43
|
+
pathArray.every((item, index) => {
|
|
44
|
+
if (item === '[]') {
|
|
45
|
+
const restOfItems = pathArray.slice(index + 1)
|
|
46
|
+
if (latest) {
|
|
47
|
+
const next: (RecursiveFormifiedDocumentNode<object> | object)[] = []
|
|
48
|
+
if (Array.isArray(latest)) {
|
|
49
|
+
latest.forEach((latest2) => {
|
|
50
|
+
const res = getValueForBlueprint(latest2, restOfItems.join('.'))
|
|
51
|
+
next.push(res)
|
|
52
|
+
})
|
|
53
|
+
} else {
|
|
54
|
+
throw new Error(`Expected value to be an array for "[]" item`)
|
|
55
|
+
}
|
|
56
|
+
if (next.length > 0) {
|
|
57
|
+
latest = next
|
|
58
|
+
} else {
|
|
59
|
+
latest = undefined
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return false
|
|
63
|
+
} else {
|
|
64
|
+
if (latest) {
|
|
65
|
+
latest = latest[item]
|
|
66
|
+
} else {
|
|
67
|
+
latest = undefined
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return true
|
|
71
|
+
})
|
|
72
|
+
// @ts-ignore
|
|
73
|
+
return latest
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const getNamePath = (path: BlueprintPath[]) => {
|
|
77
|
+
const namePath: string[] = []
|
|
78
|
+
path.forEach((p) => {
|
|
79
|
+
namePath.push(p.name)
|
|
80
|
+
if (p.list) {
|
|
81
|
+
namePath.push('[]')
|
|
82
|
+
}
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
return namePath.join('.')
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export const getBlueprintNamePath = (
|
|
89
|
+
blueprint: Pick<DocumentBlueprint, 'path'>,
|
|
90
|
+
disambiguator?: boolean
|
|
91
|
+
) => {
|
|
92
|
+
const namePath: string[] = []
|
|
93
|
+
blueprint.path.forEach((p) => {
|
|
94
|
+
if (disambiguator) {
|
|
95
|
+
if (p.parentTypename) {
|
|
96
|
+
namePath.push(p.parentTypename)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
namePath.push(p.name)
|
|
100
|
+
if (p.list) {
|
|
101
|
+
namePath.push('[]')
|
|
102
|
+
}
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
return namePath.join('.')
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
interface RecursiveFormifiedDocumentNode<T extends object>
|
|
109
|
+
extends Array<RecursiveFormifiedDocumentNode<T> | T> {}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Returns the name of the field. In the example query, `title` and `t` would both be blueprint fields
|
|
113
|
+
*
|
|
114
|
+
* ```graphql
|
|
115
|
+
* {
|
|
116
|
+
* getPostDocument(relativePath: $relativePath) {
|
|
117
|
+
* data {
|
|
118
|
+
* title,
|
|
119
|
+
* t: title # here `t` is an alias for title
|
|
120
|
+
* }
|
|
121
|
+
* }
|
|
122
|
+
* }
|
|
123
|
+
* ```
|
|
124
|
+
*/
|
|
125
|
+
export const getFieldNameOrAlias = (fieldBlueprint: FieldBlueprint) => {
|
|
126
|
+
return fieldBlueprint.path[fieldBlueprint.path.length - 1].alias
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export const spliceLocation = (string: string, location: number[] | null) => {
|
|
130
|
+
const accum: (string | number)[] = []
|
|
131
|
+
let counter = 0
|
|
132
|
+
|
|
133
|
+
string.split('.').forEach((item) => {
|
|
134
|
+
if (item === '[]') {
|
|
135
|
+
if (!location) {
|
|
136
|
+
throw new Error(`Error splicing without location`)
|
|
137
|
+
}
|
|
138
|
+
accum.push(location[counter])
|
|
139
|
+
counter++
|
|
140
|
+
} else {
|
|
141
|
+
accum.push(item)
|
|
142
|
+
}
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
return accum.join('.')
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export const getPathToChange = (
|
|
149
|
+
documentBlueprint: DocumentBlueprint | FieldBlueprint,
|
|
150
|
+
formNode: FormNode,
|
|
151
|
+
event: OnChangeEvent
|
|
152
|
+
) => {
|
|
153
|
+
const fieldName = event.field.name
|
|
154
|
+
const location = [...formNode.location, ...stripIndices(fieldName)]
|
|
155
|
+
const accum: (string | number)[] = []
|
|
156
|
+
let counter = 0
|
|
157
|
+
documentBlueprint.path.forEach((item) => {
|
|
158
|
+
accum.push(item.alias)
|
|
159
|
+
if (item.list) {
|
|
160
|
+
// If there's no match we're assuming it's a list field, and not an item within the list field
|
|
161
|
+
// eg. blocks vs blocks.0
|
|
162
|
+
if (location[counter] !== undefined) {
|
|
163
|
+
accum.push(location[counter])
|
|
164
|
+
counter++
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
return accum.join('.')
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export const formNodeId = (formNode: FormNode) => {
|
|
173
|
+
return (
|
|
174
|
+
spliceLocation(formNode.documentBlueprintId, formNode.location) +
|
|
175
|
+
formNode.documentFormId
|
|
176
|
+
)
|
|
177
|
+
}
|
|
178
|
+
export const formNodePath = (formNode: FormNode) => {
|
|
179
|
+
return spliceLocation(formNode.documentBlueprintId, formNode.location)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export const formNodeNotIn = (formNode: FormNode, formNodes: FormNode[]) => {
|
|
183
|
+
return !formNodes.find((fn) => formNodeId(fn) === formNodeId(formNode))
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export const sequential = async <A, B>(
|
|
187
|
+
items: A[] | undefined,
|
|
188
|
+
callback: (args: A, idx: number) => Promise<B>
|
|
189
|
+
) => {
|
|
190
|
+
const accum: B[] = []
|
|
191
|
+
if (!items) {
|
|
192
|
+
return []
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const reducePromises = async (previous: Promise<B>, endpoint: A) => {
|
|
196
|
+
const prev = await previous
|
|
197
|
+
// initial value will be undefined
|
|
198
|
+
if (prev) {
|
|
199
|
+
accum.push(prev)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return callback(endpoint, accum.length)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// @ts-ignore FIXME: this can be properly typed
|
|
206
|
+
const result = await items.reduce(reducePromises, Promise.resolve())
|
|
207
|
+
if (result) {
|
|
208
|
+
// @ts-ignore FIXME: this can be properly typed
|
|
209
|
+
accum.push(result)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return accum
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const getFormNodesStartingWith = (string: string, state: State) => {
|
|
216
|
+
return state.formNodes.filter((subFormNode) => {
|
|
217
|
+
return subFormNode.documentBlueprintId.startsWith(string)
|
|
218
|
+
})
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export const getFormNodesForField = (
|
|
222
|
+
fieldBlueprint: FieldBlueprint,
|
|
223
|
+
formNode: FormNode,
|
|
224
|
+
event: OnChangeEvent,
|
|
225
|
+
state: State
|
|
226
|
+
) => {
|
|
227
|
+
const pathToChange = getPathToChange(fieldBlueprint, formNode, event)
|
|
228
|
+
const formNodes = getFormNodesStartingWith(fieldBlueprint.id, state)
|
|
229
|
+
const eventLocation = [
|
|
230
|
+
...formNode.location,
|
|
231
|
+
...stripIndices(event.field.name),
|
|
232
|
+
]
|
|
233
|
+
// const existing = getIn(state.data, pathToChange)
|
|
234
|
+
return { pathToChange, formNodes, eventLocation, existing }
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export const getBlueprintAliasPath = (blueprint: DocumentBlueprint) => {
|
|
238
|
+
const namePath: string[] = []
|
|
239
|
+
const aliasPath: string[] = []
|
|
240
|
+
blueprint.path.forEach((p) => {
|
|
241
|
+
namePath.push(p.name)
|
|
242
|
+
aliasPath.push(p.alias)
|
|
243
|
+
if (p.list) {
|
|
244
|
+
namePath.push('[]')
|
|
245
|
+
aliasPath.push('[]')
|
|
246
|
+
}
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
return aliasPath.join('.')
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export const getFieldAliasForBlueprint = (path: BlueprintPath[]) => {
|
|
253
|
+
const reversePath = [...path].reverse()
|
|
254
|
+
const accum: string[] = []
|
|
255
|
+
reversePath.every((item, index) => {
|
|
256
|
+
if (index === 0) {
|
|
257
|
+
if (item.list) {
|
|
258
|
+
accum.push('[]')
|
|
259
|
+
}
|
|
260
|
+
accum.push(item.alias)
|
|
261
|
+
} else {
|
|
262
|
+
if (item.isNode) {
|
|
263
|
+
return false
|
|
264
|
+
}
|
|
265
|
+
if (item.list) {
|
|
266
|
+
accum.push('[]')
|
|
267
|
+
}
|
|
268
|
+
accum.push(item.alias)
|
|
269
|
+
}
|
|
270
|
+
return true
|
|
271
|
+
})
|
|
272
|
+
return accum.reverse().join('.')
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
*
|
|
277
|
+
* Determines the appropriate fields which should recieve an update from a form change
|
|
278
|
+
*
|
|
279
|
+
* In cases where there's polymorphic blocks, it's possible that an update would affect
|
|
280
|
+
* multiple locations that it shouldn't.
|
|
281
|
+
*
|
|
282
|
+
* An OnChange event name can look like: `blocks.2.title`, but if there are 2 block elements
|
|
283
|
+
* with a field of the same name, an event name it wouldn't be enough information for us.
|
|
284
|
+
*
|
|
285
|
+
* To get around this, the event sends the current `typename` along with it, and we use that
|
|
286
|
+
* to determine where in our blueprint the value should be updated.
|
|
287
|
+
*
|
|
288
|
+
*/
|
|
289
|
+
export const getBlueprintFieldsForEvent = (
|
|
290
|
+
blueprint: DocumentBlueprint,
|
|
291
|
+
event: OnChangeEvent
|
|
292
|
+
) => {
|
|
293
|
+
return blueprint.fields
|
|
294
|
+
.filter((fbp) => {
|
|
295
|
+
if (getBlueprintNamePath(fbp) === getEventPath(event, blueprint)) {
|
|
296
|
+
return true
|
|
297
|
+
}
|
|
298
|
+
})
|
|
299
|
+
.filter((fbp) => {
|
|
300
|
+
return filterFieldBlueprintsByParentTypename(
|
|
301
|
+
fbp,
|
|
302
|
+
event.field.data.tinaField.parentTypename
|
|
303
|
+
)
|
|
304
|
+
})
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export const filterFieldBlueprintsByParentTypename = (
|
|
308
|
+
fbp: FieldBlueprint,
|
|
309
|
+
typename
|
|
310
|
+
) => {
|
|
311
|
+
let lastDisambiguator: string
|
|
312
|
+
|
|
313
|
+
fbp.path.forEach((path) => {
|
|
314
|
+
if (path.parentTypename) {
|
|
315
|
+
lastDisambiguator = path.parentTypename
|
|
316
|
+
}
|
|
317
|
+
})
|
|
318
|
+
if (lastDisambiguator) {
|
|
319
|
+
return typename === lastDisambiguator
|
|
320
|
+
} else {
|
|
321
|
+
return true
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
*
|
|
327
|
+
* Returns the path for the event, which uses `data.tinaField` metadata to
|
|
328
|
+
* determine the shape of the field. For polymorphic objects it's necessary
|
|
329
|
+
* to build-in the name of the GraphQL type that's receiving the change.
|
|
330
|
+
*
|
|
331
|
+
* Eg. when the title of a blocks "cta" template is changed, we might see an
|
|
332
|
+
* event path like:
|
|
333
|
+
* ```
|
|
334
|
+
* getPageDocument.data.blocks.0.PageBlocksCta.title
|
|
335
|
+
* ```
|
|
336
|
+
*/
|
|
337
|
+
const getEventPath = (
|
|
338
|
+
event: OnChangeEvent,
|
|
339
|
+
blueprint: DocumentBlueprint | FieldBlueprint
|
|
340
|
+
) => {
|
|
341
|
+
const stringArray = event.field.name.split('.')
|
|
342
|
+
const eventPath = stringArray
|
|
343
|
+
.map((item) => {
|
|
344
|
+
if (isNaN(Number(item))) {
|
|
345
|
+
return item
|
|
346
|
+
}
|
|
347
|
+
return `[]`
|
|
348
|
+
})
|
|
349
|
+
.join('.')
|
|
350
|
+
const items = [blueprint.id, eventPath]
|
|
351
|
+
const isList = event.field.data.tinaField.list
|
|
352
|
+
if (isList && !eventPath.endsWith('[]')) {
|
|
353
|
+
items.push(`[]`)
|
|
354
|
+
}
|
|
355
|
+
return items.join('.')
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
export const stripIndices = (string) => {
|
|
359
|
+
const accum: string[] = []
|
|
360
|
+
const stringArray = string.split('.')
|
|
361
|
+
stringArray.forEach((item) => {
|
|
362
|
+
if (isNaN(item)) {
|
|
363
|
+
} else {
|
|
364
|
+
accum.push(item)
|
|
365
|
+
}
|
|
366
|
+
})
|
|
367
|
+
|
|
368
|
+
return accum
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
export const replaceRealNum = (string) => {
|
|
372
|
+
const stringArray = string.split('.')
|
|
373
|
+
return stringArray
|
|
374
|
+
.map((item) => {
|
|
375
|
+
if (isNaN(item)) {
|
|
376
|
+
return item
|
|
377
|
+
}
|
|
378
|
+
return '[]'
|
|
379
|
+
})
|
|
380
|
+
.join('.')
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
export const getMatchName = ({
|
|
384
|
+
field,
|
|
385
|
+
prefix,
|
|
386
|
+
blueprint,
|
|
387
|
+
}: {
|
|
388
|
+
field: TinaField
|
|
389
|
+
prefix?: string
|
|
390
|
+
blueprint: DocumentBlueprint
|
|
391
|
+
}) => {
|
|
392
|
+
const fieldName = field.list ? `${field.name}.[]` : field.name
|
|
393
|
+
const blueprintName = getBlueprintNamePath(blueprint)
|
|
394
|
+
const extra: string[] = []
|
|
395
|
+
if (prefix) {
|
|
396
|
+
extra.push(prefix)
|
|
397
|
+
}
|
|
398
|
+
const matchName = [blueprintName, ...extra, fieldName].join('.')
|
|
399
|
+
return { matchName, fieldName }
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
export const getFormNodesFromEvent = (state: State, event: OnChangeEvent) => {
|
|
403
|
+
const formNodes = state.formNodes.filter(
|
|
404
|
+
(formNode) => formNode.documentFormId === event.formId
|
|
405
|
+
)
|
|
406
|
+
return formNodes
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
export const getBlueprintFieldPath = (
|
|
410
|
+
blueprint: DocumentBlueprint,
|
|
411
|
+
blueprintField: FieldBlueprint
|
|
412
|
+
) => {
|
|
413
|
+
return blueprintField.path.slice(blueprint.path.length)
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
export const printState = (state: State) => {
|
|
417
|
+
let string = ''
|
|
418
|
+
state.blueprints.forEach((blueprint) => {
|
|
419
|
+
let bpString = `# Blueprint\n`
|
|
420
|
+
bpString = bpString + `# ${blueprint.id}`
|
|
421
|
+
bpString = bpString + `\n#`
|
|
422
|
+
bpString = bpString + `\n# Documents for blueprint`
|
|
423
|
+
bpString = bpString + `\n# ================`
|
|
424
|
+
state.formNodes
|
|
425
|
+
.filter((formNode) => formNode.documentBlueprintId === blueprint.id)
|
|
426
|
+
.forEach((formNode) => {
|
|
427
|
+
const newString = `# ${formNode.documentFormId}${
|
|
428
|
+
blueprint.id.includes('[]')
|
|
429
|
+
? ` [${formNode.location.join(', ')}]`
|
|
430
|
+
: ``
|
|
431
|
+
}`
|
|
432
|
+
bpString = bpString + `\n${newString}`
|
|
433
|
+
})
|
|
434
|
+
|
|
435
|
+
bpString = bpString + `\n#`
|
|
436
|
+
bpString = bpString + `\n# Field blueprints`
|
|
437
|
+
bpString = bpString + `\n# ================`
|
|
438
|
+
blueprint.fields
|
|
439
|
+
.filter((fbp) => fbp.documentBlueprintId === blueprint.id)
|
|
440
|
+
.forEach((fbp) => {
|
|
441
|
+
bpString = bpString + `\n# ${getFieldAliasForBlueprint(fbp.path)}`
|
|
442
|
+
// bpString = bpString + `\n# ${getBlueprintAliasPath(fbp)}`
|
|
443
|
+
// bpString = bpString + `\n# ${fbp.id}`
|
|
444
|
+
})
|
|
445
|
+
string = string + `${bpString}\n`
|
|
446
|
+
string = string + '\n'
|
|
447
|
+
})
|
|
448
|
+
string = string + `\n${state.queryString}`
|
|
449
|
+
|
|
450
|
+
return string
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
export const printEvent = (event: OnChangeEvent) => {
|
|
454
|
+
return {
|
|
455
|
+
type: event.type,
|
|
456
|
+
value: event.value,
|
|
457
|
+
previousValue: event.previousValue,
|
|
458
|
+
mutationType: event.mutationType,
|
|
459
|
+
formId: event.formId,
|
|
460
|
+
field: {
|
|
461
|
+
data: event.field?.data,
|
|
462
|
+
name: event.field?.name,
|
|
463
|
+
},
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
export const getFormNodeBlueprint = (formNode: FormNode, state: State) => {
|
|
468
|
+
return state.blueprints.find((d) => d.id === formNode.documentBlueprintId)
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
export const getMoveMapping = (existing, from, to) => {
|
|
472
|
+
const newOrderObject: { [key: number]: number } = {}
|
|
473
|
+
if (from < to) {
|
|
474
|
+
existing.map((_, i) => {
|
|
475
|
+
if (i === from) {
|
|
476
|
+
newOrderObject[i] = to
|
|
477
|
+
return
|
|
478
|
+
}
|
|
479
|
+
if (i > from) {
|
|
480
|
+
if (i < to) {
|
|
481
|
+
newOrderObject[i] = i - 1
|
|
482
|
+
return
|
|
483
|
+
} else {
|
|
484
|
+
if (i === to) {
|
|
485
|
+
newOrderObject[i] = i - 1
|
|
486
|
+
return
|
|
487
|
+
}
|
|
488
|
+
newOrderObject[i] = i
|
|
489
|
+
return
|
|
490
|
+
}
|
|
491
|
+
} else {
|
|
492
|
+
newOrderObject[i] = i
|
|
493
|
+
return
|
|
494
|
+
}
|
|
495
|
+
})
|
|
496
|
+
} else {
|
|
497
|
+
existing.map((_, i) => {
|
|
498
|
+
if (i === from) {
|
|
499
|
+
newOrderObject[i] = to
|
|
500
|
+
return
|
|
501
|
+
}
|
|
502
|
+
if (i > to) {
|
|
503
|
+
if (i < from) {
|
|
504
|
+
newOrderObject[i] = i + 1
|
|
505
|
+
return
|
|
506
|
+
} else {
|
|
507
|
+
newOrderObject[i] = i
|
|
508
|
+
return
|
|
509
|
+
}
|
|
510
|
+
} else {
|
|
511
|
+
if (i === to) {
|
|
512
|
+
newOrderObject[i] = i + 1
|
|
513
|
+
return
|
|
514
|
+
}
|
|
515
|
+
newOrderObject[i] = i
|
|
516
|
+
return
|
|
517
|
+
}
|
|
518
|
+
})
|
|
519
|
+
}
|
|
520
|
+
return newOrderObject
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
export const matchLocation = (eventLocation: number[], formNode: FormNode) => {
|
|
524
|
+
return eventLocation.every((item, index) => item === formNode.location[index])
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
export const bumpLocation = (location: number[]) => {
|
|
528
|
+
return location.map((item, index) => {
|
|
529
|
+
// Bump the last item in the location array by 1, assuming "at" is always 0
|
|
530
|
+
if (index === location.length - 1) {
|
|
531
|
+
return item + 1
|
|
532
|
+
}
|
|
533
|
+
return item
|
|
534
|
+
})
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
export const maybeLowerLocation = (location: number[], at: number) => {
|
|
538
|
+
return location.map((item, index) => {
|
|
539
|
+
// Bump the last item in the location array by 1, assuming "at" is always 0
|
|
540
|
+
if (index === location.length - 1) {
|
|
541
|
+
return item < at ? item : item - 1
|
|
542
|
+
}
|
|
543
|
+
return item
|
|
544
|
+
})
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
export const matchesAt = (location: number[], at: number) => {
|
|
548
|
+
let matches = false
|
|
549
|
+
location.map((item, index) => {
|
|
550
|
+
// Bump the last item in the location array by 1, assuming "at" is always 0
|
|
551
|
+
if (index === location.length - 1) {
|
|
552
|
+
if (item === at) {
|
|
553
|
+
matches = true
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
})
|
|
557
|
+
return matches
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
export const swapLocation = (
|
|
561
|
+
location: number[],
|
|
562
|
+
mapping: { [key: number]: number }
|
|
563
|
+
) => {
|
|
564
|
+
return location.map((item, index) => {
|
|
565
|
+
if (index === location.length - 1) {
|
|
566
|
+
return mapping[item]
|
|
567
|
+
}
|
|
568
|
+
return item
|
|
569
|
+
})
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
*
|
|
574
|
+
* Gets the sub-fields for an object field, if it's a polymorphic
|
|
575
|
+
* object then we also need to get the __typename, though
|
|
576
|
+
* we should probably supply that regardless. The current downside
|
|
577
|
+
* of this is that it needs to come from the server because we
|
|
578
|
+
* have no way of knowing what it would be from the client-side
|
|
579
|
+
*/
|
|
580
|
+
export const getSubFields = (
|
|
581
|
+
changeSet: ChangeSet
|
|
582
|
+
): { fields: Field[]; __typename: string } => {
|
|
583
|
+
// @ts-ignore FIXME: import types from newly-defined defineSchema
|
|
584
|
+
const fields = changeSet.fieldDefinition.fields
|
|
585
|
+
? // @ts-ignore FIXME: import types from newly-defined defineSchema
|
|
586
|
+
changeSet.fieldDefinition.fields
|
|
587
|
+
: // @ts-ignore FIXME: import types from newly-defined defineSchema
|
|
588
|
+
changeSet.fieldDefinition.templates[changeSet.value[0]._template].fields
|
|
589
|
+
|
|
590
|
+
let __typename
|
|
591
|
+
// @ts-ignore FIXME: import types from newly-defined defineSchema
|
|
592
|
+
if (changeSet.fieldDefinition?.templates) {
|
|
593
|
+
// @ts-ignore FIXME: import types from newly-defined defineSchema
|
|
594
|
+
__typename = changeSet.fieldDefinition.typeMap[changeSet.value[0]._template]
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
return { fields, __typename }
|
|
598
|
+
}
|