From 5fe1656a6c154a2b14f24ae3d7f533917a88dce7 Mon Sep 17 00:00:00 2001 From: Jacob Mischka Date: Wed, 26 Apr 2023 16:18:58 -0500 Subject: [PATCH 1/4] Accept eventual values for all meta item props except label Needs some UI work and additional testing, but seems to work! --- src/classes/IOClient.ts | 29 ++++- src/classes/IOComponent.ts | 8 ++ src/classes/IOPromise.ts | 14 +++ src/classes/Layout.ts | 13 ++- src/components/displayMetadata.ts | 187 ++++++++++++++++++++++++++++++ src/examples/basic/index.ts | 22 +++- src/types.ts | 6 +- 7 files changed, 265 insertions(+), 14 deletions(-) create mode 100644 src/components/displayMetadata.ts diff --git a/src/classes/IOClient.ts b/src/classes/IOClient.ts index da80541..fe571cb 100644 --- a/src/classes/IOClient.ts +++ b/src/classes/IOClient.ts @@ -1,5 +1,6 @@ import { v4 } from 'uuid' import { z } from 'zod' +import { Evt } from 'evt' import superjson from '../utils/superjson' import { T_IO_RENDER_INPUT, @@ -35,6 +36,7 @@ import displayGrid from '../components/displayGrid' import displayLink from '../components/displayLink' import displayImage from '../components/displayImage' import displayVideo from '../components/displayVideo' +import displayMetadata from '../components/displayMetadata' import urlInput from '../components/url' import { date, datetime } from '../components/inputDate' import { file } from '../components/upload' @@ -413,7 +415,8 @@ export class IOClient { Props, Output, DefaultValue - > + >, + onPropsUpdate?: Evt> ) { let props: T_IO_PROPS = inputProps ? (inputProps as T_IO_PROPS) @@ -427,7 +430,8 @@ export class IOClient { if (componentDef) { const componentGetters = componentDef.bind(this)( - inputProps ?? ({} as Props) + inputProps ?? ({} as Props), + onPropsUpdate ) if (componentGetters.props) { @@ -540,6 +544,8 @@ export class IOClient { } = {} ) { return (label: string, props?: Props) => { + const onPropsUpdate = new Evt>() + if (supportsMultiple(methodName)) { return new MultipleableIOPromise({ ...this.getPromiseProps( @@ -551,7 +557,8 @@ export class IOClient { Props, T_IO_RETURNS > - | undefined + | undefined, + onPropsUpdate as Evt> ), methodName: methodName as T_IO_MULTIPLEABLE_METHOD_NAMES, renderer: this.renderComponents.bind( @@ -559,6 +566,9 @@ export class IOClient { ) as ComponentRenderer, label, displayResolvesImmediately: this.displayResolvesImmediately, + onPropsUpdate: onPropsUpdate as Evt< + T_IO_PROPS + >, }) } @@ -573,7 +583,8 @@ export class IOClient { Props, T_IO_RETURNS > - | undefined + | undefined, + onPropsUpdate as Evt> ), methodName: methodName as T_IO_DISPLAY_METHOD_NAMES, renderer: this.renderComponents.bind( @@ -581,6 +592,9 @@ export class IOClient { ) as ComponentRenderer, label, displayResolvesImmediately: this.displayResolvesImmediately, + onPropsUpdate: onPropsUpdate as Evt< + T_IO_PROPS + >, }) : new InputIOPromise({ ...this.getPromiseProps( @@ -592,7 +606,8 @@ export class IOClient { Props, T_IO_RETURNS > - | undefined + | undefined, + onPropsUpdate as Evt> ), methodName: methodName as T_IO_INPUT_METHOD_NAMES, renderer: this.renderComponents.bind( @@ -600,6 +615,9 @@ export class IOClient { ) as ComponentRenderer, label, displayResolvesImmediately: this.displayResolvesImmediately, + onPropsUpdate: onPropsUpdate as Evt< + T_IO_PROPS + >, }) } } @@ -1016,6 +1034,7 @@ export class IOClient { * ``` */ metadata: this.createIOMethod('DISPLAY_METADATA', { + componentDef: displayMetadata, propsRequired: true, }), /** diff --git a/src/classes/IOComponent.ts b/src/classes/IOComponent.ts index c058369..6a29055 100644 --- a/src/classes/IOComponent.ts +++ b/src/classes/IOComponent.ts @@ -1,4 +1,5 @@ import { z, ZodError } from 'zod' +import { Evt } from 'evt' import { ioSchema, resolvesImmediately, @@ -70,6 +71,7 @@ export default class IOComponent { incomingState: z.infer ) => Promise>>) | undefined + onPropsUpdate: (() => T_IO_PROPS) | undefined validator: | IOPromiseValidator< @@ -100,6 +102,7 @@ export default class IOComponent { validator, multipleProps, displayResolvesImmediately, + onPropsUpdate, }: { methodName: MethodName label: string @@ -116,11 +119,16 @@ export default class IOComponent { defaultValue?: T_IO_RETURNS[] | null } displayResolvesImmediately?: boolean + onPropsUpdate?: Evt> }) { this.handleStateChange = onStateChange this.schema = ioSchema[methodName] this.validator = validator + if (onPropsUpdate) { + onPropsUpdate.attach(this.setProps.bind(this)) + } + try { initialProps = this.schema.props.parse(initialProps ?? {}) } catch (err) { diff --git a/src/classes/IOPromise.ts b/src/classes/IOPromise.ts index a52c5ca..9be031b 100644 --- a/src/classes/IOPromise.ts +++ b/src/classes/IOPromise.ts @@ -1,3 +1,4 @@ +import type { Evt } from 'evt' import { ioSchema, T_IO_DISPLAY_METHOD_NAMES, @@ -38,6 +39,7 @@ interface IOPromiseProps< methodName: MethodName label: string props: Props + onPropsUpdate?: Evt> valueGetter?: (response: ComponentReturnValue) => ComponentOutput onStateChange?: ( incomingState: T_IO_STATE @@ -71,6 +73,7 @@ export class IOPromise< | undefined /* @internal */ validator: IOPromiseValidator | undefined protected displayResolvesImmediately: boolean | undefined + protected onPropsUpdate: Evt> | undefined constructor({ renderer, @@ -81,6 +84,7 @@ export class IOPromise< onStateChange, validator, displayResolvesImmediately, + onPropsUpdate, }: IOPromiseProps) { this.renderer = renderer this.methodName = methodName @@ -90,6 +94,7 @@ export class IOPromise< this.onStateChange = onStateChange this.validator = validator this.displayResolvesImmediately = displayResolvesImmediately + this.onPropsUpdate = onPropsUpdate } then( @@ -130,6 +135,7 @@ export class IOPromise< initialProps: this.props, onStateChange: this.onStateChange, displayResolvesImmediately: this.displayResolvesImmediately, + onPropsUpdate: this.onPropsUpdate, }) } } @@ -172,6 +178,7 @@ export class InputIOPromise< onStateChange: this.onStateChange, validator: this.validator ? this.handleValidation.bind(this) : undefined, displayResolvesImmediately: this.displayResolvesImmediately, + onPropsUpdate: this.onPropsUpdate, }) } @@ -289,6 +296,7 @@ export class OptionalIOPromise< isOptional: true, validator: this.validator ? this.handleValidation.bind(this) : undefined, displayResolvesImmediately: this.displayResolvesImmediately, + onPropsUpdate: this.onPropsUpdate, }) } @@ -353,6 +361,7 @@ export class MultipleableIOPromise< ) => Promise> validator?: IOPromiseValidator | undefined displayResolvesImmediately?: boolean + onPropsUpdate?: Evt> }) { super(props) this.defaultValueGetter = defaultValueGetter @@ -439,6 +448,7 @@ export class MultipleIOPromise< incomingState: T_IO_STATE ) => Promise> validator?: IOPromiseValidator | undefined + onPropsUpdate?: Evt> }) { super(rest) this.getSingleValue = valueGetter @@ -512,6 +522,7 @@ export class MultipleIOPromise< defaultValue: this.defaultValue, }, displayResolvesImmediately: this.displayResolvesImmediately, + onPropsUpdate: this.onPropsUpdate, }) } @@ -572,6 +583,7 @@ export class OptionalMultipleIOPromise< incomingState: T_IO_STATE ) => Promise> validator?: IOPromiseValidator | undefined + onPropsUpdate?: Evt> }) { super(rest) this.getSingleValue = valueGetter @@ -645,6 +657,7 @@ export class OptionalMultipleIOPromise< defaultValue: this.defaultValue, }, displayResolvesImmediately: this.displayResolvesImmediately, + onPropsUpdate: this.onPropsUpdate, }) } } @@ -1037,6 +1050,7 @@ export class ExclusiveIOPromise< isOptional: false, validator: this.validator ? this.handleValidation.bind(this) : undefined, displayResolvesImmediately: this.displayResolvesImmediately, + onPropsUpdate: this.onPropsUpdate, }) } diff --git a/src/classes/Layout.ts b/src/classes/Layout.ts index fa8c5e6..3b9ab23 100644 --- a/src/classes/Layout.ts +++ b/src/classes/Layout.ts @@ -1,12 +1,13 @@ import { z } from 'zod' import { Literal, IO_RENDER, buttonItem, metaItemSchema } from '../ioSchema' -import { AnyDisplayIOPromise, ButtonItem, PageError } from '../types' +import { + AnyDisplayIOPromise, + ButtonItem, + PageError, + EventualValue, +} from '../types' -type EventualString = - | string - | Promise - | (() => string) - | (() => Promise) +type EventualString = EventualValue export interface BasicLayoutConfig { title?: EventualString diff --git a/src/components/displayMetadata.ts b/src/components/displayMetadata.ts new file mode 100644 index 0000000..4e27edd --- /dev/null +++ b/src/components/displayMetadata.ts @@ -0,0 +1,187 @@ +import type { Evt } from 'evt' +import { + T_IO_PROPS, + Serializable, + SerializableRecord, + ImageSchema, +} from '../ioSchema' +import { EventualValue } from '../types' + +export interface EventualMetaItem { + label: string + value?: EventualValue + url?: EventualValue + image?: EventualValue + route?: EventualValue + /** @deprecated Please use `route` instead */ + action?: EventualValue + params?: EventualValue +} + +export default function displayMetadata( + props: Pick, 'layout'> & { + data: EventualMetaItem[] + }, + onPropsUpdate?: Evt> +): { props: T_IO_PROPS<'DISPLAY_METADATA'> } { + const layout = props.layout + const metaItems: EventualMetaItem[] = [] + const data: T_IO_PROPS<'DISPLAY_METADATA'>['data'] = props.data.map( + metaItem => { + metaItem = { ...metaItem } + + const initialItem: T_IO_PROPS<'DISPLAY_METADATA'>['data'][0] = { + label: metaItem.label, + } + + if ('value' in metaItem) { + if (typeof metaItem.value === 'function') { + metaItem.value = metaItem.value() + } + + if (!(metaItem.value instanceof Promise)) { + initialItem.value = metaItem.value + } + } + + if ('url' in metaItem) { + if (typeof metaItem.url === 'function') { + metaItem.url = metaItem.url() + } + + if (!(metaItem.url instanceof Promise)) { + initialItem.url = metaItem.url + } + } + + if ('image' in metaItem) { + if (typeof metaItem.image === 'function') { + metaItem.image = metaItem.image() + } + + if (!(metaItem.image instanceof Promise)) { + initialItem.image = metaItem.image + } + } + + if ('route' in metaItem) { + if (typeof metaItem.route === 'function') { + metaItem.route = metaItem.route() + } + + if (!(metaItem.route instanceof Promise)) { + initialItem.route = metaItem.route + } + } + + if ('action' in metaItem) { + if (typeof metaItem.action === 'function') { + metaItem.action = metaItem.action() + } + + if (!(metaItem.action instanceof Promise)) { + initialItem.action = metaItem.action + } + } + + if ('params' in metaItem) { + if (typeof metaItem.params === 'function') { + metaItem.params = metaItem.params() + } + + if (!(metaItem.params instanceof Promise)) { + initialItem.params = metaItem.params + } + } + + metaItems.push(metaItem) + + return initialItem + } + ) + + if (onPropsUpdate) { + for (let i = 0; i < metaItems.length; i++) { + const metaItem = metaItems[i] + + if ('value' in metaItem) { + if (metaItem.value instanceof Promise) { + metaItem.value.then(resolvedValue => { + data[i].value = resolvedValue + onPropsUpdate?.post({ + layout, + data, + }) + }) + } + } + + if ('url' in metaItem) { + if (metaItem.url instanceof Promise) { + metaItem.url.then(resolvedurl => { + data[i].url = resolvedurl + onPropsUpdate?.post({ + layout, + data, + }) + }) + } + } + + if ('image' in metaItem) { + if (metaItem.image instanceof Promise) { + metaItem.image.then(resolvedimage => { + data[i].image = resolvedimage + onPropsUpdate?.post({ + layout, + data, + }) + }) + } + } + + if ('route' in metaItem) { + if (metaItem.route instanceof Promise) { + metaItem.route.then(resolvedroute => { + data[i].route = resolvedroute + onPropsUpdate?.post({ + layout, + data, + }) + }) + } + } + + if ('action' in metaItem) { + if (metaItem.action instanceof Promise) { + metaItem.action.then(resolvedaction => { + data[i].action = resolvedaction + onPropsUpdate?.post({ + layout, + data, + }) + }) + } + } + + if ('params' in metaItem) { + if (metaItem.params instanceof Promise) { + metaItem.params.then(resolvedparams => { + data[i].params = resolvedparams + onPropsUpdate?.post({ + layout, + data, + }) + }) + } + } + } + } + + return { + props: { + data, + layout, + }, + } +} diff --git a/src/examples/basic/index.ts b/src/examples/basic/index.ts index 905a485..ec6820b 100644 --- a/src/examples/basic/index.ts +++ b/src/examples/basic/index.ts @@ -1,4 +1,3 @@ -import { T_IO_PROPS } from './../../ioSchema' import Interval, { IOError, io, ctx, Action, Page, Layout } from '../../index' import IntervalClient from '../../classes/IntervalClient' import { @@ -13,6 +12,7 @@ import { sleep, generateRows, } from '../utils/helpers' +import type { EventualMetaItem } from '../../components/displayMetadata' import * as table_actions from './table' import * as grid_actions from './grid' import unauthorized from './unauthorized' @@ -1058,7 +1058,7 @@ const interval = new Interval({ }) }, metadata: async (io, ctx) => { - const data: T_IO_PROPS<'DISPLAY_METADATA'>['data'] = [ + const data: EventualMetaItem[] = [ { label: 'Is true', value: true, @@ -1075,6 +1075,24 @@ const interval = new Interval({ label: 'Is empty string', value: '', }, + { + label: 'Is a promise', + value: new Promise(async resolve => { + await sleep(2000) + resolve('Done!') + }), + }, + { + label: 'Is a function', + value: () => 'Called it', + }, + { + label: 'Is an async function', + value: async () => { + await sleep(3500) + return 'Did it' + }, + }, { label: 'Is long string', value: diff --git a/src/types.ts b/src/types.ts index abec530..78b7798 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,4 +1,5 @@ import type { z } from 'zod' +import type { Evt } from 'evt' import type { T_IO_RENDER_INPUT, T_IO_RESPONSE, @@ -438,7 +439,8 @@ export type IOComponentDefinition< DefaultValue = Output > = ( this: IOClient, - props: Props + props: Props, + onPropsUpdate?: Evt> ) => { props?: T_IO_PROPS getValue?: (response: T_IO_RETURNS) => Output @@ -559,3 +561,5 @@ export type IntervalErrorProps = { } export type IntervalErrorHandler = (props: IntervalErrorProps) => void + +export type EventualValue = T | Promise | (() => T) | (() => Promise) From 6e1f79bf778828b2a0095c5d82eec62ca5eeb58b Mon Sep 17 00:00:00 2001 From: Jacob Mischka Date: Thu, 27 Apr 2023 16:05:46 -0500 Subject: [PATCH 2/4] Add eventual support to metadata in python SDK Small bug where we can't tell between intentional `None` and an awaiting async value without an `undefined` equivalent. Figuring that out. --- src/components/displayMetadata.ts | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/components/displayMetadata.ts b/src/components/displayMetadata.ts index 4e27edd..87f09c3 100644 --- a/src/components/displayMetadata.ts +++ b/src/components/displayMetadata.ts @@ -34,63 +34,78 @@ export default function displayMetadata( label: metaItem.label, } - if ('value' in metaItem) { + // Currently doing all of this repetitive work separately to leverage + // static type checking, but could be done more dynamically in a loop as well + + if ('value' in metaItem && metaItem.value !== undefined) { if (typeof metaItem.value === 'function') { metaItem.value = metaItem.value() } if (!(metaItem.value instanceof Promise)) { initialItem.value = metaItem.value + } else { + initialItem.value = undefined } } - if ('url' in metaItem) { + if ('url' in metaItem && metaItem.url !== undefined) { if (typeof metaItem.url === 'function') { metaItem.url = metaItem.url() } if (!(metaItem.url instanceof Promise)) { initialItem.url = metaItem.url + } else { + initialItem.url = undefined } } - if ('image' in metaItem) { + if ('image' in metaItem && metaItem.image !== undefined) { if (typeof metaItem.image === 'function') { metaItem.image = metaItem.image() } if (!(metaItem.image instanceof Promise)) { initialItem.image = metaItem.image + } else { + initialItem.image = undefined } } - if ('route' in metaItem) { + if ('route' in metaItem && metaItem.route !== undefined) { if (typeof metaItem.route === 'function') { metaItem.route = metaItem.route() } if (!(metaItem.route instanceof Promise)) { initialItem.route = metaItem.route + } else { + initialItem.route = undefined } } - if ('action' in metaItem) { + if ('action' in metaItem && metaItem.action !== undefined) { if (typeof metaItem.action === 'function') { metaItem.action = metaItem.action() } if (!(metaItem.action instanceof Promise)) { initialItem.action = metaItem.action + } else { + initialItem.action = undefined } } - if ('params' in metaItem) { + if ('params' in metaItem && metaItem.params !== undefined) { if (typeof metaItem.params === 'function') { metaItem.params = metaItem.params() } if (!(metaItem.params instanceof Promise)) { initialItem.params = metaItem.params + } else { + initialItem.params = undefined } } From bc06d60bdd8371736a6d7b3e4e3397d73898c41c Mon Sep 17 00:00:00 2001 From: Jacob Mischka Date: Fri, 28 Apr 2023 12:12:42 -0500 Subject: [PATCH 3/4] Add undefined support to python superjson, use for pending metadata We now determine between values which are defined with the value of `undefined`, and values that are not defined at all, and use that to determine when to show a loading skeleton for the metadata values. This is done using a special custom marker class Undefined in python which is supported by our superjson implementation. --- src/examples/basic/index.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/examples/basic/index.ts b/src/examples/basic/index.ts index ec6820b..41c8047 100644 --- a/src/examples/basic/index.ts +++ b/src/examples/basic/index.ts @@ -1071,6 +1071,10 @@ const interval = new Interval({ label: 'Is null', value: null, }, + { + label: 'Is undefined', + value: undefined, + }, { label: 'Is empty string', value: '', @@ -1120,10 +1124,14 @@ const interval = new Interval({ { label: 'Image', value: 'Optional caption', - image: { - url: 'https://picsum.photos/200/300', - size: 'small', - }, + image: new Promise(resolve => { + sleep(1500).then(() => { + resolve({ + url: 'https://picsum.photos/200/300', + size: 'small', + }) + }) + }), }, ] From 3b7addb8640822401de662f3ea9c58cc550e5122 Mon Sep 17 00:00:00 2001 From: Jacob Mischka Date: Fri, 28 Apr 2023 12:25:59 -0500 Subject: [PATCH 4/4] Catch and log errors in metadata prop Promises --- src/classes/IOClient.ts | 2 +- src/components/displayMetadata.ts | 349 +++++++++++++++++------------- src/examples/basic/index.ts | 6 + 3 files changed, 204 insertions(+), 153 deletions(-) diff --git a/src/classes/IOClient.ts b/src/classes/IOClient.ts index fe571cb..83cbd0b 100644 --- a/src/classes/IOClient.ts +++ b/src/classes/IOClient.ts @@ -1034,7 +1034,7 @@ export class IOClient { * ``` */ metadata: this.createIOMethod('DISPLAY_METADATA', { - componentDef: displayMetadata, + componentDef: displayMetadata(this.logger), propsRequired: true, }), /** diff --git a/src/components/displayMetadata.ts b/src/components/displayMetadata.ts index 87f09c3..e1fec5e 100644 --- a/src/components/displayMetadata.ts +++ b/src/components/displayMetadata.ts @@ -1,4 +1,5 @@ import type { Evt } from 'evt' +import Logger from '../classes/Logger' import { T_IO_PROPS, Serializable, @@ -18,185 +19,229 @@ export interface EventualMetaItem { params?: EventualValue } -export default function displayMetadata( - props: Pick, 'layout'> & { - data: EventualMetaItem[] - }, - onPropsUpdate?: Evt> -): { props: T_IO_PROPS<'DISPLAY_METADATA'> } { - const layout = props.layout - const metaItems: EventualMetaItem[] = [] - const data: T_IO_PROPS<'DISPLAY_METADATA'>['data'] = props.data.map( - metaItem => { - metaItem = { ...metaItem } - - const initialItem: T_IO_PROPS<'DISPLAY_METADATA'>['data'][0] = { - label: metaItem.label, - } - - // Currently doing all of this repetitive work separately to leverage - // static type checking, but could be done more dynamically in a loop as well +export default function displaymetadata(logger: Logger) { + return function displayMetadata( + props: Pick, 'layout'> & { + data: EventualMetaItem[] + }, + onPropsUpdate?: Evt> + ): { props: T_IO_PROPS<'DISPLAY_METADATA'> } { + const layout = props.layout + const metaItems: EventualMetaItem[] = [] + const data: T_IO_PROPS<'DISPLAY_METADATA'>['data'] = props.data.map( + metaItem => { + metaItem = { ...metaItem } - if ('value' in metaItem && metaItem.value !== undefined) { - if (typeof metaItem.value === 'function') { - metaItem.value = metaItem.value() + const initialItem: T_IO_PROPS<'DISPLAY_METADATA'>['data'][0] = { + label: metaItem.label, } - if (!(metaItem.value instanceof Promise)) { - initialItem.value = metaItem.value - } else { - initialItem.value = undefined - } - } + // Currently doing all of this repetitive work separately to leverage + // static type checking, but could be done more dynamically in a loop as well - if ('url' in metaItem && metaItem.url !== undefined) { - if (typeof metaItem.url === 'function') { - metaItem.url = metaItem.url() - } + if ('value' in metaItem && metaItem.value !== undefined) { + if (typeof metaItem.value === 'function') { + metaItem.value = metaItem.value() + } - if (!(metaItem.url instanceof Promise)) { - initialItem.url = metaItem.url - } else { - initialItem.url = undefined + if (!(metaItem.value instanceof Promise)) { + initialItem.value = metaItem.value + } else { + initialItem.value = undefined + } } - } - if ('image' in metaItem && metaItem.image !== undefined) { - if (typeof metaItem.image === 'function') { - metaItem.image = metaItem.image() - } + if ('url' in metaItem && metaItem.url !== undefined) { + if (typeof metaItem.url === 'function') { + metaItem.url = metaItem.url() + } - if (!(metaItem.image instanceof Promise)) { - initialItem.image = metaItem.image - } else { - initialItem.image = undefined - } - } - - if ('route' in metaItem && metaItem.route !== undefined) { - if (typeof metaItem.route === 'function') { - metaItem.route = metaItem.route() + if (!(metaItem.url instanceof Promise)) { + initialItem.url = metaItem.url + } else { + initialItem.url = undefined + } } - if (!(metaItem.route instanceof Promise)) { - initialItem.route = metaItem.route - } else { - initialItem.route = undefined - } - } - - if ('action' in metaItem && metaItem.action !== undefined) { - if (typeof metaItem.action === 'function') { - metaItem.action = metaItem.action() - } + if ('image' in metaItem && metaItem.image !== undefined) { + if (typeof metaItem.image === 'function') { + metaItem.image = metaItem.image() + } - if (!(metaItem.action instanceof Promise)) { - initialItem.action = metaItem.action - } else { - initialItem.action = undefined + if (!(metaItem.image instanceof Promise)) { + initialItem.image = metaItem.image + } else { + initialItem.image = undefined + } } - } - if ('params' in metaItem && metaItem.params !== undefined) { - if (typeof metaItem.params === 'function') { - metaItem.params = metaItem.params() - } + if ('route' in metaItem && metaItem.route !== undefined) { + if (typeof metaItem.route === 'function') { + metaItem.route = metaItem.route() + } - if (!(metaItem.params instanceof Promise)) { - initialItem.params = metaItem.params - } else { - initialItem.params = undefined + if (!(metaItem.route instanceof Promise)) { + initialItem.route = metaItem.route + } else { + initialItem.route = undefined + } } - } - metaItems.push(metaItem) + if ('action' in metaItem && metaItem.action !== undefined) { + if (typeof metaItem.action === 'function') { + metaItem.action = metaItem.action() + } - return initialItem - } - ) - - if (onPropsUpdate) { - for (let i = 0; i < metaItems.length; i++) { - const metaItem = metaItems[i] - - if ('value' in metaItem) { - if (metaItem.value instanceof Promise) { - metaItem.value.then(resolvedValue => { - data[i].value = resolvedValue - onPropsUpdate?.post({ - layout, - data, - }) - }) + if (!(metaItem.action instanceof Promise)) { + initialItem.action = metaItem.action + } else { + initialItem.action = undefined + } } - } - if ('url' in metaItem) { - if (metaItem.url instanceof Promise) { - metaItem.url.then(resolvedurl => { - data[i].url = resolvedurl - onPropsUpdate?.post({ - layout, - data, - }) - }) - } - } + if ('params' in metaItem && metaItem.params !== undefined) { + if (typeof metaItem.params === 'function') { + metaItem.params = metaItem.params() + } - if ('image' in metaItem) { - if (metaItem.image instanceof Promise) { - metaItem.image.then(resolvedimage => { - data[i].image = resolvedimage - onPropsUpdate?.post({ - layout, - data, - }) - }) + if (!(metaItem.params instanceof Promise)) { + initialItem.params = metaItem.params + } else { + initialItem.params = undefined + } } - } - if ('route' in metaItem) { - if (metaItem.route instanceof Promise) { - metaItem.route.then(resolvedroute => { - data[i].route = resolvedroute - onPropsUpdate?.post({ - layout, - data, - }) - }) - } - } + metaItems.push(metaItem) - if ('action' in metaItem) { - if (metaItem.action instanceof Promise) { - metaItem.action.then(resolvedaction => { - data[i].action = resolvedaction - onPropsUpdate?.post({ - layout, - data, - }) - }) - } + return initialItem } - - if ('params' in metaItem) { - if (metaItem.params instanceof Promise) { - metaItem.params.then(resolvedparams => { - data[i].params = resolvedparams - onPropsUpdate?.post({ - layout, - data, - }) - }) + ) + + if (onPropsUpdate) { + for (let i = 0; i < metaItems.length; i++) { + const metaItem = metaItems[i] + + if ('value' in metaItem) { + if (metaItem.value instanceof Promise) { + metaItem.value + .then(resolvedValue => { + data[i].value = resolvedValue + onPropsUpdate?.post({ + layout, + data, + }) + }) + .catch(err => { + logger.error( + 'Error updating metadata field "value" with result from Promise:', + err + ) + }) + } + } + + if ('url' in metaItem) { + if (metaItem.url instanceof Promise) { + metaItem.url + .then(resolvedurl => { + data[i].url = resolvedurl + onPropsUpdate?.post({ + layout, + data, + }) + }) + .catch(err => { + logger.error( + 'Error updating metadata field "url" with result from Promise:', + err + ) + }) + } + } + + if ('image' in metaItem) { + if (metaItem.image instanceof Promise) { + metaItem.image + .then(resolvedimage => { + data[i].image = resolvedimage + onPropsUpdate?.post({ + layout, + data, + }) + }) + .catch(err => { + logger.error( + 'Error updating metadata field "image" with result from Promise:', + err + ) + }) + } + } + + if ('route' in metaItem) { + if (metaItem.route instanceof Promise) { + metaItem.route + .then(resolvedroute => { + data[i].route = resolvedroute + onPropsUpdate?.post({ + layout, + data, + }) + }) + .catch(err => { + logger.error( + 'Error updating metadata field "route" with result from Promise:', + err + ) + }) + } + } + + if ('action' in metaItem) { + if (metaItem.action instanceof Promise) { + metaItem.action + .then(resolvedaction => { + data[i].action = resolvedaction + onPropsUpdate?.post({ + layout, + data, + }) + }) + .catch(err => { + logger.error( + 'Error updating metadata field "action" with result from Promise:', + err + ) + }) + } + } + + if ('params' in metaItem) { + if (metaItem.params instanceof Promise) { + metaItem.params + .then(resolvedparams => { + data[i].params = resolvedparams + onPropsUpdate?.post({ + layout, + data, + }) + }) + .catch(err => { + logger.error( + 'Error updating metadata field "params" with result from Promise:', + err + ) + }) + } } } } - } - return { - props: { - data, - layout, - }, + return { + props: { + data, + layout, + }, + } } } diff --git a/src/examples/basic/index.ts b/src/examples/basic/index.ts index 41c8047..b76a91d 100644 --- a/src/examples/basic/index.ts +++ b/src/examples/basic/index.ts @@ -1086,6 +1086,12 @@ const interval = new Interval({ resolve('Done!') }), }, + { + label: 'Throws an error', + value: new Promise(() => { + throw new Error('Oops!') + }), + }, { label: 'Is a function', value: () => 'Called it',