@tinacms/app 0.0.21 → 0.0.23

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.
@@ -0,0 +1,348 @@
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
+ import { Client, Field, Form, FormOptions, TinaCMS } from 'tinacms'
14
+ import { assign, createMachine } from 'xstate'
15
+ import {
16
+ resolveForm,
17
+ Templateable,
18
+ TinaFieldEnriched,
19
+ TinaSchema,
20
+ } from 'tinacms'
21
+ import { sendParent } from 'xstate/lib/actions'
22
+
23
+ export type FieldType = Field & TinaFieldEnriched
24
+ export type FormValues = Record<string, unknown>
25
+ export type FormType = Form<FormValues, FieldType>
26
+
27
+ export type DataType = Record<string, unknown>
28
+
29
+ type Data = {
30
+ _internalValues: object
31
+ _internalSys: {
32
+ breadcrumbs: string[]
33
+ basename: string
34
+ filename: string
35
+ path: string
36
+ extension: string
37
+ relativePath: string
38
+ title?: string
39
+ template: string
40
+ collection: {
41
+ name: string
42
+ slug: string
43
+ label: string
44
+ path: string
45
+ format: string
46
+ matches?: string
47
+ templates?: object
48
+ fields?: object
49
+ __typename: string
50
+ }
51
+ }
52
+ }
53
+
54
+ type ContextType = {
55
+ id: string
56
+ data: null | Data
57
+ form: null | FormType
58
+ cms: TinaCMS
59
+ formifyCallback: (args: any, cms: TinaCMS) => Form
60
+ }
61
+
62
+ export const documentMachine =
63
+ /** @xstate-layout N4IgpgJg5mDOIC5QAoC2BDAxgCwJYDswBKAOgNwBdd0AbXALwKgGIIB7Qs-ANzYGswJNFjyFS5KrQZMEBXpnRUOAbQAMAXUSgADm1iVcHLSAAeiAEwAWSyUsA2VQE4AzAHYArABoQAT0QAORxJHEMdLd1d7d3NXWIBfOO9hHAJiLgMpRnwWMAAnXLZckm0aRQAzQtQhDBSxdMk6LKhZHjYFJXw1TSQQXX0O4zMEKxt7JzcvXwtVd2DQu2d-ZwBGVWX-dwTEkHw2CDhjZNE0iWpGpmM+gyMeoctzbz8EVaDLUNdAtfvIze2j1NIuTA6AgTx0emu+EGiEszlUtlcdncdnMkyey3WtlC-mWjmW7ncTlRCSSNWOpDyBVylwhA1uMLhCKRKLRFlxcxC4UiSJikRJIH+Yhp-UMUPpz38j0Q6y2cSAA */
64
+ createMachine(
65
+ {
66
+ tsTypes: {} as import('./document-machine.typegen').Typegen0,
67
+ schema: {
68
+ context: {} as ContextType,
69
+ services: {} as {
70
+ initializer: {
71
+ data: {
72
+ form: FormType | undefined
73
+ data: Data
74
+ }
75
+ }
76
+ scanner: {
77
+ data: {
78
+ data: object
79
+ }
80
+ }
81
+ },
82
+ events: {} as
83
+ | {
84
+ type: 'ADD_LOCATION'
85
+ value: string
86
+ }
87
+ | {
88
+ type: 'INIT'
89
+ }
90
+ | {
91
+ type: 'SCAN'
92
+ }
93
+ | {
94
+ type: 'RESCAN'
95
+ },
96
+ },
97
+ id: '(machine)',
98
+ initial: 'initializing',
99
+ states: {
100
+ initializing: {
101
+ invoke: {
102
+ src: 'initializer',
103
+ onDone: [
104
+ {
105
+ actions: ['storeFormAndData', 'notifyParent'],
106
+ target: 'ready',
107
+ },
108
+ ],
109
+ onError: [
110
+ {
111
+ actions: 'handleError',
112
+ target: 'error',
113
+ },
114
+ ],
115
+ },
116
+ },
117
+ ready: {},
118
+ error: {},
119
+ },
120
+ },
121
+ {
122
+ actions: {
123
+ notifyParent: sendParent((context) => {
124
+ return {
125
+ type: 'DOCUMENT_READY',
126
+ value: context.id,
127
+ }
128
+ }),
129
+ handleError: (_context, event) => {
130
+ console.error(event.data)
131
+ },
132
+ storeFormAndData: assign((context, event) => {
133
+ // context.cms.forms.add(event.data.form)
134
+ return { ...context, form: event.data.form, data: event.data.data }
135
+ }),
136
+ },
137
+ services: {
138
+ initializer: async (context) => {
139
+ const tina = context.cms.api.tina as Client
140
+ const response = await tina.request<{
141
+ node: Data
142
+ }>(
143
+ `query GetNode($id: String!) {
144
+ node(id: $id) {
145
+ ...on Document {
146
+ _internalValues: _values
147
+ _internalSys: _sys {
148
+ breadcrumbs
149
+ basename
150
+ filename
151
+ path
152
+ extension
153
+ relativePath
154
+ title
155
+ template
156
+ collection {
157
+ name
158
+ slug
159
+ label
160
+ path
161
+ format
162
+ matches
163
+ templates
164
+ fields
165
+ __typename
166
+ }
167
+ __typename
168
+ }
169
+ }
170
+ }
171
+ }`,
172
+ { variables: { id: context.id } }
173
+ )
174
+ const schema = context.cms.api.tina.schema as TinaSchema
175
+ if (!schema) {
176
+ throw new Error(`Schema must be provided`)
177
+ }
178
+ const collection = schema.getCollection(
179
+ response.node._internalSys.collection.name
180
+ )
181
+ let template: Templateable
182
+ if (collection.templates) {
183
+ template = collection.templates.find((template) => {
184
+ if (typeof template === 'string') {
185
+ throw new Error(`Global templates not supported`)
186
+ }
187
+ return template.name === response.node._internalSys.template
188
+ }) as Templateable
189
+ } else {
190
+ template = collection
191
+ }
192
+ if (!template) {
193
+ throw new Error(
194
+ `Unable to find template for node ${response.node._internalSys.path}`
195
+ )
196
+ }
197
+ const resolvedForm = resolveForm({
198
+ collection,
199
+ basename: response.node._internalSys.filename,
200
+ schema,
201
+ template,
202
+ })
203
+ const onSubmit = async (payload: Record<string, unknown>) => {
204
+ try {
205
+ const mutationString = `#graphql
206
+ mutation UpdateDocument($collection: String!, $relativePath: String!, $params: DocumentMutation!) {
207
+ updateDocument(collection: $collection, relativePath: $relativePath, params: $params) {
208
+ __typename
209
+ }
210
+ }
211
+ `
212
+
213
+ await context.cms.api.tina.request(mutationString, {
214
+ variables: {
215
+ collection: response.node._internalSys.collection.name,
216
+ relativePath: response.node._internalSys.relativePath,
217
+ params: schema.transformPayload(
218
+ response.node._internalSys.collection.name,
219
+ payload
220
+ ),
221
+ },
222
+ })
223
+ context.cms.alerts.success('Document saved!')
224
+ } catch (e) {
225
+ context.cms.alerts.error(
226
+ 'There was a problem saving your document'
227
+ )
228
+ console.error(e)
229
+ }
230
+ }
231
+ const formConfig = {
232
+ id: context.id,
233
+ label:
234
+ response.node._internalSys.title ||
235
+ response.node._internalSys.collection.label,
236
+ initialValues: response.node._internalValues,
237
+ fields: resolvedForm.fields,
238
+ onSubmit,
239
+ }
240
+ const formifyCallback = context.formifyCallback
241
+ const form = buildForm(
242
+ formConfig,
243
+ context.cms,
244
+ (args) => {
245
+ if (formifyCallback) {
246
+ return formifyCallback(args, context.cms)
247
+ } else {
248
+ return args.createForm(args.formConfig)
249
+ }
250
+ },
251
+ true,
252
+ onSubmit
253
+ )
254
+ return { form, data: response.node }
255
+ },
256
+ },
257
+ }
258
+ )
259
+
260
+ type FormCreator = (formConfig: FormOptions<any>) => Form
261
+ interface GlobalFormOptions {
262
+ icon?: any
263
+ layout: 'fullscreen' | 'popup'
264
+ }
265
+ type GlobalFormCreator = (
266
+ formConfig: FormOptions<any>,
267
+ options?: GlobalFormOptions
268
+ ) => Form
269
+ interface GlobalFormOptions {
270
+ icon?: any
271
+ layout: 'fullscreen' | 'popup'
272
+ }
273
+ export interface FormifyArgs {
274
+ formConfig: FormOptions<any>
275
+ createForm: FormCreator
276
+ createGlobalForm: FormCreator
277
+ skip?: () => void
278
+ }
279
+
280
+ export type formifyCallback = (args: FormifyArgs, cms: TinaCMS) => Form | void
281
+ export type onSubmitArgs = {
282
+ /**
283
+ * @deprecated queryString is actually a mutation string, use `mutationString` instead
284
+ */
285
+ queryString: string
286
+ mutationString: string
287
+ variables: object
288
+ }
289
+
290
+ export const generateFormCreators = (cms: TinaCMS, showInSidebar?: boolean) => {
291
+ const createForm = (formConfig: FormOptions<any, any>) => {
292
+ return new Form(formConfig)
293
+ }
294
+ const createGlobalForm: GlobalFormCreator = (
295
+ formConfig,
296
+ options?: { icon?: any; layout: 'fullscreen' | 'popup' }
297
+ ) => {
298
+ const form = new Form({
299
+ ...formConfig,
300
+ global: { global: true, ...options },
301
+ })
302
+ return form
303
+ }
304
+ return { createForm, createGlobalForm }
305
+ }
306
+
307
+ export const buildForm = (
308
+ formConfig: any,
309
+ cms: TinaCMS,
310
+ formify: formifyCallback,
311
+ showInSidebar: boolean = false,
312
+ onSubmit?: (args: any) => void
313
+ ): FormType | undefined => {
314
+ const { createForm, createGlobalForm } = generateFormCreators(
315
+ cms,
316
+ showInSidebar
317
+ )
318
+ const SKIPPED = 'SKIPPED'
319
+ let form
320
+ let skipped
321
+ const skip = () => {
322
+ skipped = SKIPPED
323
+ }
324
+ if (skipped) return
325
+
326
+ if (formify) {
327
+ form = formify(
328
+ {
329
+ formConfig,
330
+ createForm,
331
+ createGlobalForm,
332
+ skip,
333
+ },
334
+ cms
335
+ )
336
+ } else {
337
+ form = createForm(formConfig)
338
+ }
339
+
340
+ if (!(form instanceof Form)) {
341
+ if (skipped === SKIPPED) {
342
+ return
343
+ }
344
+ throw new Error('formify must return a form or skip()')
345
+ }
346
+
347
+ return form
348
+ }