@sitecore-feaas/clientside 0.3.0 → 0.3.2
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/README.md +8 -6
- package/build/components/FEAASComponent.js +8 -4
- package/build/components/FEAASComponent.js.map +1 -1
- package/build/components/FEAASElement.js +18 -4
- package/build/components/FEAASElement.js.map +1 -1
- package/build/dom/rendering.js +1 -1
- package/build/dom/rendering.js.map +1 -1
- package/build/headless.js +4 -5
- package/build/headless.js.map +1 -1
- package/build/ui/FEAASExternal.js +8 -0
- package/build/ui/FEAASExternal.js.map +1 -1
- package/build/ui/FEAASPicker.js +10 -1
- package/build/ui/FEAASPicker.js.map +1 -1
- package/build/ui/react.js +18 -10
- package/build/ui/react.js.map +1 -1
- package/build/{dom → utils}/data.js +3 -0
- package/build/utils/data.js.map +1 -0
- package/build/utils/datapath.js +6 -2
- package/build/utils/datapath.js.map +1 -1
- package/build/utils/settings.js +154 -52
- package/build/utils/settings.js.map +1 -1
- package/dist/browser/headless.cjs +6 -6
- package/dist/browser/headless.cjs.map +4 -4
- package/dist/browser/headless.esm.js +6 -6
- package/dist/browser/headless.esm.js.map +1 -1
- package/dist/browser/index.cjs +7 -7
- package/dist/browser/index.cjs.map +4 -4
- package/dist/browser/index.esm.js +7 -7
- package/dist/browser/index.esm.js.map +1 -1
- package/dist/browser/react.cjs +6 -6
- package/dist/browser/react.cjs.map +4 -4
- package/dist/browser/react.esm.js +6 -6
- package/dist/browser/react.esm.js.map +1 -1
- package/dist/node/headless.cjs +4 -4
- package/dist/node/headless.cjs.map +4 -4
- package/dist/node/headless.esm.js +4 -4
- package/dist/node/headless.esm.js.map +4 -4
- package/dist/node/index.cjs +6 -6
- package/dist/node/index.cjs.map +4 -4
- package/dist/node/index.esm.js +6 -6
- package/dist/node/index.esm.js.map +4 -4
- package/dist/node/react.cjs +5 -5
- package/dist/node/react.cjs.map +4 -4
- package/dist/node/react.esm.js +5 -5
- package/dist/node/react.esm.js.map +4 -4
- package/headless.cjs +4 -4
- package/headless.cjs.map +4 -4
- package/headless.js +4 -4
- package/headless.js.map +4 -4
- package/package.json +1 -1
- package/react.cjs +6 -6
- package/react.cjs.map +4 -4
- package/react.js +6 -6
- package/react.js.map +4 -4
- package/src/components/FEAASComponent.tsx +11 -5
- package/src/components/FEAASElement.tsx +18 -3
- package/src/dom/component.ts +2 -2
- package/src/dom/html.ts +2 -2
- package/src/dom/rendering.ts +9 -7
- package/src/headless.ts +5 -5
- package/src/ui/FEAASExternal.tsx +10 -0
- package/src/ui/FEAASPicker.tsx +10 -2
- package/src/ui/react.tsx +24 -11
- package/src/{dom → utils}/data.ts +3 -1
- package/src/utils/datapath.ts +7 -2
- package/src/utils/settings.ts +184 -53
- package/test/FEAASComponentData.test.tsx +56 -52
- package/test/ServerComponent.test.tsx +14 -3
- package/test/cdn.ts +1 -1
- package/test/datasettings.test.ts +248 -0
- package/test/{extras.ts → extras.test.ts} +1 -1
- package/test/fetch.ts +2 -2
- package/types/components/FEAASComponent.d.ts +7 -4
- package/types/components/FEAASComponent.d.ts.map +1 -1
- package/types/components/FEAASElement.d.ts +1 -0
- package/types/components/FEAASElement.d.ts.map +1 -1
- package/types/dom/component.d.ts +2 -2
- package/types/dom/component.d.ts.map +1 -1
- package/types/dom/html.d.ts +2 -2
- package/types/dom/html.d.ts.map +1 -1
- package/types/dom/rendering.d.ts +8 -7
- package/types/dom/rendering.d.ts.map +1 -1
- package/types/headless.d.ts +5 -5
- package/types/headless.d.ts.map +1 -1
- package/types/tsbuildinfo +1 -1
- package/types/ui/FEAASExternal.d.ts +4 -0
- package/types/ui/FEAASExternal.d.ts.map +1 -1
- package/types/ui/FEAASPicker.d.ts +3 -3
- package/types/ui/FEAASPicker.d.ts.map +1 -1
- package/types/ui/react.d.ts +7 -5
- package/types/ui/react.d.ts.map +1 -1
- package/types/ui/tsbuildinfo +1 -1
- package/types/{dom → utils}/data.d.ts +1 -1
- package/types/utils/data.d.ts.map +1 -0
- package/types/utils/datapath.d.ts +1 -0
- package/types/utils/datapath.d.ts.map +1 -1
- package/types/utils/settings.d.ts +85 -9
- package/types/utils/settings.d.ts.map +1 -1
- package/build/dom/data.js.map +0 -1
- package/build/utils/fetch.js +0 -40
- package/build/utils/fetch.js.map +0 -1
- package/src/utils/fetch.ts +0 -60
- package/test/cjs/tsconfig.json +0 -9
- package/test/fetch-data.ts +0 -55
- package/test/settings.test.ts +0 -78
- package/types/dom/data.d.ts.map +0 -1
- package/types/utils/fetch.d.ts +0 -9
- package/types/utils/fetch.d.ts.map +0 -1
|
@@ -2,7 +2,7 @@ import { DEFAULT_CDN_URL, FEAASCDNComponentParams, fetchAndRevalidateComponent,
|
|
|
2
2
|
import { renderDOMContent, renderDOMElement } from '../dom/rendering.js'
|
|
3
3
|
import { loadStylesheetAllowStale } from '../headless.js'
|
|
4
4
|
import { ControlledPromise } from '../utils/promise.js'
|
|
5
|
-
import {
|
|
5
|
+
import { DataOptions, DataSettings } from '../utils/settings.js'
|
|
6
6
|
import { FEAASElement } from './FEAASElement.js'
|
|
7
7
|
|
|
8
8
|
export function FEAASComponentsProps(element: FEAASElement) {
|
|
@@ -25,7 +25,7 @@ export function FEAASComponentsProps(element: FEAASElement) {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
return {
|
|
28
|
-
data: data || {},
|
|
28
|
+
data: (data || {}) as DataOptions,
|
|
29
29
|
cdn: element.getContextAttribute('cdn') ?? cdn ?? DEFAULT_CDN_URL,
|
|
30
30
|
library: element.getAttribute('library') ?? library,
|
|
31
31
|
component: element.getAttribute('component') ?? component,
|
|
@@ -94,12 +94,13 @@ export class FEAASComponent extends FEAASElement<FEAASComponentsProps, string> {
|
|
|
94
94
|
version: 'responsive',
|
|
95
95
|
withStylesheet: true,
|
|
96
96
|
editable: false,
|
|
97
|
-
suspended: false
|
|
97
|
+
suspended: false,
|
|
98
|
+
data: {}
|
|
98
99
|
} as Partial<FEAASComponentsProps>
|
|
99
100
|
|
|
100
101
|
needsRefresh: boolean
|
|
101
102
|
|
|
102
|
-
setData(data:
|
|
103
|
+
setData(data: DataOptions) {
|
|
103
104
|
this.set({ data })
|
|
104
105
|
}
|
|
105
106
|
|
|
@@ -107,6 +108,10 @@ export class FEAASComponent extends FEAASElement<FEAASComponentsProps, string> {
|
|
|
107
108
|
return FEAASComponentsProps(this)
|
|
108
109
|
}
|
|
109
110
|
|
|
111
|
+
isJSONAttribute(prop: keyof FEAASComponentsProps) {
|
|
112
|
+
return prop == 'data'
|
|
113
|
+
}
|
|
114
|
+
|
|
110
115
|
attributeChangedCallback(name: string, oldValue: string, newValue: string): void {
|
|
111
116
|
if (oldValue === newValue) return
|
|
112
117
|
|
|
@@ -130,7 +135,7 @@ export class FEAASComponent extends FEAASElement<FEAASComponentsProps, string> {
|
|
|
130
135
|
|
|
131
136
|
this.whenDataReady = this.whenDataReady.restart()
|
|
132
137
|
|
|
133
|
-
const data = await
|
|
138
|
+
const data = await DataSettings.fetch(this.props.data)
|
|
134
139
|
|
|
135
140
|
const alreadyRendered = this.alreadyRendered
|
|
136
141
|
|
|
@@ -253,6 +258,7 @@ declare global {
|
|
|
253
258
|
interface IntrinsicElements {
|
|
254
259
|
'feaas-component': {
|
|
255
260
|
data?: any
|
|
261
|
+
suppressHydrationWarning?: boolean
|
|
256
262
|
template?: string
|
|
257
263
|
instance?: string
|
|
258
264
|
editable?: boolean
|
|
@@ -137,6 +137,10 @@ export class FEAASElement<Props = any, Payload = any> extends WebComponent {
|
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
+
isJSONAttribute(attribute: keyof Props) {
|
|
141
|
+
return false
|
|
142
|
+
}
|
|
143
|
+
|
|
140
144
|
log(...args: any) {
|
|
141
145
|
if (typeof process != 'undefined' && process.env.TESTING) return
|
|
142
146
|
console.log(...args)
|
|
@@ -445,12 +449,23 @@ export class FEAASElement<Props = any, Payload = any> extends WebComponent {
|
|
|
445
449
|
...this.overrides
|
|
446
450
|
}
|
|
447
451
|
for (var property in this.props) {
|
|
452
|
+
const isJSON = this.isJSONAttribute(property)
|
|
448
453
|
const value = this.props[property]
|
|
454
|
+
const valueAsString = isJSON ? JSON.stringify(value) : String(value)
|
|
455
|
+
const defaultValue = this.defaultProps[property as keyof typeof this.defaultProps]
|
|
456
|
+
const defaultValueAsString = isJSON ? JSON.stringify(defaultValue) : String(defaultValue)
|
|
449
457
|
const attribute = property.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()
|
|
450
|
-
|
|
458
|
+
const oldValue = this.getAttribute(attribute)
|
|
459
|
+
try {
|
|
460
|
+
var odlValueAsString: string = isJSON ? JSON.stringify(JSON.parse(oldValue)) : oldValue
|
|
461
|
+
} catch (e) {
|
|
462
|
+
var odlValueAsString: string = null
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
if (value == null || valueAsString === defaultValueAsString) {
|
|
451
466
|
this.removeAttribute(attribute)
|
|
452
|
-
} else if (typeof value == 'string' || typeof value == 'number' || typeof value == 'boolean') {
|
|
453
|
-
if (
|
|
467
|
+
} else if (typeof value == 'string' || typeof value == 'number' || typeof value == 'boolean' || isJSON) {
|
|
468
|
+
if (odlValueAsString != valueAsString) this.setAttribute(attribute, valueAsString)
|
|
454
469
|
}
|
|
455
470
|
}
|
|
456
471
|
} catch (e) {
|
package/src/dom/component.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { fetchAndRevalidateComponent } from '../cdn.js'
|
|
2
|
-
import {
|
|
2
|
+
import { DataScopes } from '../utils/settings.js'
|
|
3
3
|
import { FEAASComponentProps, renderDOMContent, renderDOMElement } from './rendering.js'
|
|
4
4
|
|
|
5
|
-
export function renderComponentElement(content: string, datasources?:
|
|
5
|
+
export function renderComponentElement(content: string, datasources?: DataScopes) {
|
|
6
6
|
const wrapper = document.createElement('div')
|
|
7
7
|
wrapper.innerHTML = content
|
|
8
8
|
const fragment = document.createDocumentFragment()
|
package/src/dom/html.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** Lightweight DOM parser and serializer to use in server environments */
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { DataScopes } from '../utils/settings.js'
|
|
4
4
|
import { FEAASCustomizations, renderDOMElement } from './rendering.js'
|
|
5
5
|
|
|
6
6
|
type HTMLAttribute = { name: string; value: string }
|
|
@@ -323,7 +323,7 @@ export function serializeHTML(nodes: HTMLNode[]): string {
|
|
|
323
323
|
.join('')
|
|
324
324
|
}
|
|
325
325
|
|
|
326
|
-
export function renderHTMLContent(template: string, data?:
|
|
326
|
+
export function renderHTMLContent(template: string, data?: DataScopes, config?: FEAASCustomizations) {
|
|
327
327
|
const newTemplate = config?.processTemplate?.(null, template, data) || template
|
|
328
328
|
const nodes = parseHTML(newTemplate)
|
|
329
329
|
return renderDOMElement(nodes[0] as unknown as HTMLElement, data, config).outerHTML
|
package/src/dom/rendering.ts
CHANGED
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
import { FEAASCDNComponentProps } from '../cdn.js'
|
|
2
|
-
import {
|
|
2
|
+
import { queryScopes, serializeCH1RichText } from '../utils/data.js'
|
|
3
|
+
import { Data, DataScopes } from '../utils/settings.js'
|
|
3
4
|
import { parseHTML } from './html.js'
|
|
4
5
|
import { loadScript } from './scripts.js'
|
|
5
6
|
import { normalizeCollectionScope } from '../utils/datapath.js'
|
|
7
|
+
import { DataOptions } from '../headless.js'
|
|
6
8
|
|
|
7
9
|
export interface FEAASCustomizations {
|
|
8
|
-
processTemplate?: (element: HTMLElement, template: string, data:
|
|
9
|
-
processData?: (element: HTMLElement, data:
|
|
10
|
+
processTemplate?: (element: HTMLElement, template: string, data: DataScopes) => string
|
|
11
|
+
processData?: (element: HTMLElement, data: DataScopes) => any
|
|
10
12
|
processValue?: (element: HTMLElement, property: string, value: any) => any
|
|
11
13
|
shouldRepeat?: (element: HTMLElement, value: any) => boolean
|
|
12
|
-
onRender?: (element: HTMLElement, data:
|
|
14
|
+
onRender?: (element: HTMLElement, data: DataScopes) => void
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
export interface ExplicitTemplate {
|
|
16
18
|
template: string
|
|
17
19
|
}
|
|
18
20
|
|
|
19
|
-
export type FEAASComponentProps<T =
|
|
21
|
+
export type FEAASComponentProps<T = DataOptions> = Partial<ExplicitTemplate> & {
|
|
20
22
|
data?: T
|
|
21
23
|
} & (ExplicitTemplate | FEAASCDNComponentProps) &
|
|
22
24
|
FEAASCustomizations
|
|
@@ -197,7 +199,7 @@ function isElement(node: Node): node is HTMLElement {
|
|
|
197
199
|
return node.nodeType == 1
|
|
198
200
|
}
|
|
199
201
|
|
|
200
|
-
export function renderDOMContent(root: HTMLElement, template: string, data?:
|
|
202
|
+
export function renderDOMContent(root: HTMLElement, template: string, data?: Data, config?: FEAASCustomizations) {
|
|
201
203
|
const newTemplate = config?.processTemplate?.(root, template, data) || template
|
|
202
204
|
root.innerHTML = newTemplate
|
|
203
205
|
root.classList.add('-feaas')
|
|
@@ -213,7 +215,7 @@ export function renderDOMContent(root: HTMLElement, template: string, data?: Dat
|
|
|
213
215
|
return renderDOMElement(root, data, config)
|
|
214
216
|
}
|
|
215
217
|
|
|
216
|
-
export function renderHTMLContent(template: string, data?:
|
|
218
|
+
export function renderHTMLContent(template: string, data?: Data, config?: FEAASCustomizations, fakeDOM = false) {
|
|
217
219
|
const newTemplate = config?.processTemplate?.(null, template, data) || template
|
|
218
220
|
if (typeof document === 'undefined' || fakeDOM) {
|
|
219
221
|
const nodes = parseHTML(newTemplate)
|
package/src/headless.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import FEAASReactElement from './components/FEAASReactElement.js'
|
|
7
7
|
|
|
8
8
|
export * from './dom/rendering.js'
|
|
9
|
-
export * from './
|
|
9
|
+
export * from './utils/data.js'
|
|
10
10
|
export * from './dom/scripts.js'
|
|
11
11
|
export * from './dom/stylesheet.js'
|
|
12
12
|
export * from './dom/component.js'
|
|
@@ -20,10 +20,10 @@ export * from './components/FEAASLoader.js'
|
|
|
20
20
|
export * from './components/FEAASStylesheet.js'
|
|
21
21
|
export * from './utils/datapath.js'
|
|
22
22
|
export * as DOM from './dom/rendering.js'
|
|
23
|
-
export * as
|
|
24
|
-
export * as
|
|
25
|
-
export
|
|
26
|
-
export
|
|
23
|
+
export * as DataScope from './utils/data.js'
|
|
24
|
+
export * as DataPath from './utils/datapath.js'
|
|
25
|
+
export { DataSettings, fetchData } from './utils/settings.js'
|
|
26
|
+
export type { Data, DataOptionsById, DataOptions, DataScopes } from './utils/settings.js'
|
|
27
27
|
export * as Thumbnail from './dom/thumbnail.js'
|
|
28
28
|
|
|
29
29
|
declare global {
|
package/src/ui/FEAASExternal.tsx
CHANGED
|
@@ -232,6 +232,14 @@ export default class FEAASExternal extends FEAASElement<FEAASExternalProps, Reac
|
|
|
232
232
|
props?.src || (typeof window !== 'undefined' ? window.location.search.match(/feaasSrc=([^&]+)/)?.[1] : null)
|
|
233
233
|
return src ? <Component src={src} {...props} /> : null
|
|
234
234
|
}
|
|
235
|
+
|
|
236
|
+
static linkComponents(components: Promise<any>[]): () => any {
|
|
237
|
+
return () => components.map((components) => null)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
static getComponent(id: string) {
|
|
241
|
+
return FEAASExternal.registered[id]
|
|
242
|
+
}
|
|
235
243
|
}
|
|
236
244
|
|
|
237
245
|
declare global {
|
|
@@ -251,4 +259,6 @@ FEAASExternal.register('feaas-external')
|
|
|
251
259
|
FEAASExternal.setRegistrationCallback()
|
|
252
260
|
|
|
253
261
|
export const registerComponent = FEAASExternal.registerComponent
|
|
262
|
+
export const linkComponents = FEAASExternal.linkComponents
|
|
263
|
+
export const getComponent = FEAASExternal.getComponent
|
|
254
264
|
export { FEAASExternal as External }
|
package/src/ui/FEAASPicker.tsx
CHANGED
|
@@ -46,6 +46,12 @@ export class FEAASPicker extends FEAASElement<FEAASPickerProps, typeof FEAASPick
|
|
|
46
46
|
const components = useLibrary('components')
|
|
47
47
|
const rules = useLibrary('stylesheet.styles')
|
|
48
48
|
const component = components.find((c) => c.id == context?.getAttribute('component'))
|
|
49
|
+
var settings = context?.getAttribute('data')
|
|
50
|
+
if (settings) {
|
|
51
|
+
try {
|
|
52
|
+
settings = JSON.parse(settings)
|
|
53
|
+
} catch (e) {}
|
|
54
|
+
}
|
|
49
55
|
return (
|
|
50
56
|
<Picker
|
|
51
57
|
rules={rules}
|
|
@@ -53,9 +59,11 @@ export class FEAASPicker extends FEAASElement<FEAASPickerProps, typeof FEAASPick
|
|
|
53
59
|
themeContext={context}
|
|
54
60
|
placeholder={placeholder}
|
|
55
61
|
component={component}
|
|
56
|
-
|
|
57
|
-
|
|
62
|
+
data={settings}
|
|
63
|
+
versionId={context?.getAttribute('version')}
|
|
64
|
+
mode={mode || (component ? 'component' : null)}
|
|
58
65
|
setAttribute={(context, attribute, value) => {
|
|
66
|
+
if (attribute == 'data' && value) value = JSON.stringify(value)
|
|
59
67
|
this.props.setAttribute(context, attribute, value)
|
|
60
68
|
forceRender()
|
|
61
69
|
}}
|
package/src/ui/react.tsx
CHANGED
|
@@ -7,29 +7,34 @@ import {
|
|
|
7
7
|
} from './index.js'
|
|
8
8
|
import React from 'react'
|
|
9
9
|
import { renderHTMLContent } from '../dom/html.js'
|
|
10
|
-
import {
|
|
10
|
+
import { DataSettings } from '../utils/settings.js'
|
|
11
11
|
|
|
12
12
|
export { renderHTMLContent }
|
|
13
|
-
export { External } from './FEAASExternal.js'
|
|
13
|
+
export { External, linkComponents, registerComponent, getComponent } from './FEAASExternal.js'
|
|
14
14
|
export * from './index.js'
|
|
15
15
|
export * from '../headless.js'
|
|
16
16
|
|
|
17
|
-
export
|
|
18
|
-
|
|
17
|
+
export type FEAASReactComponentProps = FEAASComponentProps & {
|
|
18
|
+
src?: string
|
|
19
|
+
preload?: boolean
|
|
20
|
+
lastModified?: Date | string
|
|
21
|
+
}
|
|
22
|
+
export const ServerComponent = async (props: FEAASComponentProps) => {
|
|
23
|
+
const fetchedData = await DataSettings.fetch(props?.data || {})
|
|
19
24
|
|
|
20
|
-
const p = { ...props }
|
|
25
|
+
const p: FEAASReactComponentProps = { ...props }
|
|
21
26
|
|
|
22
|
-
if (('src' in
|
|
23
|
-
const response = await fetchComponent(
|
|
27
|
+
if (('src' in props || 'component' in props) && props?.template == null) {
|
|
28
|
+
const response = await fetchComponent(props)
|
|
24
29
|
|
|
25
30
|
p.template = response.template
|
|
26
|
-
|
|
31
|
+
p.lastModified = response.lastModified
|
|
27
32
|
}
|
|
28
33
|
|
|
29
|
-
return
|
|
34
|
+
return FEAASReactComponent({ ...p, data: fetchedData })
|
|
30
35
|
}
|
|
31
36
|
|
|
32
|
-
export function
|
|
37
|
+
export function FEAASReactComponent({ data, ...props }: FEAASReactComponentProps) {
|
|
33
38
|
let html = ''
|
|
34
39
|
let preload = null
|
|
35
40
|
|
|
@@ -39,6 +44,13 @@ export function FEAASEmbed({ data, ...props }: FEAASComponentProps<any> & { src?
|
|
|
39
44
|
|
|
40
45
|
if ('src' in props) {
|
|
41
46
|
props = { ...parseComponentSource(props.src), ...props }
|
|
47
|
+
} else if (!('template' in props)) {
|
|
48
|
+
if (!props.version) {
|
|
49
|
+
props = {
|
|
50
|
+
...props,
|
|
51
|
+
version: 'responsive'
|
|
52
|
+
}
|
|
53
|
+
}
|
|
42
54
|
}
|
|
43
55
|
|
|
44
56
|
return (
|
|
@@ -51,6 +63,7 @@ export function FEAASEmbed({ data, ...props }: FEAASComponentProps<any> & { src?
|
|
|
51
63
|
)}
|
|
52
64
|
<feaas-component
|
|
53
65
|
{...props}
|
|
66
|
+
suppressHydrationWarning={true}
|
|
54
67
|
template={null}
|
|
55
68
|
data={typeof data == 'string' ? data : data ? JSON.stringify(data) : null}
|
|
56
69
|
dangerouslySetInnerHTML={{ __html: html || '' }}
|
|
@@ -59,4 +72,4 @@ export function FEAASEmbed({ data, ...props }: FEAASComponentProps<any> & { src?
|
|
|
59
72
|
)
|
|
60
73
|
}
|
|
61
74
|
|
|
62
|
-
export const Component =
|
|
75
|
+
export const Component = FEAASReactComponent
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { JSONPath } from 'jsonpath-plus'
|
|
2
2
|
|
|
3
|
-
export
|
|
3
|
+
export function queryObject(json: any, path: string) {
|
|
4
|
+
return JSONPath({ path, json })
|
|
5
|
+
}
|
|
4
6
|
|
|
5
7
|
export function queryScopes(scopes: any, datapath: string, singleValue = false, json: any = scopes) {
|
|
6
8
|
var path = datapath
|
package/src/utils/datapath.ts
CHANGED
|
@@ -20,16 +20,21 @@ export const parseRange = (qualifier: string) => {
|
|
|
20
20
|
return bits.join(',')
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
export const getQualifier = (jsonpath: string) => jsonpath?.match(/(\.\*|\[[^\]]
|
|
23
|
+
export const getQualifier = (jsonpath: string) => jsonpath?.match(/(\.\*|\[[^\]]*\])$/)?.[0]
|
|
24
24
|
|
|
25
25
|
export function cleanCollectionBit(path: string) {
|
|
26
|
-
return path.replace(/(
|
|
26
|
+
return path.replace(/(\.?\[[^\]\[]*\]|\.\*)$/g, '')
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
export function normalizeCollectionScope(path: string) {
|
|
30
30
|
return cleanCollectionBit(path) + '.*'
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
// increment numbers by given numbers, exclude second value of ranges to ensure inclusivity
|
|
34
|
+
export function adjustNumbers(string: string, diff: number) {
|
|
35
|
+
return string?.replace(/(.|^)(\d+)/g, (m, b, n) => b + (b == ':' ? n : String(parseInt(n) + diff)))
|
|
36
|
+
}
|
|
37
|
+
|
|
33
38
|
export function getHumanizedLabel(path: string, maxBits = 2) {
|
|
34
39
|
var bits = cleanCollectionBit(path)
|
|
35
40
|
.split(/]\.|\.\*\./g)
|
package/src/utils/settings.ts
CHANGED
|
@@ -1,69 +1,200 @@
|
|
|
1
|
-
|
|
2
|
-
let cleanSettings = {}
|
|
3
|
-
|
|
4
|
-
const hasNoBody =
|
|
5
|
-
settings?.method === 'GET' || settings?.method === 'HEAD' || Object.keys(settings?.body || {}).length === 0
|
|
6
|
-
|
|
7
|
-
const hasNoHeaders =
|
|
8
|
-
settings.headers &&
|
|
9
|
-
(Object.keys(settings.headers).length === 0 ||
|
|
10
|
-
(Object.keys(settings.headers).length === 1 &&
|
|
11
|
-
(settings.headers['Content-type'] || settings.headers['Content-Type']) &&
|
|
12
|
-
(settings.headers['Content-type'] === 'application/json' ||
|
|
13
|
-
settings.headers['Content-Type'] === 'application/json')))
|
|
14
|
-
|
|
15
|
-
const hasNoParams = settings.params || Object.keys(settings.params || {}).length === 0
|
|
16
|
-
|
|
17
|
-
if (settings.method && settings.method !== 'GET') cleanSettings = { ...cleanSettings, method: settings.method }
|
|
18
|
-
if (settings.body && !hasNoBody) cleanSettings = { ...cleanSettings, body: settings.body }
|
|
19
|
-
cleanSettings = {
|
|
20
|
-
...cleanSettings,
|
|
21
|
-
headers: hasNoHeaders ? { 'Content-type': 'application/json' } : settings.headers
|
|
22
|
-
}
|
|
23
|
-
if (settings.params && !hasNoParams) cleanSettings = { ...cleanSettings, params: settings.params }
|
|
1
|
+
import { queryObject } from './data.js'
|
|
24
2
|
|
|
25
|
-
|
|
26
|
-
}
|
|
3
|
+
export type DataSourceId = string
|
|
27
4
|
|
|
28
|
-
|
|
29
|
-
|
|
5
|
+
// Fully resolved data - object or array
|
|
6
|
+
export type Data = Record<string, any> | any[]
|
|
30
7
|
|
|
31
|
-
|
|
8
|
+
// Data keyed by datasource id
|
|
9
|
+
export type DataScopes = Data | Record<DataSourceId, Data>
|
|
32
10
|
|
|
33
|
-
|
|
11
|
+
// Data settings is json object representing fetch call
|
|
12
|
+
export type DataSettings = ReturnType<typeof DataSettings>
|
|
34
13
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const isUrlEncoded = contentType === 'application/x-www-form-urlencoded'
|
|
14
|
+
// Data options/samples indexed by id
|
|
15
|
+
export type DataOptionsById = Record<DataSourceId, DataSettings | Data>
|
|
38
16
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
body = Object.keys(normalized.body).reduce((form: any, key) => {
|
|
42
|
-
form.append(key, normalized.body[key])
|
|
43
|
-
return form
|
|
44
|
-
}, new FormData())
|
|
45
|
-
if (isUrlEncoded) body = new URLSearchParams(normalized.body)
|
|
17
|
+
// Data options combines of the above: data, settings, plain or keyed by datasource id
|
|
18
|
+
export type DataOptions = Data | DataSettings | DataOptionsById
|
|
46
19
|
|
|
47
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Cleans the provided settings object by removing unnecessary properties that match the defaults that will be re-added
|
|
22
|
+
* back through normalizations. This method is used for code generated for the user, neant to make it look neater.
|
|
23
|
+
*
|
|
24
|
+
* @param {any} settings - The settings object to be cleaned.
|
|
25
|
+
* @returns {Object} - The cleaned settings object.
|
|
26
|
+
*/
|
|
27
|
+
export function clean(settings: any) {
|
|
28
|
+
const { params, method, headers, body, url } = DataSettings(settings)
|
|
29
|
+
var cleanSettings = { url } as Partial<DataSettings>
|
|
30
|
+
var cleanHeaders = { ...headers }
|
|
31
|
+
if (headers['Content-Type'] === 'application/json') delete cleanHeaders['Content-Type']
|
|
32
|
+
if (headers['Accept'] === 'application/json') delete cleanHeaders['Accept']
|
|
33
|
+
if (Object.keys(cleanHeaders).length > 0) {
|
|
34
|
+
cleanSettings.headers = cleanHeaders
|
|
35
|
+
}
|
|
36
|
+
if (method !== 'GET') {
|
|
37
|
+
cleanSettings.method = method
|
|
38
|
+
}
|
|
39
|
+
if (!(method === 'GET' || method === 'HEAD' || Object.keys(body).length === 0)) {
|
|
40
|
+
cleanSettings.body = body
|
|
41
|
+
}
|
|
42
|
+
if (Object.keys(params).length > 0) {
|
|
43
|
+
cleanSettings = { ...cleanSettings, params }
|
|
44
|
+
}
|
|
45
|
+
return cleanSettings
|
|
46
|
+
}
|
|
48
47
|
|
|
49
|
-
|
|
48
|
+
/**
|
|
49
|
+
* Checks if the provided data is of type DataSettings by checking if all keys are recognized as settings keys.
|
|
50
|
+
*
|
|
51
|
+
* @param {any} data - The data to be checked.
|
|
52
|
+
* @returns {boolean} - True if the data is of type DataSettings, false otherwise.
|
|
53
|
+
*/
|
|
54
|
+
export const isDataSettings = (data: any): data is DataSettings => {
|
|
55
|
+
if (typeof data.url != 'string') return false
|
|
56
|
+
|
|
57
|
+
for (var property in data) {
|
|
58
|
+
if (
|
|
59
|
+
property == 'body' ||
|
|
60
|
+
property == 'params' ||
|
|
61
|
+
property == 'headers' ||
|
|
62
|
+
property == 'url' ||
|
|
63
|
+
property === 'method' ||
|
|
64
|
+
(property == 'jsonpath' && typeof data[property] == 'string')
|
|
65
|
+
) {
|
|
66
|
+
continue
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return false
|
|
70
|
+
}
|
|
50
71
|
|
|
51
|
-
|
|
72
|
+
return true
|
|
73
|
+
}
|
|
52
74
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
75
|
+
/**
|
|
76
|
+
* Converts settings object into fetch function arguments.
|
|
77
|
+
*
|
|
78
|
+
* @param {any} settings - The settings object.
|
|
79
|
+
* @returns {Array} - An array containing URL and options objects as fetch function arguments.
|
|
80
|
+
*/
|
|
81
|
+
export function toFetchArguments(settings?: any): Parameters<typeof fetch> {
|
|
82
|
+
const { url, headers, params, method, body: payload } = DataSettings(settings)
|
|
83
|
+
|
|
84
|
+
let body = undefined
|
|
85
|
+
|
|
86
|
+
const contentType = headers['Content-Type']
|
|
87
|
+
|
|
88
|
+
if (method != 'GET' && method != 'HEAD') {
|
|
89
|
+
if (contentType === 'application/json') body = JSON.stringify(payload)
|
|
90
|
+
if (contentType === 'multipart/form-data')
|
|
91
|
+
body = Object.keys(payload).reduce((form, key) => {
|
|
92
|
+
form.append(key, (payload as any)[key])
|
|
93
|
+
return form
|
|
94
|
+
}, new FormData())
|
|
95
|
+
if (contentType === 'application/x-www-form-urlencoded') body = new URLSearchParams(payload)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const query = Object.keys(params).length ? `?${new URLSearchParams(params)}` : ''
|
|
56
99
|
|
|
57
|
-
return [
|
|
100
|
+
return [
|
|
101
|
+
`${url}${query}`,
|
|
102
|
+
{
|
|
103
|
+
headers,
|
|
104
|
+
body,
|
|
105
|
+
method
|
|
106
|
+
}
|
|
107
|
+
]
|
|
58
108
|
}
|
|
59
109
|
|
|
60
|
-
|
|
110
|
+
/**
|
|
111
|
+
* Creates a DataSettings object based on the provided settings. These settings are used later to fetch data for the
|
|
112
|
+
* component. It's essentially fetch options with url.
|
|
113
|
+
*
|
|
114
|
+
* @param {any} settings - The settings object.
|
|
115
|
+
* @returns {DataSettings} - The DataSettings object.
|
|
116
|
+
*/
|
|
117
|
+
export function DataSettings(settings: any) {
|
|
118
|
+
const headers = {} as any
|
|
119
|
+
for (const key in settings?.headers || {}) {
|
|
120
|
+
if (settings.headers.hasOwnProperty(key)) {
|
|
121
|
+
const headerCase = key.replace(/^[a-z]|\-[a-z]/g, (m) => m.toUpperCase())
|
|
122
|
+
headers[headerCase] = settings.headers[key]
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (!headers.Accept) headers['Accept'] = 'application/json'
|
|
61
126
|
return {
|
|
62
|
-
params: settings?.params || {},
|
|
63
|
-
headers:
|
|
64
|
-
jsonpath: settings?.jsonpath || '$',
|
|
65
|
-
method: settings?.method || 'GET'
|
|
66
|
-
|
|
67
|
-
|
|
127
|
+
params: (settings?.params || {}) as Record<string, any>,
|
|
128
|
+
headers: headers as Record<string, string>,
|
|
129
|
+
jsonpath: String(settings?.jsonpath || '$'),
|
|
130
|
+
method: (settings?.method || 'GET').toUpperCase() as
|
|
131
|
+
| 'GET'
|
|
132
|
+
| 'POST'
|
|
133
|
+
| 'OPTIONS'
|
|
134
|
+
| 'PUT'
|
|
135
|
+
| 'DELETE'
|
|
136
|
+
| 'UPDATE'
|
|
137
|
+
| 'HEAD',
|
|
138
|
+
body: (settings?.body || {}) as Record<string, any>,
|
|
139
|
+
url: String(settings?.url || '')
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Fetches data based on the provided settings. It is expected that reesponse is json (forced by Accept header)
|
|
144
|
+
*
|
|
145
|
+
* @param {DataSettings} settings - The data settings.
|
|
146
|
+
* @returns {Promise<Data>} - A promise that resolves to the fetched data.
|
|
147
|
+
*/
|
|
148
|
+
const fetchDataSettings = async (settings: DataSettings): Promise<Data> => {
|
|
149
|
+
try {
|
|
150
|
+
const [url, options] = toFetchArguments(settings)
|
|
151
|
+
const response = await DataSettings.fetchImplementation(url, {
|
|
152
|
+
...options,
|
|
153
|
+
// next.js only allows lowercase method
|
|
154
|
+
method: options.method.toLowerCase()
|
|
155
|
+
})
|
|
156
|
+
const json = await response.json()
|
|
157
|
+
if (settings.jsonpath && settings.jsonpath != '$') {
|
|
158
|
+
return queryObject(json, settings.jsonpath)
|
|
159
|
+
}
|
|
160
|
+
return json
|
|
161
|
+
} catch (e) {
|
|
162
|
+
return {}
|
|
68
163
|
}
|
|
69
164
|
}
|
|
165
|
+
/**
|
|
166
|
+
* Fetches data based on the provided data options. Options is a union of different types, settngs, data or
|
|
167
|
+
* datasource-id keyed settings/data. If the function doesnt find settings in the object, it returns it as is. It
|
|
168
|
+
* supports mixing of static and fetched data. If multiple data settings are found, requests are made in parallel.
|
|
169
|
+
*
|
|
170
|
+
* @param {DataOptions} data - The data options.
|
|
171
|
+
* @returns {Promise<DataScopes>} - A promise that resolves to the fetched data.
|
|
172
|
+
*/
|
|
173
|
+
export async function fetchData(data: DataOptions): Promise<DataScopes> {
|
|
174
|
+
if (!data) return data
|
|
175
|
+
|
|
176
|
+
if (isDataSettings(data)) return fetchDataSettings(data)
|
|
177
|
+
|
|
178
|
+
const keys = Object.keys(data)
|
|
179
|
+
|
|
180
|
+
const allRequests = await Promise.all(
|
|
181
|
+
keys
|
|
182
|
+
.map((key: keyof typeof data) => (isDataSettings(data[key]) ? fetchDataSettings(data[key]) : null))
|
|
183
|
+
.filter(Boolean)
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
return keys.reduce(
|
|
187
|
+
(obj, key: keyof typeof data) => ({
|
|
188
|
+
...obj,
|
|
189
|
+
[key]: isDataSettings(data[key]) ? allRequests.shift() : data[key]
|
|
190
|
+
}),
|
|
191
|
+
{}
|
|
192
|
+
)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
DataSettings.clean = clean
|
|
196
|
+
DataSettings.toFetchArguments = toFetchArguments
|
|
197
|
+
DataSettings.fetchOne = fetchDataSettings
|
|
198
|
+
DataSettings.fetch = fetchData
|
|
199
|
+
DataSettings.isDataSettings = isDataSettings
|
|
200
|
+
DataSettings.fetchImplementation = (...args: Parameters<typeof fetch>) => fetch(...args)
|